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.
- package/README.md +56 -1
- package/binding.gyp +7 -2
- package/build/Release/nobjc_native.node +0 -0
- package/dist/index.d.ts +116 -1
- package/dist/index.js +117 -2
- package/dist/native.d.ts +2 -2
- package/dist/native.js +2 -2
- package/package.json +1 -1
- package/src/native/debug.h +19 -0
- package/src/native/ffi-utils.h +342 -0
- package/src/native/method-forwarding.h +5 -0
- package/src/native/method-forwarding.mm +110 -58
- package/src/native/nobjc.mm +3 -0
- package/src/native/protocol-impl.mm +26 -7
- package/src/native/protocol-storage.h +61 -0
- package/src/native/subclass-impl.h +20 -0
- package/src/native/subclass-impl.mm +1001 -0
- package/src/native/type-conversion.h +6 -8
|
@@ -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
|