objc-js 0.0.11 → 0.0.13
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/binding.gyp +2 -1
- package/build/Release/nobjc_native.node +0 -0
- package/package.json +1 -1
- package/src/native/bridge.h +4 -108
- package/src/native/method-forwarding.h +38 -0
- package/src/native/method-forwarding.mm +416 -0
- package/src/native/protocol-impl.h +6 -53
- package/src/native/protocol-impl.mm +38 -606
- package/src/native/protocol-storage.h +82 -0
- package/src/native/type-conversion.h +612 -0
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
#include "protocol-impl.h"
|
|
2
|
+
#include "method-forwarding.h"
|
|
2
3
|
#include "ObjcObject.h"
|
|
3
|
-
#include "
|
|
4
|
+
#include "protocol-storage.h"
|
|
5
|
+
#include "type-conversion.h"
|
|
4
6
|
#include <Foundation/Foundation.h>
|
|
5
7
|
#include <atomic>
|
|
6
8
|
#include <chrono>
|
|
7
|
-
#include <mutex>
|
|
8
9
|
#include <napi.h>
|
|
9
|
-
#include <objc/message.h>
|
|
10
10
|
#include <objc/runtime.h>
|
|
11
|
-
#include <pthread.h>
|
|
12
11
|
#include <sstream>
|
|
13
12
|
|
|
14
|
-
//
|
|
13
|
+
// MARK: - Global Storage Definition
|
|
14
|
+
|
|
15
15
|
std::unordered_map<void *, ProtocolImplementation> g_implementations;
|
|
16
16
|
std::mutex g_implementations_mutex;
|
|
17
17
|
|
|
@@ -75,594 +75,6 @@ std::vector<std::string> ParseMethodSignature(const char *typeEncoding) {
|
|
|
75
75
|
return types;
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
// Convert Objective-C value to JavaScript value
|
|
79
|
-
Napi::Value ConvertObjCValueToJS(Napi::Env env, void *valuePtr,
|
|
80
|
-
const char *typeEncoding) {
|
|
81
|
-
SimplifiedTypeEncoding simplifiedType(typeEncoding);
|
|
82
|
-
|
|
83
|
-
switch (simplifiedType[0]) {
|
|
84
|
-
case 'c': {
|
|
85
|
-
char value = *(char *)valuePtr;
|
|
86
|
-
return Napi::Number::New(env, value);
|
|
87
|
-
}
|
|
88
|
-
case 'i': {
|
|
89
|
-
int value = *(int *)valuePtr;
|
|
90
|
-
return Napi::Number::New(env, value);
|
|
91
|
-
}
|
|
92
|
-
case 's': {
|
|
93
|
-
short value = *(short *)valuePtr;
|
|
94
|
-
return Napi::Number::New(env, value);
|
|
95
|
-
}
|
|
96
|
-
case 'l': {
|
|
97
|
-
long value = *(long *)valuePtr;
|
|
98
|
-
return Napi::Number::New(env, value);
|
|
99
|
-
}
|
|
100
|
-
case 'q': {
|
|
101
|
-
long long value = *(long long *)valuePtr;
|
|
102
|
-
return Napi::Number::New(env, value);
|
|
103
|
-
}
|
|
104
|
-
case 'C': {
|
|
105
|
-
unsigned char value = *(unsigned char *)valuePtr;
|
|
106
|
-
return Napi::Number::New(env, value);
|
|
107
|
-
}
|
|
108
|
-
case 'I': {
|
|
109
|
-
unsigned int value = *(unsigned int *)valuePtr;
|
|
110
|
-
return Napi::Number::New(env, value);
|
|
111
|
-
}
|
|
112
|
-
case 'S': {
|
|
113
|
-
unsigned short value = *(unsigned short *)valuePtr;
|
|
114
|
-
return Napi::Number::New(env, value);
|
|
115
|
-
}
|
|
116
|
-
case 'L': {
|
|
117
|
-
unsigned long value = *(unsigned long *)valuePtr;
|
|
118
|
-
return Napi::Number::New(env, value);
|
|
119
|
-
}
|
|
120
|
-
case 'Q': {
|
|
121
|
-
unsigned long long value = *(unsigned long long *)valuePtr;
|
|
122
|
-
return Napi::Number::New(env, value);
|
|
123
|
-
}
|
|
124
|
-
case 'f': {
|
|
125
|
-
float value = *(float *)valuePtr;
|
|
126
|
-
return Napi::Number::New(env, value);
|
|
127
|
-
}
|
|
128
|
-
case 'd': {
|
|
129
|
-
double value = *(double *)valuePtr;
|
|
130
|
-
return Napi::Number::New(env, value);
|
|
131
|
-
}
|
|
132
|
-
case 'B': {
|
|
133
|
-
bool value = *(bool *)valuePtr;
|
|
134
|
-
return Napi::Boolean::New(env, value);
|
|
135
|
-
}
|
|
136
|
-
case '*': {
|
|
137
|
-
char *value = *(char **)valuePtr;
|
|
138
|
-
if (value == nullptr) {
|
|
139
|
-
return env.Null();
|
|
140
|
-
}
|
|
141
|
-
return Napi::String::New(env, value);
|
|
142
|
-
}
|
|
143
|
-
case '@': {
|
|
144
|
-
id value = *(__strong id *)valuePtr;
|
|
145
|
-
if (value == nil) {
|
|
146
|
-
return env.Null();
|
|
147
|
-
}
|
|
148
|
-
return ObjcObject::NewInstance(env, value);
|
|
149
|
-
}
|
|
150
|
-
case '#': {
|
|
151
|
-
Class value = *(Class *)valuePtr;
|
|
152
|
-
if (value == nil) {
|
|
153
|
-
return env.Null();
|
|
154
|
-
}
|
|
155
|
-
return ObjcObject::NewInstance(env, value);
|
|
156
|
-
}
|
|
157
|
-
case ':': {
|
|
158
|
-
SEL value = *(SEL *)valuePtr;
|
|
159
|
-
if (value == nullptr) {
|
|
160
|
-
return env.Null();
|
|
161
|
-
}
|
|
162
|
-
NSString *selectorString = NSStringFromSelector(value);
|
|
163
|
-
if (selectorString == nil) {
|
|
164
|
-
return env.Null();
|
|
165
|
-
}
|
|
166
|
-
return Napi::String::New(env, [selectorString UTF8String]);
|
|
167
|
-
}
|
|
168
|
-
case 'v':
|
|
169
|
-
return env.Undefined();
|
|
170
|
-
default:
|
|
171
|
-
return env.Undefined();
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// MARK: - ThreadSafeFunction Callback Handler
|
|
176
|
-
|
|
177
|
-
// This function runs on the JavaScript thread
|
|
178
|
-
void CallJSCallback(Napi::Env env, Napi::Function jsCallback, InvocationData* data) {
|
|
179
|
-
if (!data) {
|
|
180
|
-
NSLog(@"Error: InvocationData is null in CallJSCallback");
|
|
181
|
-
return;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
NSInvocation *invocation = data->invocation;
|
|
185
|
-
if (!invocation) {
|
|
186
|
-
NSLog(@"Error: NSInvocation is null in CallJSCallback");
|
|
187
|
-
return;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// Extract arguments using NSInvocation
|
|
191
|
-
NSMethodSignature *sig = [invocation methodSignature];
|
|
192
|
-
if (!sig) {
|
|
193
|
-
NSLog(@"Error: Failed to get method signature for selector %s",
|
|
194
|
-
data->selectorName.c_str());
|
|
195
|
-
return;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
std::vector<napi_value> jsArgs;
|
|
199
|
-
|
|
200
|
-
// Skip first two arguments (self and _cmd)
|
|
201
|
-
for (NSUInteger i = 2; i < [sig numberOfArguments]; i++) {
|
|
202
|
-
const char *type = [sig getArgumentTypeAtIndex:i];
|
|
203
|
-
SimplifiedTypeEncoding argType(type);
|
|
204
|
-
|
|
205
|
-
switch (argType[0]) {
|
|
206
|
-
case 'c': {
|
|
207
|
-
char value;
|
|
208
|
-
[invocation getArgument:&value atIndex:i];
|
|
209
|
-
jsArgs.push_back(Napi::Number::New(env, value));
|
|
210
|
-
break;
|
|
211
|
-
}
|
|
212
|
-
case 'i': {
|
|
213
|
-
int value;
|
|
214
|
-
[invocation getArgument:&value atIndex:i];
|
|
215
|
-
jsArgs.push_back(Napi::Number::New(env, value));
|
|
216
|
-
break;
|
|
217
|
-
}
|
|
218
|
-
case 's': {
|
|
219
|
-
short value;
|
|
220
|
-
[invocation getArgument:&value atIndex:i];
|
|
221
|
-
jsArgs.push_back(Napi::Number::New(env, value));
|
|
222
|
-
break;
|
|
223
|
-
}
|
|
224
|
-
case 'l': {
|
|
225
|
-
long value;
|
|
226
|
-
[invocation getArgument:&value atIndex:i];
|
|
227
|
-
jsArgs.push_back(Napi::Number::New(env, value));
|
|
228
|
-
break;
|
|
229
|
-
}
|
|
230
|
-
case 'q': {
|
|
231
|
-
long long value;
|
|
232
|
-
[invocation getArgument:&value atIndex:i];
|
|
233
|
-
jsArgs.push_back(Napi::Number::New(env, value));
|
|
234
|
-
break;
|
|
235
|
-
}
|
|
236
|
-
case 'C': {
|
|
237
|
-
unsigned char value;
|
|
238
|
-
[invocation getArgument:&value atIndex:i];
|
|
239
|
-
jsArgs.push_back(Napi::Number::New(env, value));
|
|
240
|
-
break;
|
|
241
|
-
}
|
|
242
|
-
case 'I': {
|
|
243
|
-
unsigned int value;
|
|
244
|
-
[invocation getArgument:&value atIndex:i];
|
|
245
|
-
jsArgs.push_back(Napi::Number::New(env, value));
|
|
246
|
-
break;
|
|
247
|
-
}
|
|
248
|
-
case 'S': {
|
|
249
|
-
unsigned short value;
|
|
250
|
-
[invocation getArgument:&value atIndex:i];
|
|
251
|
-
jsArgs.push_back(Napi::Number::New(env, value));
|
|
252
|
-
break;
|
|
253
|
-
}
|
|
254
|
-
case 'L': {
|
|
255
|
-
unsigned long value;
|
|
256
|
-
[invocation getArgument:&value atIndex:i];
|
|
257
|
-
jsArgs.push_back(Napi::Number::New(env, value));
|
|
258
|
-
break;
|
|
259
|
-
}
|
|
260
|
-
case 'Q': {
|
|
261
|
-
unsigned long long value;
|
|
262
|
-
[invocation getArgument:&value atIndex:i];
|
|
263
|
-
jsArgs.push_back(Napi::Number::New(env, value));
|
|
264
|
-
break;
|
|
265
|
-
}
|
|
266
|
-
case 'f': {
|
|
267
|
-
float value;
|
|
268
|
-
[invocation getArgument:&value atIndex:i];
|
|
269
|
-
jsArgs.push_back(Napi::Number::New(env, value));
|
|
270
|
-
break;
|
|
271
|
-
}
|
|
272
|
-
case 'd': {
|
|
273
|
-
double value;
|
|
274
|
-
[invocation getArgument:&value atIndex:i];
|
|
275
|
-
jsArgs.push_back(Napi::Number::New(env, value));
|
|
276
|
-
break;
|
|
277
|
-
}
|
|
278
|
-
case 'B': {
|
|
279
|
-
bool value;
|
|
280
|
-
[invocation getArgument:&value atIndex:i];
|
|
281
|
-
jsArgs.push_back(Napi::Boolean::New(env, value));
|
|
282
|
-
break;
|
|
283
|
-
}
|
|
284
|
-
case '*': {
|
|
285
|
-
char *value;
|
|
286
|
-
[invocation getArgument:&value atIndex:i];
|
|
287
|
-
if (value == nullptr) {
|
|
288
|
-
jsArgs.push_back(env.Null());
|
|
289
|
-
} else {
|
|
290
|
-
jsArgs.push_back(Napi::String::New(env, value));
|
|
291
|
-
}
|
|
292
|
-
break;
|
|
293
|
-
}
|
|
294
|
-
case '@': {
|
|
295
|
-
__unsafe_unretained id value;
|
|
296
|
-
[invocation getArgument:&value atIndex:i];
|
|
297
|
-
if (value == nil) {
|
|
298
|
-
jsArgs.push_back(env.Null());
|
|
299
|
-
} else {
|
|
300
|
-
jsArgs.push_back(ObjcObject::NewInstance(env, value));
|
|
301
|
-
}
|
|
302
|
-
break;
|
|
303
|
-
}
|
|
304
|
-
case '#': {
|
|
305
|
-
Class value;
|
|
306
|
-
[invocation getArgument:&value atIndex:i];
|
|
307
|
-
if (value == nil) {
|
|
308
|
-
jsArgs.push_back(env.Null());
|
|
309
|
-
} else {
|
|
310
|
-
jsArgs.push_back(ObjcObject::NewInstance(env, value));
|
|
311
|
-
}
|
|
312
|
-
break;
|
|
313
|
-
}
|
|
314
|
-
case ':': {
|
|
315
|
-
SEL value;
|
|
316
|
-
[invocation getArgument:&value atIndex:i];
|
|
317
|
-
if (value == nullptr) {
|
|
318
|
-
jsArgs.push_back(env.Null());
|
|
319
|
-
} else {
|
|
320
|
-
NSString *selString = NSStringFromSelector(value);
|
|
321
|
-
if (selString == nil) {
|
|
322
|
-
jsArgs.push_back(env.Null());
|
|
323
|
-
} else {
|
|
324
|
-
jsArgs.push_back(Napi::String::New(env, [selString UTF8String]));
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
break;
|
|
328
|
-
}
|
|
329
|
-
case '^': {
|
|
330
|
-
void *value;
|
|
331
|
-
[invocation getArgument:&value atIndex:i];
|
|
332
|
-
if (value == nullptr) {
|
|
333
|
-
jsArgs.push_back(env.Null());
|
|
334
|
-
} else {
|
|
335
|
-
jsArgs.push_back(env.Undefined());
|
|
336
|
-
}
|
|
337
|
-
break;
|
|
338
|
-
}
|
|
339
|
-
default:
|
|
340
|
-
jsArgs.push_back(env.Undefined());
|
|
341
|
-
break;
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
// Call the JavaScript callback
|
|
346
|
-
try {
|
|
347
|
-
Napi::Value result = jsCallback.Call(jsArgs);
|
|
348
|
-
|
|
349
|
-
// Handle return value if the method expects one
|
|
350
|
-
const char *returnType = [sig methodReturnType];
|
|
351
|
-
SimplifiedTypeEncoding retType(returnType);
|
|
352
|
-
|
|
353
|
-
if (retType[0] != 'v') { // Not void
|
|
354
|
-
// Convert JS return value to Objective-C and set it
|
|
355
|
-
if (!result.IsUndefined() && !result.IsNull()) {
|
|
356
|
-
switch (retType[0]) {
|
|
357
|
-
case 'c': {
|
|
358
|
-
char value = result.As<Napi::Number>().Int32Value();
|
|
359
|
-
[invocation setReturnValue:&value];
|
|
360
|
-
break;
|
|
361
|
-
}
|
|
362
|
-
case 'i': {
|
|
363
|
-
int value = result.As<Napi::Number>().Int32Value();
|
|
364
|
-
[invocation setReturnValue:&value];
|
|
365
|
-
break;
|
|
366
|
-
}
|
|
367
|
-
case 's': {
|
|
368
|
-
short value = result.As<Napi::Number>().Int32Value();
|
|
369
|
-
[invocation setReturnValue:&value];
|
|
370
|
-
break;
|
|
371
|
-
}
|
|
372
|
-
case 'l': {
|
|
373
|
-
long value = result.As<Napi::Number>().Int64Value();
|
|
374
|
-
[invocation setReturnValue:&value];
|
|
375
|
-
break;
|
|
376
|
-
}
|
|
377
|
-
case 'q': {
|
|
378
|
-
long long value = result.As<Napi::Number>().Int64Value();
|
|
379
|
-
[invocation setReturnValue:&value];
|
|
380
|
-
break;
|
|
381
|
-
}
|
|
382
|
-
case 'C': {
|
|
383
|
-
unsigned char value = result.As<Napi::Number>().Uint32Value();
|
|
384
|
-
[invocation setReturnValue:&value];
|
|
385
|
-
break;
|
|
386
|
-
}
|
|
387
|
-
case 'I': {
|
|
388
|
-
unsigned int value = result.As<Napi::Number>().Uint32Value();
|
|
389
|
-
[invocation setReturnValue:&value];
|
|
390
|
-
break;
|
|
391
|
-
}
|
|
392
|
-
case 'S': {
|
|
393
|
-
unsigned short value = result.As<Napi::Number>().Uint32Value();
|
|
394
|
-
[invocation setReturnValue:&value];
|
|
395
|
-
break;
|
|
396
|
-
}
|
|
397
|
-
case 'L': {
|
|
398
|
-
unsigned long value = result.As<Napi::Number>().Int64Value();
|
|
399
|
-
[invocation setReturnValue:&value];
|
|
400
|
-
break;
|
|
401
|
-
}
|
|
402
|
-
case 'Q': {
|
|
403
|
-
unsigned long long value = result.As<Napi::Number>().Int64Value();
|
|
404
|
-
[invocation setReturnValue:&value];
|
|
405
|
-
break;
|
|
406
|
-
}
|
|
407
|
-
case 'f': {
|
|
408
|
-
float value = result.As<Napi::Number>().FloatValue();
|
|
409
|
-
[invocation setReturnValue:&value];
|
|
410
|
-
break;
|
|
411
|
-
}
|
|
412
|
-
case 'd': {
|
|
413
|
-
double value = result.As<Napi::Number>().DoubleValue();
|
|
414
|
-
[invocation setReturnValue:&value];
|
|
415
|
-
break;
|
|
416
|
-
}
|
|
417
|
-
case 'B': {
|
|
418
|
-
bool value = result.As<Napi::Boolean>().Value();
|
|
419
|
-
[invocation setReturnValue:&value];
|
|
420
|
-
break;
|
|
421
|
-
}
|
|
422
|
-
case '@': {
|
|
423
|
-
// Return an Objective-C object
|
|
424
|
-
if (result.IsObject()) {
|
|
425
|
-
Napi::Object resultObj = result.As<Napi::Object>();
|
|
426
|
-
// Check if it's an ObjcObject instance
|
|
427
|
-
if (resultObj.InstanceOf(ObjcObject::constructor.Value())) {
|
|
428
|
-
ObjcObject *objcObj = Napi::ObjectWrap<ObjcObject>::Unwrap(resultObj);
|
|
429
|
-
id objcValue = objcObj->objcObject;
|
|
430
|
-
[invocation setReturnValue:&objcValue];
|
|
431
|
-
} else {
|
|
432
|
-
NSLog(@"Warning: result object is not an ObjcObject instance");
|
|
433
|
-
}
|
|
434
|
-
} else if (result.IsNull()) {
|
|
435
|
-
id objcValue = nil;
|
|
436
|
-
[invocation setReturnValue:&objcValue];
|
|
437
|
-
} else {
|
|
438
|
-
NSLog(@"Warning: result is not an object (type: %d)", result.Type());
|
|
439
|
-
}
|
|
440
|
-
break;
|
|
441
|
-
}
|
|
442
|
-
default:
|
|
443
|
-
NSLog(@"Warning: Unsupported return type '%c' for selector %s",
|
|
444
|
-
retType[0], data->selectorName.c_str());
|
|
445
|
-
break;
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
} catch (const Napi::Error &e) {
|
|
450
|
-
NSLog(@"Error calling JavaScript callback for %s: %s",
|
|
451
|
-
data->selectorName.c_str(), e.what());
|
|
452
|
-
} catch (const std::exception &e) {
|
|
453
|
-
NSLog(@"Exception calling JavaScript callback for %s: %s",
|
|
454
|
-
data->selectorName.c_str(), e.what());
|
|
455
|
-
} catch (...) {
|
|
456
|
-
NSLog(@"Unknown error calling JavaScript callback for %s",
|
|
457
|
-
data->selectorName.c_str());
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
// Clean up the invocation data
|
|
461
|
-
delete data;
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
// MARK: - Message Forwarding Implementation
|
|
465
|
-
|
|
466
|
-
// Override respondsToSelector to return YES for methods we implement
|
|
467
|
-
BOOL RespondsToSelector(id self, SEL _cmd, SEL selector) {
|
|
468
|
-
void *ptr = (__bridge void *)self;
|
|
469
|
-
|
|
470
|
-
// Check if this is one of our implemented methods
|
|
471
|
-
{
|
|
472
|
-
std::lock_guard<std::mutex> lock(g_implementations_mutex);
|
|
473
|
-
auto it = g_implementations.find(ptr);
|
|
474
|
-
if (it != g_implementations.end()) {
|
|
475
|
-
NSString *selectorString = NSStringFromSelector(selector);
|
|
476
|
-
if (selectorString != nil) {
|
|
477
|
-
std::string selName = [selectorString UTF8String];
|
|
478
|
-
auto callbackIt = it->second.callbacks.find(selName);
|
|
479
|
-
if (callbackIt != it->second.callbacks.end()) {
|
|
480
|
-
return YES;
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
// For methods we don't implement, check if NSObject responds to them
|
|
487
|
-
// This handles standard NSObject methods like description, isEqual:, etc.
|
|
488
|
-
return [NSObject instancesRespondToSelector:selector];
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
// Provide method signature for message forwarding
|
|
492
|
-
NSMethodSignature* MethodSignatureForSelector(id self, SEL _cmd, SEL selector) {
|
|
493
|
-
void *ptr = (__bridge void *)self;
|
|
494
|
-
|
|
495
|
-
std::lock_guard<std::mutex> lock(g_implementations_mutex);
|
|
496
|
-
auto it = g_implementations.find(ptr);
|
|
497
|
-
if (it != g_implementations.end()) {
|
|
498
|
-
NSString *selectorString = NSStringFromSelector(selector);
|
|
499
|
-
std::string selName = [selectorString UTF8String];
|
|
500
|
-
auto encIt = it->second.typeEncodings.find(selName);
|
|
501
|
-
if (encIt != it->second.typeEncodings.end()) {
|
|
502
|
-
return [NSMethodSignature signatureWithObjCTypes:encIt->second.c_str()];
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
// Fall back to superclass for methods we don't implement
|
|
506
|
-
return [NSObject instanceMethodSignatureForSelector:selector];
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
// Handle forwarded invocations
|
|
510
|
-
void ForwardInvocation(id self, SEL _cmd, NSInvocation *invocation) {
|
|
511
|
-
if (!invocation) {
|
|
512
|
-
NSLog(@"Error: ForwardInvocation called with nil invocation");
|
|
513
|
-
return;
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
// Retain the invocation to keep it alive during async call
|
|
517
|
-
[invocation retainArguments];
|
|
518
|
-
|
|
519
|
-
SEL selector = [invocation selector];
|
|
520
|
-
NSString *selectorString = NSStringFromSelector(selector);
|
|
521
|
-
if (!selectorString) {
|
|
522
|
-
NSLog(@"Error: Failed to convert selector to string");
|
|
523
|
-
return;
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
std::string selectorName = [selectorString UTF8String];
|
|
527
|
-
|
|
528
|
-
// Store self pointer for later lookups
|
|
529
|
-
void *ptr = (__bridge void *)self;
|
|
530
|
-
|
|
531
|
-
// First pass: get ONLY thread-safe data (pthread_t, TSFN, typeEncoding)
|
|
532
|
-
// DO NOT access any N-API values here - we may not be on the JS thread!
|
|
533
|
-
pthread_t js_thread;
|
|
534
|
-
Napi::ThreadSafeFunction tsfn;
|
|
535
|
-
std::string typeEncoding;
|
|
536
|
-
{
|
|
537
|
-
std::lock_guard<std::mutex> lock(g_implementations_mutex);
|
|
538
|
-
auto it = g_implementations.find(ptr);
|
|
539
|
-
if (it == g_implementations.end()) {
|
|
540
|
-
NSLog(@"Warning: Protocol implementation not found for instance %p", self);
|
|
541
|
-
return;
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
auto callbackIt = it->second.callbacks.find(selectorName);
|
|
545
|
-
if (callbackIt == it->second.callbacks.end()) {
|
|
546
|
-
NSLog(@"Warning: Callback not found for selector %s", selectorName.c_str());
|
|
547
|
-
return;
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
// Get thread ID first - this is a plain C type, safe from any thread
|
|
551
|
-
js_thread = it->second.js_thread;
|
|
552
|
-
|
|
553
|
-
// Get the ThreadSafeFunction - this is thread-safe by design
|
|
554
|
-
// IMPORTANT: We must Acquire() to increment the ref count, because copying
|
|
555
|
-
// a ThreadSafeFunction does NOT increment it. If DeallocImplementation runs
|
|
556
|
-
// and calls Release() on the original, our copy would become invalid.
|
|
557
|
-
tsfn = callbackIt->second;
|
|
558
|
-
napi_status acq_status = tsfn.Acquire();
|
|
559
|
-
if (acq_status != napi_ok) {
|
|
560
|
-
NSLog(@"Warning: Failed to acquire ThreadSafeFunction for selector %s", selectorName.c_str());
|
|
561
|
-
return;
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
// Get the type encoding for return value handling
|
|
565
|
-
auto encIt = it->second.typeEncodings.find(selectorName);
|
|
566
|
-
if (encIt != it->second.typeEncodings.end()) {
|
|
567
|
-
typeEncoding = encIt->second;
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
// Check if we're on the JS thread BEFORE any N-API value access
|
|
572
|
-
bool is_js_thread = pthread_equal(pthread_self(), js_thread);
|
|
573
|
-
|
|
574
|
-
// Create invocation data
|
|
575
|
-
auto data = new InvocationData();
|
|
576
|
-
data->invocation = invocation;
|
|
577
|
-
data->selectorName = selectorName;
|
|
578
|
-
data->typeEncoding = typeEncoding;
|
|
579
|
-
|
|
580
|
-
if (is_js_thread) {
|
|
581
|
-
// We're on the JS thread, so it's safe to access N-API values directly
|
|
582
|
-
// Release the TSFN we acquired - we don't need it on this path
|
|
583
|
-
tsfn.Release();
|
|
584
|
-
|
|
585
|
-
// Do a second lookup to get the JS callback
|
|
586
|
-
// IMPORTANT: We must call .Value() while holding the lock to prevent
|
|
587
|
-
// DeallocImplementation from erasing the entry while we're using it
|
|
588
|
-
napi_env stored_env;
|
|
589
|
-
Napi::Function jsFn;
|
|
590
|
-
{
|
|
591
|
-
std::lock_guard<std::mutex> lock(g_implementations_mutex);
|
|
592
|
-
auto it = g_implementations.find(ptr);
|
|
593
|
-
if (it == g_implementations.end()) {
|
|
594
|
-
NSLog(@"Warning: Protocol implementation not found for instance %p (JS thread path)", self);
|
|
595
|
-
delete data;
|
|
596
|
-
return;
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
auto jsCallbackIt = it->second.jsCallbacks.find(selectorName);
|
|
600
|
-
if (jsCallbackIt == it->second.jsCallbacks.end()) {
|
|
601
|
-
NSLog(@"Warning: JS callback not found for selector %s (JS thread path)", selectorName.c_str());
|
|
602
|
-
delete data;
|
|
603
|
-
return;
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
stored_env = it->second.env;
|
|
607
|
-
// Get the function value WHILE HOLDING THE LOCK
|
|
608
|
-
// This prevents a race with DeallocImplementation clearing the jsCallbacks map
|
|
609
|
-
jsFn = jsCallbackIt->second.Value();
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
// Now jsFn is a local Napi::Function (napi_value), safe to use after lock release
|
|
613
|
-
Napi::Env env(stored_env);
|
|
614
|
-
CallJSCallback(env, jsFn, data);
|
|
615
|
-
// Data is deleted in CallJSCallback
|
|
616
|
-
} else {
|
|
617
|
-
// We're on a different thread - NEVER access FunctionReference here!
|
|
618
|
-
// Use ThreadSafeFunction to marshal to JS thread
|
|
619
|
-
napi_status status = tsfn.BlockingCall(data, CallJSCallback);
|
|
620
|
-
|
|
621
|
-
// Release our acquired reference to the TSFN
|
|
622
|
-
// This balances the Acquire() call above
|
|
623
|
-
tsfn.Release();
|
|
624
|
-
|
|
625
|
-
if (status != napi_ok) {
|
|
626
|
-
NSLog(@"Error: Failed to call ThreadSafeFunction for selector %s (status: %d)",
|
|
627
|
-
selectorName.c_str(), status);
|
|
628
|
-
delete data;
|
|
629
|
-
return;
|
|
630
|
-
}
|
|
631
|
-
// Data will be deleted in the callback
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
// Deallocation implementation
|
|
636
|
-
void DeallocImplementation(id self, SEL _cmd) {
|
|
637
|
-
@autoreleasepool {
|
|
638
|
-
// Remove the implementation from the global map
|
|
639
|
-
std::lock_guard<std::mutex> lock(g_implementations_mutex);
|
|
640
|
-
void *ptr = (__bridge void *)self;
|
|
641
|
-
auto it = g_implementations.find(ptr);
|
|
642
|
-
if (it != g_implementations.end()) {
|
|
643
|
-
// Release all ThreadSafeFunctions and JS callbacks
|
|
644
|
-
// Do this carefully to avoid issues during shutdown
|
|
645
|
-
try {
|
|
646
|
-
for (auto& pair : it->second.callbacks) {
|
|
647
|
-
// Release the ThreadSafeFunction
|
|
648
|
-
pair.second.Release();
|
|
649
|
-
}
|
|
650
|
-
it->second.callbacks.clear();
|
|
651
|
-
it->second.jsCallbacks.clear();
|
|
652
|
-
it->second.typeEncodings.clear();
|
|
653
|
-
} catch (...) {
|
|
654
|
-
// Ignore errors during cleanup
|
|
655
|
-
NSLog(@"Warning: Exception during callback cleanup for instance %p", self);
|
|
656
|
-
}
|
|
657
|
-
g_implementations.erase(it);
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
// Call the superclass dealloc
|
|
662
|
-
// Note: Under ARC, we don't need to manually call [super dealloc]
|
|
663
|
-
// The runtime handles this automatically
|
|
664
|
-
}
|
|
665
|
-
|
|
666
78
|
// MARK: - Main Implementation
|
|
667
79
|
|
|
668
80
|
Napi::Value CreateProtocolImplementation(const Napi::CallbackInfo &info) {
|
|
@@ -718,13 +130,33 @@ Napi::Value CreateProtocolImplementation(const Napi::CallbackInfo &info) {
|
|
|
718
130
|
// Get the method implementations object's property names
|
|
719
131
|
Napi::Array propertyNames = methodImplementations.GetPropertyNames();
|
|
720
132
|
|
|
133
|
+
// Detect if we're running in Electron by checking process.versions.electron
|
|
134
|
+
bool isElectron = false;
|
|
135
|
+
try {
|
|
136
|
+
Napi::Object global = env.Global();
|
|
137
|
+
if (global.Has("process")) {
|
|
138
|
+
Napi::Object process = global.Get("process").As<Napi::Object>();
|
|
139
|
+
if (process.Has("versions")) {
|
|
140
|
+
Napi::Object versions = process.Get("versions").As<Napi::Object>();
|
|
141
|
+
isElectron = versions.Has("electron");
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
} catch (...) {
|
|
145
|
+
// If detection fails, assume not Electron
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (isElectron) {
|
|
149
|
+
NSLog(@"[DEBUG] Detected Electron runtime, will always use TSFN path");
|
|
150
|
+
}
|
|
151
|
+
|
|
721
152
|
// Store callbacks for this instance (we'll set the instance pointer later)
|
|
722
153
|
ProtocolImplementation impl{
|
|
723
154
|
.callbacks = {},
|
|
724
155
|
.typeEncodings = {},
|
|
725
156
|
.className = className,
|
|
726
157
|
.env = env,
|
|
727
|
-
.js_thread = pthread_self() // Store the current (JS) thread ID
|
|
158
|
+
.js_thread = pthread_self(), // Store the current (JS) thread ID
|
|
159
|
+
.isElectron = isElectron
|
|
728
160
|
};
|
|
729
161
|
|
|
730
162
|
// Store default type encodings to keep them alive
|
|
@@ -781,7 +213,7 @@ Napi::Value CreateProtocolImplementation(const Napi::CallbackInfo &info) {
|
|
|
781
213
|
for (size_t j = 0; j < colonCount; j++) {
|
|
782
214
|
defaultEncoding += "@";
|
|
783
215
|
}
|
|
784
|
-
|
|
216
|
+
|
|
785
217
|
// Store the string to keep it alive, then use its c_str()
|
|
786
218
|
defaultTypeEncodings.push_back(std::move(defaultEncoding));
|
|
787
219
|
typeEncoding = defaultTypeEncodings.back().c_str();
|
|
@@ -793,27 +225,28 @@ Napi::Value CreateProtocolImplementation(const Napi::CallbackInfo &info) {
|
|
|
793
225
|
|
|
794
226
|
// Create a ThreadSafeFunction for this callback
|
|
795
227
|
Napi::ThreadSafeFunction tsfn = Napi::ThreadSafeFunction::New(
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
228
|
+
env,
|
|
229
|
+
jsCallback, // The JS function to call
|
|
230
|
+
"ProtocolCallback", // Resource name
|
|
231
|
+
0, // Max queue size (0 = unlimited)
|
|
232
|
+
1, // Initial thread count
|
|
233
|
+
[](Napi::Env) {} // Finalizer (no context to clean up)
|
|
802
234
|
);
|
|
803
|
-
|
|
235
|
+
|
|
804
236
|
// Store both the TSFN and the original JS function
|
|
805
237
|
impl.callbacks[selectorName] = tsfn;
|
|
806
238
|
impl.jsCallbacks[selectorName] = Napi::Persistent(jsCallback);
|
|
807
239
|
impl.typeEncodings[selectorName] = std::string(typeEncoding);
|
|
808
240
|
}
|
|
809
|
-
|
|
241
|
+
|
|
810
242
|
// Override respondsToSelector
|
|
811
|
-
// Use class_addMethod since the class is being created and doesn't have
|
|
243
|
+
// Use class_addMethod since the class is being created and doesn't have
|
|
244
|
+
// methods yet
|
|
812
245
|
if (!class_addMethod(newClass, @selector(respondsToSelector:),
|
|
813
246
|
(IMP)RespondsToSelector, "B@::")) {
|
|
814
247
|
NSLog(@"Warning: Failed to add respondsToSelector: method");
|
|
815
248
|
}
|
|
816
|
-
|
|
249
|
+
|
|
817
250
|
// Add message forwarding methods to the class
|
|
818
251
|
class_addMethod(newClass, @selector(methodSignatureForSelector:),
|
|
819
252
|
(IMP)MethodSignatureForSelector, "@@::");
|
|
@@ -848,4 +281,3 @@ Napi::Value CreateProtocolImplementation(const Napi::CallbackInfo &info) {
|
|
|
848
281
|
// Return wrapped object
|
|
849
282
|
return ObjcObject::NewInstance(env, instance);
|
|
850
283
|
}
|
|
851
|
-
|