objc-js 1.1.0 → 1.2.0
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 +1 -0
- package/dist/index.d.ts +77 -1
- package/dist/index.js +321 -58
- package/dist/native.d.ts +2 -2
- package/dist/native.js +2 -2
- package/package.json +2 -1
- package/prebuilds/darwin-arm64/node.napi.armv8.node +0 -0
- package/prebuilds/darwin-arm64/objc-js.napi.armv8.node +0 -0
- package/prebuilds/darwin-x64/node.napi.node +0 -0
- package/src/native/ObjcObject.h +40 -3
- package/src/native/ObjcObject.mm +630 -18
- package/src/native/bridge.h +6 -2
- package/src/native/call-function.h +274 -0
- package/src/native/forwarding-common.h +42 -5
- package/src/native/forwarding-common.mm +18 -15
- package/src/native/method-forwarding.mm +51 -55
- package/src/native/nobjc.mm +4 -0
- package/src/native/protocol-impl.mm +7 -6
- package/src/native/protocol-manager.h +9 -8
- package/src/native/protocol-storage.h +19 -8
- package/src/native/struct-utils.h +205 -5
- package/src/native/subclass-impl.mm +39 -37
- package/src/native/subclass-manager.h +10 -9
package/src/native/bridge.h
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
#include <format>
|
|
5
5
|
#include <napi.h>
|
|
6
6
|
#include <objc/objc.h>
|
|
7
|
+
#include <string_view>
|
|
7
8
|
|
|
8
9
|
#ifndef NATIVE_BRIDGE_H
|
|
9
10
|
#define NATIVE_BRIDGE_H
|
|
@@ -66,8 +67,8 @@ template <typename T1, typename T2> bool IsInRange(T1 value) {
|
|
|
66
67
|
// MARK: - Conversion Implementation
|
|
67
68
|
|
|
68
69
|
struct ObjcArgumentContext {
|
|
69
|
-
std::
|
|
70
|
-
std::
|
|
70
|
+
std::string_view className;
|
|
71
|
+
std::string_view selectorName;
|
|
71
72
|
int argumentIndex;
|
|
72
73
|
};
|
|
73
74
|
|
|
@@ -277,6 +278,9 @@ inline auto AsObjCArgument(const Napi::Value &value, const char *typeEncoding,
|
|
|
277
278
|
return ConvertToNativeValue<SEL>(value, context);
|
|
278
279
|
case '@':
|
|
279
280
|
return ConvertToNativeValue<id>(value, context);
|
|
281
|
+
case '#':
|
|
282
|
+
// Class is also an id in ObjC — an ObjcObject wrapping a Class works here
|
|
283
|
+
return ConvertToNativeValue<id>(value, context);
|
|
280
284
|
case '^': // Pointer type (^v, ^c, etc.)
|
|
281
285
|
if (value.IsBuffer()) {
|
|
282
286
|
Napi::Buffer<uint8_t> buffer = value.As<Napi::Buffer<uint8_t>>();
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
// ============================================================================
|
|
4
|
+
// call-function.h - C Function Calling via dlsym + libffi
|
|
5
|
+
// ============================================================================
|
|
6
|
+
//
|
|
7
|
+
// Provides the ability to call C functions (like NSLog, CGRectMake, etc.)
|
|
8
|
+
// exported from loaded frameworks. Uses dlsym to look up the function
|
|
9
|
+
// symbol and libffi to perform the call with correct ABI handling.
|
|
10
|
+
//
|
|
11
|
+
// NOTE: No @autoreleasepool is used here. C functions like NSHomeDirectory()
|
|
12
|
+
// return autoreleased objects, and wrapping the call in @autoreleasepool would
|
|
13
|
+
// drain the pool before ObjcObject::NewInstance can retain the returned object,
|
|
14
|
+
// leaving the ObjcObject holding a dangling pointer. The caller's autorelease
|
|
15
|
+
// pool (from Node's/Bun's event loop) handles cleanup instead.
|
|
16
|
+
//
|
|
17
|
+
|
|
18
|
+
#include "constants.h"
|
|
19
|
+
#include "debug.h"
|
|
20
|
+
#include "ffi-utils.h"
|
|
21
|
+
#include "struct-utils.h"
|
|
22
|
+
#include "type-conversion.h"
|
|
23
|
+
#include <Foundation/Foundation.h>
|
|
24
|
+
#include <dlfcn.h>
|
|
25
|
+
#include <ffi.h>
|
|
26
|
+
#include <memory>
|
|
27
|
+
#include <napi.h>
|
|
28
|
+
#include <string>
|
|
29
|
+
#include <vector>
|
|
30
|
+
|
|
31
|
+
// MARK: - Argument Extraction
|
|
32
|
+
|
|
33
|
+
/// Extract a JS argument into a buffer based on the type encoding.
|
|
34
|
+
/// Handles struct types via PackJSValueAsStruct, and simple types via
|
|
35
|
+
/// ExtractJSArgumentToBuffer.
|
|
36
|
+
///
|
|
37
|
+
/// Returns the owned buffer (for structs) so the caller can keep it alive.
|
|
38
|
+
inline std::unique_ptr<uint8_t[]>
|
|
39
|
+
ExtractFunctionArgument(Napi::Env env, const Napi::Value &jsValue,
|
|
40
|
+
const std::string &typeEncoding, void **outArgPtr,
|
|
41
|
+
const std::string &functionName, int argIndex,
|
|
42
|
+
FFITypeGuard &guard) {
|
|
43
|
+
const char *simplified = SimplifyTypeEncoding(typeEncoding.c_str());
|
|
44
|
+
|
|
45
|
+
if (*simplified == '{') {
|
|
46
|
+
// Struct argument: pack JS value into struct buffer
|
|
47
|
+
auto structBytes = PackJSValueAsStruct(env, jsValue, typeEncoding.c_str());
|
|
48
|
+
auto buffer = std::make_unique<uint8_t[]>(structBytes.size());
|
|
49
|
+
memcpy(buffer.get(), structBytes.data(), structBytes.size());
|
|
50
|
+
*outArgPtr = buffer.get();
|
|
51
|
+
NOBJC_LOG("ExtractFunctionArgument: Packed struct arg %d (%zu bytes)",
|
|
52
|
+
argIndex, structBytes.size());
|
|
53
|
+
return buffer;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Simple type: compute size and extract
|
|
57
|
+
size_t argSize = GetSizeForTypeEncoding(*simplified);
|
|
58
|
+
if (argSize == 0 && *simplified != 'v') {
|
|
59
|
+
// Complex type - use NSGetSizeAndAlignment
|
|
60
|
+
NSUInteger nsSize, nsAlignment;
|
|
61
|
+
NSGetSizeAndAlignment(typeEncoding.c_str(), &nsSize, &nsAlignment);
|
|
62
|
+
argSize = nsSize;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (argSize == 0) {
|
|
66
|
+
argSize = nobjc::kDefaultArgBufferSize;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
auto buffer = std::make_unique<uint8_t[]>(argSize);
|
|
70
|
+
memset(buffer.get(), 0, argSize);
|
|
71
|
+
|
|
72
|
+
ObjcArgumentContext context = {
|
|
73
|
+
.className = functionName,
|
|
74
|
+
.selectorName = functionName,
|
|
75
|
+
.argumentIndex = argIndex,
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
ExtractJSArgumentToBuffer(env, jsValue, typeEncoding.c_str(), buffer.get(),
|
|
79
|
+
context);
|
|
80
|
+
*outArgPtr = buffer.get();
|
|
81
|
+
|
|
82
|
+
NOBJC_LOG("ExtractFunctionArgument: Extracted arg %d (type=%s, size=%zu)",
|
|
83
|
+
argIndex, typeEncoding.c_str(), argSize);
|
|
84
|
+
return buffer;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// MARK: - Return Value Conversion
|
|
88
|
+
|
|
89
|
+
/// Convert an FFI return buffer to a JS value, handling structs.
|
|
90
|
+
inline Napi::Value ConvertFunctionReturnToJS(Napi::Env env, void *returnBuffer,
|
|
91
|
+
const std::string &typeEncoding) {
|
|
92
|
+
const char *simplified = SimplifyTypeEncoding(typeEncoding.c_str());
|
|
93
|
+
|
|
94
|
+
if (*simplified == '{') {
|
|
95
|
+
// Struct return: unpack to JS object
|
|
96
|
+
return UnpackStructToJSValue(env, static_cast<const uint8_t *>(returnBuffer),
|
|
97
|
+
typeEncoding.c_str());
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Simple type
|
|
101
|
+
return ConvertFFIReturnToJS(env, returnBuffer, typeEncoding.c_str());
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// MARK: - CallFunction Implementation
|
|
105
|
+
|
|
106
|
+
/// Native implementation of CallFunction.
|
|
107
|
+
///
|
|
108
|
+
/// Arguments:
|
|
109
|
+
/// info[0]: function name (string)
|
|
110
|
+
/// info[1]: return type encoding (string)
|
|
111
|
+
/// info[2]: argument type encodings (array of strings)
|
|
112
|
+
/// info[3]: fixed argument count (number) - if < total args, uses
|
|
113
|
+
/// ffi_prep_cif_var for variadic calling convention
|
|
114
|
+
/// info[4+]: actual arguments
|
|
115
|
+
inline Napi::Value CallFunction(const Napi::CallbackInfo &info) {
|
|
116
|
+
Napi::Env env = info.Env();
|
|
117
|
+
|
|
118
|
+
// Validate minimum arguments
|
|
119
|
+
if (info.Length() < 4) {
|
|
120
|
+
throw Napi::TypeError::New(
|
|
121
|
+
env,
|
|
122
|
+
"CallFunction requires at least 4 arguments: name, returnType, "
|
|
123
|
+
"argTypes, fixedArgCount");
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Parse function name
|
|
127
|
+
if (!info[0].IsString()) {
|
|
128
|
+
throw Napi::TypeError::New(env, "First argument (function name) must be "
|
|
129
|
+
"a string");
|
|
130
|
+
}
|
|
131
|
+
std::string functionName = info[0].As<Napi::String>().Utf8Value();
|
|
132
|
+
|
|
133
|
+
// Parse return type encoding
|
|
134
|
+
if (!info[1].IsString()) {
|
|
135
|
+
throw Napi::TypeError::New(
|
|
136
|
+
env, "Second argument (return type) must be a string");
|
|
137
|
+
}
|
|
138
|
+
std::string returnType = info[1].As<Napi::String>().Utf8Value();
|
|
139
|
+
|
|
140
|
+
// Parse argument type encodings
|
|
141
|
+
if (!info[2].IsArray()) {
|
|
142
|
+
throw Napi::TypeError::New(
|
|
143
|
+
env, "Third argument (arg types) must be an array of strings");
|
|
144
|
+
}
|
|
145
|
+
Napi::Array argTypesArray = info[2].As<Napi::Array>();
|
|
146
|
+
uint32_t argCount = argTypesArray.Length();
|
|
147
|
+
|
|
148
|
+
std::vector<std::string> argTypes;
|
|
149
|
+
argTypes.reserve(argCount);
|
|
150
|
+
for (uint32_t i = 0; i < argCount; i++) {
|
|
151
|
+
Napi::Value v = argTypesArray.Get(i);
|
|
152
|
+
if (!v.IsString()) {
|
|
153
|
+
throw Napi::TypeError::New(
|
|
154
|
+
env,
|
|
155
|
+
"Each element of argTypes must be a string (ObjC type encoding)");
|
|
156
|
+
}
|
|
157
|
+
argTypes.push_back(v.As<Napi::String>().Utf8Value());
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Parse fixed arg count
|
|
161
|
+
if (!info[3].IsNumber()) {
|
|
162
|
+
throw Napi::TypeError::New(
|
|
163
|
+
env, "Fourth argument (fixedArgCount) must be a number");
|
|
164
|
+
}
|
|
165
|
+
int fixedArgCount = info[3].As<Napi::Number>().Int32Value();
|
|
166
|
+
|
|
167
|
+
// Validate argument count
|
|
168
|
+
uint32_t providedArgs = info.Length() - 4;
|
|
169
|
+
if (providedArgs != argCount) {
|
|
170
|
+
throw Napi::Error::New(
|
|
171
|
+
env, "Expected " + std::to_string(argCount) +
|
|
172
|
+
" arguments but got " + std::to_string(providedArgs) +
|
|
173
|
+
" for function '" + functionName + "'");
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
NOBJC_LOG("CallFunction: Looking up '%s' (return=%s, %u args, %d fixed)",
|
|
177
|
+
functionName.c_str(), returnType.c_str(), argCount,
|
|
178
|
+
fixedArgCount);
|
|
179
|
+
|
|
180
|
+
// Look up the function symbol
|
|
181
|
+
void *funcPtr = dlsym(RTLD_DEFAULT, functionName.c_str());
|
|
182
|
+
if (!funcPtr) {
|
|
183
|
+
throw Napi::Error::New(
|
|
184
|
+
env, "Function '" + functionName +
|
|
185
|
+
"' not found. Make sure the framework is loaded first. "
|
|
186
|
+
"dlsym error: " +
|
|
187
|
+
std::string(dlerror() ?: "unknown"));
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
NOBJC_LOG("CallFunction: Found '%s' at %p", functionName.c_str(), funcPtr);
|
|
191
|
+
|
|
192
|
+
// Build FFI type arrays
|
|
193
|
+
FFITypeGuard guard;
|
|
194
|
+
|
|
195
|
+
// Return type
|
|
196
|
+
size_t returnSize = 0;
|
|
197
|
+
ffi_type *returnFFIType =
|
|
198
|
+
GetFFITypeForEncoding(returnType.c_str(), &returnSize, guard);
|
|
199
|
+
|
|
200
|
+
// Argument types
|
|
201
|
+
std::vector<ffi_type *> argFFITypes;
|
|
202
|
+
argFFITypes.reserve(argCount);
|
|
203
|
+
for (uint32_t i = 0; i < argCount; i++) {
|
|
204
|
+
ffi_type *argType =
|
|
205
|
+
GetFFITypeForEncoding(argTypes[i].c_str(), nullptr, guard);
|
|
206
|
+
argFFITypes.push_back(argType);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Prepare the FFI CIF
|
|
210
|
+
ffi_cif cif;
|
|
211
|
+
ffi_status status;
|
|
212
|
+
bool isVariadic =
|
|
213
|
+
(fixedArgCount >= 0 && static_cast<uint32_t>(fixedArgCount) < argCount);
|
|
214
|
+
|
|
215
|
+
if (isVariadic) {
|
|
216
|
+
NOBJC_LOG("CallFunction: Using variadic CIF (%d fixed, %u total)",
|
|
217
|
+
fixedArgCount, argCount);
|
|
218
|
+
status = ffi_prep_cif_var(&cif, FFI_DEFAULT_ABI, fixedArgCount, argCount,
|
|
219
|
+
returnFFIType, argFFITypes.data());
|
|
220
|
+
} else {
|
|
221
|
+
status = ffi_prep_cif(&cif, FFI_DEFAULT_ABI, argCount, returnFFIType,
|
|
222
|
+
argFFITypes.data());
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (status != FFI_OK) {
|
|
226
|
+
throw Napi::Error::New(env,
|
|
227
|
+
"Failed to prepare FFI call for function '" +
|
|
228
|
+
functionName + "' (ffi_prep_cif status: " +
|
|
229
|
+
std::to_string(status) + ")");
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Extract arguments from JS values
|
|
233
|
+
std::vector<void *> argValues(argCount);
|
|
234
|
+
std::vector<std::unique_ptr<uint8_t[]>> argBuffers;
|
|
235
|
+
argBuffers.reserve(argCount);
|
|
236
|
+
|
|
237
|
+
for (uint32_t i = 0; i < argCount; i++) {
|
|
238
|
+
void *argPtr = nullptr;
|
|
239
|
+
auto buffer = ExtractFunctionArgument(env, info[4 + i], argTypes[i],
|
|
240
|
+
&argPtr, functionName, i, guard);
|
|
241
|
+
argValues[i] = argPtr;
|
|
242
|
+
argBuffers.push_back(std::move(buffer));
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Prepare return buffer
|
|
246
|
+
std::unique_ptr<uint8_t[]> returnBuffer;
|
|
247
|
+
const char *simplifiedReturn = SimplifyTypeEncoding(returnType.c_str());
|
|
248
|
+
bool isVoidReturn = (*simplifiedReturn == 'v');
|
|
249
|
+
|
|
250
|
+
if (!isVoidReturn) {
|
|
251
|
+
size_t bufferSize =
|
|
252
|
+
returnSize > 0 ? returnSize : nobjc::kMinReturnBufferSize;
|
|
253
|
+
// Ensure minimum size for ffi_call (must be at least ffi_arg size)
|
|
254
|
+
if (bufferSize < sizeof(ffi_arg)) {
|
|
255
|
+
bufferSize = sizeof(ffi_arg);
|
|
256
|
+
}
|
|
257
|
+
returnBuffer = std::make_unique<uint8_t[]>(bufferSize);
|
|
258
|
+
memset(returnBuffer.get(), 0, bufferSize);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Make the FFI call
|
|
262
|
+
NOBJC_LOG("CallFunction: Calling '%s' with %u args...",
|
|
263
|
+
functionName.c_str(), argCount);
|
|
264
|
+
ffi_call(&cif, FFI_FN(funcPtr), returnBuffer ? returnBuffer.get() : nullptr,
|
|
265
|
+
argCount > 0 ? argValues.data() : nullptr);
|
|
266
|
+
NOBJC_LOG("CallFunction: '%s' returned successfully", functionName.c_str());
|
|
267
|
+
|
|
268
|
+
// Convert return value
|
|
269
|
+
if (isVoidReturn) {
|
|
270
|
+
return env.Undefined();
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return ConvertFunctionReturnToJS(env, returnBuffer.get(), returnType);
|
|
274
|
+
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
#include "memory-utils.h"
|
|
5
5
|
#include "protocol-storage.h"
|
|
6
|
+
#include <cstring>
|
|
6
7
|
#include <functional>
|
|
7
8
|
#include <napi.h>
|
|
8
9
|
#include <optional>
|
|
@@ -13,6 +14,42 @@
|
|
|
13
14
|
typedef struct NSInvocation NSInvocation;
|
|
14
15
|
#endif
|
|
15
16
|
|
|
17
|
+
// MARK: - Forwarding Pipeline Cache
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Thread-local cache to avoid redundant lock acquisition in the
|
|
21
|
+
* RespondsToSelector -> MethodSignatureForSelector pipeline.
|
|
22
|
+
*
|
|
23
|
+
* A single forwarded call triggers both methods sequentially on the same thread.
|
|
24
|
+
* By caching the type encoding from RespondsToSelector, MethodSignatureForSelector
|
|
25
|
+
* can skip the lock entirely on cache hit.
|
|
26
|
+
*/
|
|
27
|
+
struct ForwardingPipelineCache {
|
|
28
|
+
void* key; // instance ptr (protocols) or class ptr (subclasses)
|
|
29
|
+
SEL selector;
|
|
30
|
+
char typeEncoding[128];
|
|
31
|
+
bool valid;
|
|
32
|
+
|
|
33
|
+
void store(void* k, SEL sel, const char* encoding) {
|
|
34
|
+
key = k;
|
|
35
|
+
selector = sel;
|
|
36
|
+
std::strncpy(typeEncoding, encoding, sizeof(typeEncoding) - 1);
|
|
37
|
+
typeEncoding[sizeof(typeEncoding) - 1] = '\0';
|
|
38
|
+
valid = true;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
void invalidate() { valid = false; }
|
|
42
|
+
|
|
43
|
+
bool matches(void* k, SEL sel) const {
|
|
44
|
+
return valid && key == k && selector == sel;
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
inline ForwardingPipelineCache& GetForwardingCache() {
|
|
49
|
+
static thread_local ForwardingPipelineCache cache = {nullptr, nullptr, "", false};
|
|
50
|
+
return cache;
|
|
51
|
+
}
|
|
52
|
+
|
|
16
53
|
// MARK: - Forwarding Context
|
|
17
54
|
|
|
18
55
|
/**
|
|
@@ -51,18 +88,18 @@ struct ForwardingCallbacks {
|
|
|
51
88
|
// Look up context data under lock. Returns nullopt if not found.
|
|
52
89
|
// Also acquires the TSFN.
|
|
53
90
|
std::function<std::optional<ForwardingContext>(
|
|
54
|
-
void *lookupKey,
|
|
91
|
+
void *lookupKey, SEL selector)>
|
|
55
92
|
lookupContext;
|
|
56
93
|
|
|
57
94
|
// Get the JS function for direct call (called within HandleScope).
|
|
58
95
|
// Returns empty function if not found.
|
|
59
|
-
std::function<Napi::Function(void *lookupKey,
|
|
60
|
-
|
|
96
|
+
std::function<Napi::Function(void *lookupKey, SEL selector,
|
|
97
|
+
Napi::Env env)>
|
|
61
98
|
getJSFunction;
|
|
62
99
|
|
|
63
100
|
// Re-acquire TSFN for fallback path. Returns nullopt if not found.
|
|
64
101
|
std::function<std::optional<Napi::ThreadSafeFunction>(
|
|
65
|
-
void *lookupKey,
|
|
102
|
+
void *lookupKey, SEL selector)>
|
|
66
103
|
reacquireTSFN;
|
|
67
104
|
|
|
68
105
|
// What callback type to use
|
|
@@ -81,7 +118,7 @@ struct ForwardingCallbacks {
|
|
|
81
118
|
* @param callbacks The storage-specific callback functions
|
|
82
119
|
*/
|
|
83
120
|
void ForwardInvocationCommon(NSInvocation *invocation,
|
|
84
|
-
|
|
121
|
+
SEL selector, void *lookupKey,
|
|
85
122
|
const ForwardingCallbacks &callbacks);
|
|
86
123
|
|
|
87
124
|
#endif // FORWARDING_COMMON_H
|
|
@@ -9,12 +9,14 @@
|
|
|
9
9
|
// MARK: - ForwardInvocationCommon Implementation
|
|
10
10
|
|
|
11
11
|
void ForwardInvocationCommon(NSInvocation *invocation,
|
|
12
|
-
|
|
12
|
+
SEL selector, void *lookupKey,
|
|
13
13
|
const ForwardingCallbacks &callbacks) {
|
|
14
|
+
const char *selectorCStr = sel_getName(selector);
|
|
15
|
+
|
|
14
16
|
// Look up context data (acquires TSFN)
|
|
15
|
-
auto contextOpt = callbacks.lookupContext(lookupKey,
|
|
17
|
+
auto contextOpt = callbacks.lookupContext(lookupKey, selector);
|
|
16
18
|
if (!contextOpt) {
|
|
17
|
-
NOBJC_WARN("Lookup failed for selector %s",
|
|
19
|
+
NOBJC_WARN("Lookup failed for selector %s", selectorCStr);
|
|
18
20
|
[invocation release];
|
|
19
21
|
return;
|
|
20
22
|
}
|
|
@@ -27,7 +29,7 @@ void ForwardInvocationCommon(NSInvocation *invocation,
|
|
|
27
29
|
// Create invocation data with RAII guard
|
|
28
30
|
auto data = new InvocationData();
|
|
29
31
|
data->invocation = invocation;
|
|
30
|
-
data->selectorName =
|
|
32
|
+
data->selectorName = selectorCStr;
|
|
31
33
|
data->typeEncoding = ctx.typeEncoding;
|
|
32
34
|
data->callbackType = callbacks.callbackType;
|
|
33
35
|
data->instancePtr = ctx.instancePtr;
|
|
@@ -45,7 +47,7 @@ void ForwardInvocationCommon(NSInvocation *invocation,
|
|
|
45
47
|
|
|
46
48
|
if (use_direct_call) {
|
|
47
49
|
NOBJC_LOG("ForwardInvocationCommon: Using direct call path for selector %s",
|
|
48
|
-
|
|
50
|
+
selectorCStr);
|
|
49
51
|
|
|
50
52
|
// Release the TSFN since we're calling directly
|
|
51
53
|
ctx.tsfn.Release();
|
|
@@ -66,28 +68,28 @@ void ForwardInvocationCommon(NSInvocation *invocation,
|
|
|
66
68
|
|
|
67
69
|
// Fallback to callback lookup if cache miss (shouldn't happen)
|
|
68
70
|
if (jsFn.IsEmpty()) {
|
|
69
|
-
jsFn = callbacks.getJSFunction(lookupKey,
|
|
71
|
+
jsFn = callbacks.getJSFunction(lookupKey, selector, callEnv);
|
|
70
72
|
}
|
|
71
73
|
|
|
72
74
|
if (jsFn.IsEmpty()) {
|
|
73
75
|
NOBJC_WARN("JS function not found for selector %s (direct path)",
|
|
74
|
-
|
|
76
|
+
selectorCStr);
|
|
75
77
|
return; // dataGuard cleans up
|
|
76
78
|
}
|
|
77
79
|
|
|
78
80
|
// Transfer ownership to CallJSCallback - it will clean up
|
|
79
81
|
CallJSCallback(callEnv, jsFn, dataGuard.release());
|
|
80
82
|
NOBJC_LOG("ForwardInvocationCommon: Direct call succeeded for %s",
|
|
81
|
-
|
|
83
|
+
selectorCStr);
|
|
82
84
|
} catch (const std::exception &e) {
|
|
83
85
|
NOBJC_ERROR("Error calling JS callback directly: %s", e.what());
|
|
84
86
|
NOBJC_LOG("Falling back to ThreadSafeFunction for selector %s",
|
|
85
|
-
|
|
87
|
+
selectorCStr);
|
|
86
88
|
|
|
87
89
|
// Re-create data for fallback since we may have released it
|
|
88
90
|
auto fallbackData = new InvocationData();
|
|
89
91
|
fallbackData->invocation = invocation;
|
|
90
|
-
fallbackData->selectorName =
|
|
92
|
+
fallbackData->selectorName = selectorCStr;
|
|
91
93
|
fallbackData->typeEncoding = ctx.typeEncoding;
|
|
92
94
|
fallbackData->callbackType = callbacks.callbackType;
|
|
93
95
|
fallbackData->instancePtr = ctx.instancePtr;
|
|
@@ -95,20 +97,21 @@ void ForwardInvocationCommon(NSInvocation *invocation,
|
|
|
95
97
|
InvocationDataGuard fallbackGuard(fallbackData);
|
|
96
98
|
|
|
97
99
|
// Re-acquire TSFN for fallback
|
|
98
|
-
auto tsfnOpt = callbacks.reacquireTSFN(lookupKey,
|
|
100
|
+
auto tsfnOpt = callbacks.reacquireTSFN(lookupKey, selector);
|
|
99
101
|
if (tsfnOpt) {
|
|
100
|
-
|
|
102
|
+
std::string selectorStr(selectorCStr);
|
|
103
|
+
if (FallbackToTSFN(*tsfnOpt, fallbackGuard.release(), selectorStr)) {
|
|
101
104
|
return; // Data cleaned up in callback
|
|
102
105
|
}
|
|
103
106
|
NOBJC_ERROR("ForwardInvocationCommon: Fallback failed for %s",
|
|
104
|
-
|
|
107
|
+
selectorCStr);
|
|
105
108
|
}
|
|
106
109
|
// If we get here, fallbackGuard cleans up
|
|
107
110
|
}
|
|
108
111
|
} else {
|
|
109
112
|
// Cross-thread call via TSFN (or Electron forcing TSFN)
|
|
110
113
|
NOBJC_LOG("ForwardInvocationCommon: Using TSFN+runloop path for selector %s",
|
|
111
|
-
|
|
114
|
+
selectorCStr);
|
|
112
115
|
|
|
113
116
|
std::mutex completionMutex;
|
|
114
117
|
std::condition_variable completionCv;
|
|
@@ -124,7 +127,7 @@ void ForwardInvocationCommon(NSInvocation *invocation,
|
|
|
124
127
|
|
|
125
128
|
if (status != napi_ok) {
|
|
126
129
|
NOBJC_ERROR("Failed to call ThreadSafeFunction for selector %s (status: %d)",
|
|
127
|
-
|
|
130
|
+
selectorCStr, status);
|
|
128
131
|
// We already released from guard, so clean up manually
|
|
129
132
|
[invocation release];
|
|
130
133
|
delete data;
|