objc-js 0.0.12 → 0.0.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,342 @@
1
+ #ifndef FFI_UTILS_H
2
+ #define FFI_UTILS_H
3
+
4
+ #include <ffi.h>
5
+ #include <Foundation/Foundation.h>
6
+ #include <napi.h>
7
+ #include <objc/runtime.h>
8
+ #include <objc/message.h>
9
+ #include <vector>
10
+ #include <memory>
11
+ #include "debug.h"
12
+ #include "bridge.h"
13
+ #include "type-conversion.h"
14
+
15
+ // MARK: - Type Size Calculation
16
+
17
+ inline size_t GetSizeForTypeEncoding(char typeCode) {
18
+ switch (typeCode) {
19
+ case 'c': return sizeof(char);
20
+ case 'i': return sizeof(int);
21
+ case 's': return sizeof(short);
22
+ case 'l': return sizeof(long);
23
+ case 'q': return sizeof(long long);
24
+ case 'C': return sizeof(unsigned char);
25
+ case 'I': return sizeof(unsigned int);
26
+ case 'S': return sizeof(unsigned short);
27
+ case 'L': return sizeof(unsigned long);
28
+ case 'Q': return sizeof(unsigned long long);
29
+ case 'f': return sizeof(float);
30
+ case 'd': return sizeof(double);
31
+ case 'B': return sizeof(bool);
32
+ case '@':
33
+ case '#':
34
+ case ':':
35
+ case '*':
36
+ case '^':
37
+ return sizeof(void*);
38
+ case 'v':
39
+ return 0;
40
+ default:
41
+ NOBJC_ERROR("GetSizeForTypeEncoding: Unknown type code '%c'", typeCode);
42
+ return 0;
43
+ }
44
+ }
45
+
46
+ // MARK: - FFI Type Mapping - Simple Types
47
+
48
+ inline ffi_type* GetFFITypeForSimpleEncoding(char typeCode) {
49
+ switch (typeCode) {
50
+ case 'c': return &ffi_type_sint8;
51
+ case 'i': return &ffi_type_sint32;
52
+ case 's': return &ffi_type_sint16;
53
+ case 'l': return &ffi_type_slong;
54
+ case 'q': return &ffi_type_sint64;
55
+ case 'C': return &ffi_type_uint8;
56
+ case 'I': return &ffi_type_uint32;
57
+ case 'S': return &ffi_type_uint16;
58
+ case 'L': return &ffi_type_ulong;
59
+ case 'Q': return &ffi_type_uint64;
60
+ case 'f': return &ffi_type_float;
61
+ case 'd': return &ffi_type_double;
62
+ case 'B': return &ffi_type_uint8; // BOOL
63
+ case '@':
64
+ case '#':
65
+ case ':':
66
+ case '*':
67
+ case '^':
68
+ return &ffi_type_pointer;
69
+ case 'v': return &ffi_type_void;
70
+ default:
71
+ NOBJC_ERROR("GetFFITypeForSimpleEncoding: Unknown type code '%c'", typeCode);
72
+ return &ffi_type_void;
73
+ }
74
+ }
75
+
76
+ // Forward declaration
77
+ inline ffi_type* GetFFITypeForEncoding(const char* encoding, size_t* outSize,
78
+ std::vector<ffi_type*>& allocatedTypes);
79
+
80
+ // MARK: - Struct Type Parsing
81
+
82
+ inline ffi_type* ParseStructEncoding(const char* encoding, size_t* outSize,
83
+ std::vector<ffi_type*>& allocatedTypes) {
84
+ NOBJC_LOG("ParseStructEncoding: parsing struct '%s'", encoding);
85
+
86
+ if (encoding[0] != '{') {
87
+ NOBJC_ERROR("ParseStructEncoding: Expected '{' at start");
88
+ return nullptr;
89
+ }
90
+
91
+ // Skip struct name (format: {StructName=...})
92
+ const char* ptr = encoding + 1;
93
+ while (*ptr && *ptr != '=' && *ptr != '}') {
94
+ ptr++;
95
+ }
96
+
97
+ if (*ptr == '}') {
98
+ // Empty struct
99
+ NOBJC_LOG("ParseStructEncoding: empty struct");
100
+ return &ffi_type_void;
101
+ }
102
+
103
+ if (*ptr != '=') {
104
+ NOBJC_ERROR("ParseStructEncoding: Expected '=' after struct name");
105
+ return nullptr;
106
+ }
107
+
108
+ ptr++; // Skip '='
109
+
110
+ // Parse field types
111
+ std::vector<ffi_type*> fieldTypes;
112
+
113
+ while (*ptr && *ptr != '}') {
114
+ // Skip any qualifiers
115
+ while (*ptr && (*ptr == 'r' || *ptr == 'n' || *ptr == 'N' ||
116
+ *ptr == 'o' || *ptr == 'O' || *ptr == 'R' || *ptr == 'V')) {
117
+ ptr++;
118
+ }
119
+
120
+ if (*ptr == '}') break;
121
+
122
+ // Determine field encoding length
123
+ const char* fieldStart = ptr;
124
+ size_t fieldLen = 1;
125
+
126
+ if (*ptr == '{') {
127
+ // Nested struct - find matching '}'
128
+ int depth = 1;
129
+ ptr++;
130
+ while (*ptr && depth > 0) {
131
+ if (*ptr == '{') depth++;
132
+ else if (*ptr == '}') depth--;
133
+ ptr++;
134
+ }
135
+ fieldLen = ptr - fieldStart;
136
+ } else if (*ptr == '(') {
137
+ // Union - find matching ')'
138
+ int depth = 1;
139
+ ptr++;
140
+ while (*ptr && depth > 0) {
141
+ if (*ptr == '(') depth++;
142
+ else if (*ptr == ')') depth--;
143
+ ptr++;
144
+ }
145
+ fieldLen = ptr - fieldStart;
146
+ } else {
147
+ // Simple type - just one character (potentially with digits after)
148
+ ptr++;
149
+ // Skip any size/alignment digits
150
+ while (*ptr && isdigit(*ptr)) {
151
+ ptr++;
152
+ }
153
+ fieldLen = ptr - fieldStart;
154
+ }
155
+
156
+ // Create temporary null-terminated string for field encoding
157
+ std::string fieldEncoding(fieldStart, fieldLen);
158
+ NOBJC_LOG("ParseStructEncoding: parsing field '%s'", fieldEncoding.c_str());
159
+
160
+ // Recursively get FFI type for this field
161
+ ffi_type* fieldType = GetFFITypeForEncoding(fieldEncoding.c_str(), nullptr, allocatedTypes);
162
+ if (!fieldType) {
163
+ NOBJC_ERROR("ParseStructEncoding: Failed to parse field type");
164
+ return nullptr;
165
+ }
166
+
167
+ fieldTypes.push_back(fieldType);
168
+ }
169
+
170
+ if (fieldTypes.empty()) {
171
+ NOBJC_LOG("ParseStructEncoding: no fields found");
172
+ return &ffi_type_void;
173
+ }
174
+
175
+ // Allocate ffi_type struct on heap
176
+ ffi_type* structType = new ffi_type;
177
+ structType->type = FFI_TYPE_STRUCT;
178
+ structType->size = 0;
179
+ structType->alignment = 0;
180
+
181
+ // Allocate elements array (null-terminated)
182
+ structType->elements = new ffi_type*[fieldTypes.size() + 1];
183
+ for (size_t i = 0; i < fieldTypes.size(); i++) {
184
+ structType->elements[i] = fieldTypes[i];
185
+ }
186
+ structType->elements[fieldTypes.size()] = nullptr;
187
+
188
+ // Calculate size using NSGetSizeAndAlignment
189
+ NSUInteger size, alignment;
190
+ NSGetSizeAndAlignment(encoding, &size, &alignment);
191
+
192
+ if (outSize) {
193
+ *outSize = size;
194
+ }
195
+
196
+ NOBJC_LOG("ParseStructEncoding: struct has %zu fields, size=%zu",
197
+ fieldTypes.size(), (size_t)size);
198
+
199
+ // Add to cleanup list
200
+ allocatedTypes.push_back(structType);
201
+
202
+ return structType;
203
+ }
204
+
205
+ // MARK: - Main FFI Type Function
206
+
207
+ inline ffi_type* GetFFITypeForEncoding(const char* encoding, size_t* outSize,
208
+ std::vector<ffi_type*>& allocatedTypes) {
209
+ if (!encoding || !*encoding) {
210
+ NOBJC_ERROR("GetFFITypeForEncoding: null or empty encoding");
211
+ return &ffi_type_void;
212
+ }
213
+
214
+ // Simplify encoding (remove qualifiers)
215
+ SimplifiedTypeEncoding simplified(encoding);
216
+ const char* simpleEncoding = simplified.c_str();
217
+
218
+ char firstChar = simpleEncoding[0];
219
+
220
+ // Handle structs and unions
221
+ if (firstChar == '{') {
222
+ return ParseStructEncoding(simpleEncoding, outSize, allocatedTypes);
223
+ } else if (firstChar == '(') {
224
+ // Unions are treated like structs for FFI purposes
225
+ return ParseStructEncoding(simpleEncoding, outSize, allocatedTypes);
226
+ }
227
+
228
+ // Simple type
229
+ ffi_type* type = GetFFITypeForSimpleEncoding(firstChar);
230
+
231
+ if (outSize) {
232
+ *outSize = GetSizeForTypeEncoding(firstChar);
233
+ }
234
+
235
+ return type;
236
+ }
237
+
238
+ // MARK: - Argument Extraction
239
+
240
+ inline void ExtractJSArgumentToBuffer(Napi::Env env, const Napi::Value& jsValue,
241
+ const char* typeEncoding, void* buffer,
242
+ const ObjcArgumentContext& context) {
243
+ NOBJC_LOG("ExtractJSArgumentToBuffer: typeEncoding=%s, buffer=%p", typeEncoding, buffer);
244
+
245
+ // Use existing AsObjCArgument to convert JS to ObjcType variant
246
+ auto optType = AsObjCArgument(jsValue, typeEncoding, context);
247
+
248
+ if (!optType.has_value()) {
249
+ NOBJC_ERROR("ExtractJSArgumentToBuffer: AsObjCArgument returned nullopt");
250
+ throw Napi::Error::New(env, "Failed to convert JS argument to ObjC type");
251
+ }
252
+
253
+ ObjcType objcType = optType.value();
254
+ NOBJC_LOG("ExtractJSArgumentToBuffer: Got ObjcType variant, index=%zu", objcType.index());
255
+
256
+ // Visit the outer variant
257
+ std::visit([&](auto&& arg) {
258
+ using T = std::decay_t<decltype(arg)>;
259
+
260
+ if constexpr (std::is_same_v<T, BaseObjcType>) {
261
+ NOBJC_LOG("ExtractJSArgumentToBuffer: Outer variant is BaseObjcType, visiting inner...");
262
+ // It's a BaseObjcType directly - need to visit it to get the actual type
263
+ std::visit([&](auto&& innerArg) {
264
+ using InnerT = std::decay_t<decltype(innerArg)>;
265
+
266
+ if constexpr (std::is_same_v<InnerT, std::monostate>) {
267
+ NOBJC_LOG("ExtractJSArgumentToBuffer: Inner type is monostate (void)");
268
+ // void - do nothing
269
+ } else if constexpr (std::is_same_v<InnerT, std::string>) {
270
+ NOBJC_LOG("ExtractJSArgumentToBuffer: Inner type is std::string");
271
+ // String - store pointer
272
+ const char* cstr = innerArg.c_str();
273
+ *((const char**)buffer) = cstr;
274
+ NOBJC_LOG("ExtractJSArgumentToBuffer: Stored string pointer: %p", cstr);
275
+ } else if constexpr (std::is_same_v<InnerT, id>) {
276
+ NOBJC_LOG("ExtractJSArgumentToBuffer: Inner type is id (object)");
277
+ // Object - store pointer directly
278
+ *((id*)buffer) = innerArg;
279
+ NOBJC_LOG("ExtractJSArgumentToBuffer: Stored object pointer: %p", innerArg);
280
+ } else if constexpr (std::is_same_v<InnerT, Class>) {
281
+ NOBJC_LOG("ExtractJSArgumentToBuffer: Inner type is Class");
282
+ // Class - store pointer directly
283
+ *((Class*)buffer) = innerArg;
284
+ NOBJC_LOG("ExtractJSArgumentToBuffer: Stored Class pointer: %p", innerArg);
285
+ } else if constexpr (std::is_same_v<InnerT, SEL>) {
286
+ NOBJC_LOG("ExtractJSArgumentToBuffer: Inner type is SEL");
287
+ // SEL - store pointer directly
288
+ *((SEL*)buffer) = innerArg;
289
+ NOBJC_LOG("ExtractJSArgumentToBuffer: Stored SEL: %s", sel_getName(innerArg));
290
+ } else if constexpr (std::is_same_v<InnerT, void*>) {
291
+ NOBJC_LOG("ExtractJSArgumentToBuffer: Inner type is void*");
292
+ // Generic pointer - store directly
293
+ *((void**)buffer) = innerArg;
294
+ NOBJC_LOG("ExtractJSArgumentToBuffer: Stored void pointer: %p", innerArg);
295
+ } else {
296
+ NOBJC_LOG("ExtractJSArgumentToBuffer: Inner type is primitive, size=%zu", sizeof(InnerT));
297
+ // Primitive type - copy value
298
+ *((InnerT*)buffer) = innerArg;
299
+ }
300
+ }, arg);
301
+ } else if constexpr (std::is_same_v<T, BaseObjcType*>) {
302
+ NOBJC_LOG("ExtractJSArgumentToBuffer: Type is BaseObjcType*");
303
+ // Pointer to base type - dereference and copy
304
+ std::visit([&](auto&& innerArg) {
305
+ using InnerT = std::decay_t<decltype(innerArg)>;
306
+ if constexpr (!std::is_same_v<InnerT, std::monostate>) {
307
+ NOBJC_LOG("ExtractJSArgumentToBuffer: Copying inner type, size=%zu", sizeof(InnerT));
308
+ *((InnerT*)buffer) = innerArg;
309
+ }
310
+ }, *arg);
311
+ } else {
312
+ NOBJC_ERROR("ExtractJSArgumentToBuffer: Unexpected outer variant type!");
313
+ }
314
+ }, objcType);
315
+
316
+ NOBJC_LOG("ExtractJSArgumentToBuffer: Completed successfully");
317
+ }
318
+
319
+ // MARK: - Return Value Conversion
320
+
321
+ inline Napi::Value ConvertFFIReturnToJS(Napi::Env env, void* returnBuffer,
322
+ const char* typeEncoding) {
323
+ SimplifiedTypeEncoding simplified(typeEncoding);
324
+ char typeCode = simplified[0];
325
+
326
+ // Use existing ObjCToJS for most types
327
+ return ObjCToJS(env, returnBuffer, typeCode);
328
+ }
329
+
330
+ // MARK: - Cleanup Helper
331
+
332
+ inline void CleanupAllocatedFFITypes(std::vector<ffi_type*>& allocatedTypes) {
333
+ for (ffi_type* type : allocatedTypes) {
334
+ if (type && type->type == FFI_TYPE_STRUCT && type->elements) {
335
+ delete[] type->elements;
336
+ }
337
+ delete type;
338
+ }
339
+ allocatedTypes.clear();
340
+ }
341
+
342
+ #endif // FFI_UTILS_H
@@ -20,6 +20,11 @@ typedef struct NSInvocation NSInvocation;
20
20
  void CallJSCallback(Napi::Env env, Napi::Function jsCallback,
21
21
  InvocationData *data);
22
22
 
23
+ // Helper function to fallback to ThreadSafeFunction when direct call fails
24
+ // Returns true if fallback succeeded, false otherwise
25
+ bool FallbackToTSFN(Napi::ThreadSafeFunction &tsfn, InvocationData *data,
26
+ const std::string &selectorName);
27
+
23
28
  // MARK: - ObjC Runtime Method Implementations
24
29
 
25
30
  // Override respondsToSelector to return YES for methods we implement