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
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
#ifndef PROTOCOL_STORAGE_H
|
|
2
|
+
#define PROTOCOL_STORAGE_H
|
|
3
|
+
|
|
4
|
+
#include <condition_variable>
|
|
5
|
+
#include <mutex>
|
|
6
|
+
#include <napi.h>
|
|
7
|
+
#include <pthread.h>
|
|
8
|
+
#include <string>
|
|
9
|
+
#include <unordered_map>
|
|
10
|
+
|
|
11
|
+
// Forward declarations for Objective-C types
|
|
12
|
+
#ifdef __OBJC__
|
|
13
|
+
@class NSInvocation;
|
|
14
|
+
#else
|
|
15
|
+
typedef struct NSInvocation NSInvocation;
|
|
16
|
+
#endif
|
|
17
|
+
|
|
18
|
+
// MARK: - Data Structures
|
|
19
|
+
|
|
20
|
+
// Data passed from native thread to JS thread for invocation handling
|
|
21
|
+
struct InvocationData {
|
|
22
|
+
NSInvocation *invocation;
|
|
23
|
+
std::string selectorName;
|
|
24
|
+
std::string typeEncoding;
|
|
25
|
+
// Synchronization: we use NonBlockingCall + runloop pumping to avoid
|
|
26
|
+
// deadlocks in Electron while still getting return values
|
|
27
|
+
std::mutex *completionMutex;
|
|
28
|
+
std::condition_variable *completionCv;
|
|
29
|
+
bool *isComplete;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// Stores information about a protocol implementation instance
|
|
33
|
+
struct ProtocolImplementation {
|
|
34
|
+
// ThreadSafeFunction for each selector - allows calling JS from any thread
|
|
35
|
+
std::unordered_map<std::string, Napi::ThreadSafeFunction> callbacks;
|
|
36
|
+
// Original JS functions for direct calls (kept alive by persistent refs)
|
|
37
|
+
std::unordered_map<std::string, Napi::FunctionReference> jsCallbacks;
|
|
38
|
+
// Type encodings for each selector
|
|
39
|
+
std::unordered_map<std::string, std::string> typeEncodings;
|
|
40
|
+
// Dynamically generated class name
|
|
41
|
+
std::string className;
|
|
42
|
+
// Store the environment for direct calls
|
|
43
|
+
napi_env env;
|
|
44
|
+
// Store the JS thread ID for thread detection
|
|
45
|
+
pthread_t js_thread;
|
|
46
|
+
// Flag to indicate if running in Electron (requires TSFN path always)
|
|
47
|
+
bool isElectron;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// MARK: - Global Storage
|
|
51
|
+
|
|
52
|
+
// Global map: instance pointer -> implementation details
|
|
53
|
+
// This keeps JavaScript callbacks alive for the lifetime of the Objective-C
|
|
54
|
+
// object
|
|
55
|
+
extern std::unordered_map<void *, ProtocolImplementation> g_implementations;
|
|
56
|
+
extern std::mutex g_implementations_mutex;
|
|
57
|
+
|
|
58
|
+
// MARK: - Storage Access Helpers
|
|
59
|
+
|
|
60
|
+
// Helper to signal completion of an invocation
|
|
61
|
+
inline void SignalInvocationComplete(InvocationData *data) {
|
|
62
|
+
if (data->completionMutex && data->completionCv && data->isComplete) {
|
|
63
|
+
std::lock_guard<std::mutex> lock(*data->completionMutex);
|
|
64
|
+
*data->isComplete = true;
|
|
65
|
+
data->completionCv->notify_one();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Look up implementation for an instance pointer
|
|
70
|
+
// Returns nullptr if not found
|
|
71
|
+
// Caller must hold g_implementations_mutex
|
|
72
|
+
inline ProtocolImplementation *
|
|
73
|
+
FindImplementation(void *instancePtr) {
|
|
74
|
+
auto it = g_implementations.find(instancePtr);
|
|
75
|
+
if (it != g_implementations.end()) {
|
|
76
|
+
return &it->second;
|
|
77
|
+
}
|
|
78
|
+
return nullptr;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
#endif // PROTOCOL_STORAGE_H
|
|
82
|
+
|
|
@@ -0,0 +1,612 @@
|
|
|
1
|
+
#ifndef TYPE_CONVERSION_H
|
|
2
|
+
#define TYPE_CONVERSION_H
|
|
3
|
+
|
|
4
|
+
#include "ObjcObject.h"
|
|
5
|
+
#include <Foundation/Foundation.h>
|
|
6
|
+
#include <napi.h>
|
|
7
|
+
#include <objc/runtime.h>
|
|
8
|
+
#include <string>
|
|
9
|
+
|
|
10
|
+
// MARK: - Type Encoding Utilities
|
|
11
|
+
|
|
12
|
+
// Helper class to manage the lifetime of simplified type encodings
|
|
13
|
+
class SimplifiedTypeEncoding {
|
|
14
|
+
private:
|
|
15
|
+
std::string simplified;
|
|
16
|
+
|
|
17
|
+
public:
|
|
18
|
+
SimplifiedTypeEncoding(const char *typeEncoding) : simplified(typeEncoding) {
|
|
19
|
+
// Remove any leading qualifiers (r=const, n=in, N=inout, o=out, O=bycopy,
|
|
20
|
+
// R=byref, V=oneway)
|
|
21
|
+
while (!simplified.empty() &&
|
|
22
|
+
(simplified[0] == 'r' || simplified[0] == 'n' ||
|
|
23
|
+
simplified[0] == 'N' || simplified[0] == 'o' ||
|
|
24
|
+
simplified[0] == 'O' || simplified[0] == 'R' ||
|
|
25
|
+
simplified[0] == 'V')) {
|
|
26
|
+
simplified.erase(0, 1);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const char *c_str() const { return simplified.c_str(); }
|
|
31
|
+
char operator[](size_t index) const { return simplified[index]; }
|
|
32
|
+
operator const char *() const { return simplified.c_str(); }
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// Legacy function for compatibility - returns pointer to internal string
|
|
36
|
+
// WARNING: The returned pointer is only valid as long as the typeEncoding
|
|
37
|
+
// parameter is valid
|
|
38
|
+
inline const char *SimplifyTypeEncoding(const char *typeEncoding) {
|
|
39
|
+
// For simple cases where there are no qualifiers, return the original pointer
|
|
40
|
+
if (typeEncoding && typeEncoding[0] != 'r' && typeEncoding[0] != 'n' &&
|
|
41
|
+
typeEncoding[0] != 'N' && typeEncoding[0] != 'o' &&
|
|
42
|
+
typeEncoding[0] != 'O' && typeEncoding[0] != 'R' &&
|
|
43
|
+
typeEncoding[0] != 'V') {
|
|
44
|
+
return typeEncoding;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// For complex cases, we need to skip qualifiers
|
|
48
|
+
// This is a temporary fix - callers should use SimplifiedTypeEncoding class
|
|
49
|
+
static thread_local std::string buffer;
|
|
50
|
+
buffer = typeEncoding;
|
|
51
|
+
while (!buffer.empty() &&
|
|
52
|
+
(buffer[0] == 'r' || buffer[0] == 'n' || buffer[0] == 'N' ||
|
|
53
|
+
buffer[0] == 'o' || buffer[0] == 'O' || buffer[0] == 'R' ||
|
|
54
|
+
buffer[0] == 'V')) {
|
|
55
|
+
buffer.erase(0, 1);
|
|
56
|
+
}
|
|
57
|
+
return buffer.c_str();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// MARK: - ObjC to JS Conversion
|
|
61
|
+
|
|
62
|
+
// Convert an Objective-C value (from a pointer) to a JavaScript value
|
|
63
|
+
inline Napi::Value ObjCToJS(Napi::Env env, void *valuePtr, char typeCode) {
|
|
64
|
+
switch (typeCode) {
|
|
65
|
+
case 'c': {
|
|
66
|
+
char value = *(char *)valuePtr;
|
|
67
|
+
return Napi::Number::New(env, value);
|
|
68
|
+
}
|
|
69
|
+
case 'i': {
|
|
70
|
+
int value = *(int *)valuePtr;
|
|
71
|
+
return Napi::Number::New(env, value);
|
|
72
|
+
}
|
|
73
|
+
case 's': {
|
|
74
|
+
short value = *(short *)valuePtr;
|
|
75
|
+
return Napi::Number::New(env, value);
|
|
76
|
+
}
|
|
77
|
+
case 'l': {
|
|
78
|
+
long value = *(long *)valuePtr;
|
|
79
|
+
return Napi::Number::New(env, value);
|
|
80
|
+
}
|
|
81
|
+
case 'q': {
|
|
82
|
+
long long value = *(long long *)valuePtr;
|
|
83
|
+
return Napi::Number::New(env, value);
|
|
84
|
+
}
|
|
85
|
+
case 'C': {
|
|
86
|
+
unsigned char value = *(unsigned char *)valuePtr;
|
|
87
|
+
return Napi::Number::New(env, value);
|
|
88
|
+
}
|
|
89
|
+
case 'I': {
|
|
90
|
+
unsigned int value = *(unsigned int *)valuePtr;
|
|
91
|
+
return Napi::Number::New(env, value);
|
|
92
|
+
}
|
|
93
|
+
case 'S': {
|
|
94
|
+
unsigned short value = *(unsigned short *)valuePtr;
|
|
95
|
+
return Napi::Number::New(env, value);
|
|
96
|
+
}
|
|
97
|
+
case 'L': {
|
|
98
|
+
unsigned long value = *(unsigned long *)valuePtr;
|
|
99
|
+
return Napi::Number::New(env, value);
|
|
100
|
+
}
|
|
101
|
+
case 'Q': {
|
|
102
|
+
unsigned long long value = *(unsigned long long *)valuePtr;
|
|
103
|
+
return Napi::Number::New(env, value);
|
|
104
|
+
}
|
|
105
|
+
case 'f': {
|
|
106
|
+
float value = *(float *)valuePtr;
|
|
107
|
+
return Napi::Number::New(env, value);
|
|
108
|
+
}
|
|
109
|
+
case 'd': {
|
|
110
|
+
double value = *(double *)valuePtr;
|
|
111
|
+
return Napi::Number::New(env, value);
|
|
112
|
+
}
|
|
113
|
+
case 'B': {
|
|
114
|
+
bool value = *(bool *)valuePtr;
|
|
115
|
+
return Napi::Boolean::New(env, value);
|
|
116
|
+
}
|
|
117
|
+
case '*': {
|
|
118
|
+
char *value = *(char **)valuePtr;
|
|
119
|
+
if (value == nullptr) {
|
|
120
|
+
return env.Null();
|
|
121
|
+
}
|
|
122
|
+
return Napi::String::New(env, value);
|
|
123
|
+
}
|
|
124
|
+
case '@': {
|
|
125
|
+
id value = *(__strong id *)valuePtr;
|
|
126
|
+
if (value == nil) {
|
|
127
|
+
return env.Null();
|
|
128
|
+
}
|
|
129
|
+
return ObjcObject::NewInstance(env, value);
|
|
130
|
+
}
|
|
131
|
+
case '#': {
|
|
132
|
+
Class value = *(Class *)valuePtr;
|
|
133
|
+
if (value == nil) {
|
|
134
|
+
return env.Null();
|
|
135
|
+
}
|
|
136
|
+
return ObjcObject::NewInstance(env, value);
|
|
137
|
+
}
|
|
138
|
+
case ':': {
|
|
139
|
+
SEL value = *(SEL *)valuePtr;
|
|
140
|
+
if (value == nullptr) {
|
|
141
|
+
return env.Null();
|
|
142
|
+
}
|
|
143
|
+
NSString *selectorString = NSStringFromSelector(value);
|
|
144
|
+
if (selectorString == nil) {
|
|
145
|
+
return env.Null();
|
|
146
|
+
}
|
|
147
|
+
return Napi::String::New(env, [selectorString UTF8String]);
|
|
148
|
+
}
|
|
149
|
+
case 'v':
|
|
150
|
+
return env.Undefined();
|
|
151
|
+
default:
|
|
152
|
+
return env.Undefined();
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Extract an argument from NSInvocation and convert to JS value
|
|
157
|
+
inline Napi::Value ExtractInvocationArgumentToJS(Napi::Env env,
|
|
158
|
+
NSInvocation *invocation,
|
|
159
|
+
NSUInteger index,
|
|
160
|
+
char typeCode) {
|
|
161
|
+
switch (typeCode) {
|
|
162
|
+
case 'c': {
|
|
163
|
+
char value;
|
|
164
|
+
[invocation getArgument:&value atIndex:index];
|
|
165
|
+
return Napi::Number::New(env, value);
|
|
166
|
+
}
|
|
167
|
+
case 'i': {
|
|
168
|
+
int value;
|
|
169
|
+
[invocation getArgument:&value atIndex:index];
|
|
170
|
+
return Napi::Number::New(env, value);
|
|
171
|
+
}
|
|
172
|
+
case 's': {
|
|
173
|
+
short value;
|
|
174
|
+
[invocation getArgument:&value atIndex:index];
|
|
175
|
+
return Napi::Number::New(env, value);
|
|
176
|
+
}
|
|
177
|
+
case 'l': {
|
|
178
|
+
long value;
|
|
179
|
+
[invocation getArgument:&value atIndex:index];
|
|
180
|
+
return Napi::Number::New(env, value);
|
|
181
|
+
}
|
|
182
|
+
case 'q': {
|
|
183
|
+
long long value;
|
|
184
|
+
[invocation getArgument:&value atIndex:index];
|
|
185
|
+
return Napi::Number::New(env, value);
|
|
186
|
+
}
|
|
187
|
+
case 'C': {
|
|
188
|
+
unsigned char value;
|
|
189
|
+
[invocation getArgument:&value atIndex:index];
|
|
190
|
+
return Napi::Number::New(env, value);
|
|
191
|
+
}
|
|
192
|
+
case 'I': {
|
|
193
|
+
unsigned int value;
|
|
194
|
+
[invocation getArgument:&value atIndex:index];
|
|
195
|
+
return Napi::Number::New(env, value);
|
|
196
|
+
}
|
|
197
|
+
case 'S': {
|
|
198
|
+
unsigned short value;
|
|
199
|
+
[invocation getArgument:&value atIndex:index];
|
|
200
|
+
return Napi::Number::New(env, value);
|
|
201
|
+
}
|
|
202
|
+
case 'L': {
|
|
203
|
+
unsigned long value;
|
|
204
|
+
[invocation getArgument:&value atIndex:index];
|
|
205
|
+
return Napi::Number::New(env, value);
|
|
206
|
+
}
|
|
207
|
+
case 'Q': {
|
|
208
|
+
unsigned long long value;
|
|
209
|
+
[invocation getArgument:&value atIndex:index];
|
|
210
|
+
return Napi::Number::New(env, value);
|
|
211
|
+
}
|
|
212
|
+
case 'f': {
|
|
213
|
+
float value;
|
|
214
|
+
[invocation getArgument:&value atIndex:index];
|
|
215
|
+
return Napi::Number::New(env, value);
|
|
216
|
+
}
|
|
217
|
+
case 'd': {
|
|
218
|
+
double value;
|
|
219
|
+
[invocation getArgument:&value atIndex:index];
|
|
220
|
+
return Napi::Number::New(env, value);
|
|
221
|
+
}
|
|
222
|
+
case 'B': {
|
|
223
|
+
bool value;
|
|
224
|
+
[invocation getArgument:&value atIndex:index];
|
|
225
|
+
return Napi::Boolean::New(env, value);
|
|
226
|
+
}
|
|
227
|
+
case '*': {
|
|
228
|
+
char *value;
|
|
229
|
+
[invocation getArgument:&value atIndex:index];
|
|
230
|
+
if (value == nullptr) {
|
|
231
|
+
return env.Null();
|
|
232
|
+
}
|
|
233
|
+
return Napi::String::New(env, value);
|
|
234
|
+
}
|
|
235
|
+
case '@': {
|
|
236
|
+
__unsafe_unretained id value;
|
|
237
|
+
[invocation getArgument:&value atIndex:index];
|
|
238
|
+
if (value == nil) {
|
|
239
|
+
return env.Null();
|
|
240
|
+
}
|
|
241
|
+
return ObjcObject::NewInstance(env, value);
|
|
242
|
+
}
|
|
243
|
+
case '#': {
|
|
244
|
+
Class value;
|
|
245
|
+
[invocation getArgument:&value atIndex:index];
|
|
246
|
+
if (value == nil) {
|
|
247
|
+
return env.Null();
|
|
248
|
+
}
|
|
249
|
+
return ObjcObject::NewInstance(env, value);
|
|
250
|
+
}
|
|
251
|
+
case ':': {
|
|
252
|
+
SEL value;
|
|
253
|
+
[invocation getArgument:&value atIndex:index];
|
|
254
|
+
if (value == nullptr) {
|
|
255
|
+
return env.Null();
|
|
256
|
+
}
|
|
257
|
+
NSString *selString = NSStringFromSelector(value);
|
|
258
|
+
if (selString == nil) {
|
|
259
|
+
return env.Null();
|
|
260
|
+
}
|
|
261
|
+
return Napi::String::New(env, [selString UTF8String]);
|
|
262
|
+
}
|
|
263
|
+
case '^': {
|
|
264
|
+
void *value;
|
|
265
|
+
[invocation getArgument:&value atIndex:index];
|
|
266
|
+
if (value == nullptr) {
|
|
267
|
+
return env.Null();
|
|
268
|
+
}
|
|
269
|
+
return env.Undefined();
|
|
270
|
+
}
|
|
271
|
+
default:
|
|
272
|
+
return env.Undefined();
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// MARK: - JS to ObjC Return Value Conversion
|
|
277
|
+
|
|
278
|
+
// Set the return value on an NSInvocation from a JS value
|
|
279
|
+
inline void SetInvocationReturnFromJS(NSInvocation *invocation,
|
|
280
|
+
Napi::Value result, char typeCode,
|
|
281
|
+
const char *selectorName) {
|
|
282
|
+
if (result.IsUndefined() || result.IsNull()) {
|
|
283
|
+
// For null/undefined, set nil for object types, skip for others
|
|
284
|
+
if (typeCode == '@') {
|
|
285
|
+
id objcValue = nil;
|
|
286
|
+
[invocation setReturnValue:&objcValue];
|
|
287
|
+
}
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
auto asSigned64 = [&](int64_t &out) -> bool {
|
|
292
|
+
if (result.IsBoolean()) {
|
|
293
|
+
out = result.As<Napi::Boolean>().Value() ? 1 : 0;
|
|
294
|
+
return true;
|
|
295
|
+
}
|
|
296
|
+
if (result.IsNumber()) {
|
|
297
|
+
out = result.As<Napi::Number>().Int64Value();
|
|
298
|
+
return true;
|
|
299
|
+
}
|
|
300
|
+
return false;
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
auto asUnsigned64 = [&](uint64_t &out) -> bool {
|
|
304
|
+
if (result.IsBoolean()) {
|
|
305
|
+
out = result.As<Napi::Boolean>().Value() ? 1 : 0;
|
|
306
|
+
return true;
|
|
307
|
+
}
|
|
308
|
+
if (result.IsNumber()) {
|
|
309
|
+
out = static_cast<uint64_t>(result.As<Napi::Number>().Int64Value());
|
|
310
|
+
return true;
|
|
311
|
+
}
|
|
312
|
+
return false;
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
auto asDouble = [&](double &out) -> bool {
|
|
316
|
+
if (result.IsBoolean()) {
|
|
317
|
+
out = result.As<Napi::Boolean>().Value() ? 1.0 : 0.0;
|
|
318
|
+
return true;
|
|
319
|
+
}
|
|
320
|
+
if (result.IsNumber()) {
|
|
321
|
+
out = result.As<Napi::Number>().DoubleValue();
|
|
322
|
+
return true;
|
|
323
|
+
}
|
|
324
|
+
return false;
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
switch (typeCode) {
|
|
328
|
+
case 'c': {
|
|
329
|
+
int64_t value = 0;
|
|
330
|
+
if (!asSigned64(value)) {
|
|
331
|
+
NSLog(@"Warning: result is not a number/boolean for selector %s",
|
|
332
|
+
selectorName);
|
|
333
|
+
break;
|
|
334
|
+
}
|
|
335
|
+
char valueChar = static_cast<char>(value);
|
|
336
|
+
[invocation setReturnValue:&valueChar];
|
|
337
|
+
break;
|
|
338
|
+
}
|
|
339
|
+
case 'i': {
|
|
340
|
+
int64_t value = 0;
|
|
341
|
+
if (!asSigned64(value)) {
|
|
342
|
+
NSLog(@"Warning: result is not a number/boolean for selector %s",
|
|
343
|
+
selectorName);
|
|
344
|
+
break;
|
|
345
|
+
}
|
|
346
|
+
int valueInt = static_cast<int>(value);
|
|
347
|
+
[invocation setReturnValue:&valueInt];
|
|
348
|
+
break;
|
|
349
|
+
}
|
|
350
|
+
case 's': {
|
|
351
|
+
int64_t value = 0;
|
|
352
|
+
if (!asSigned64(value)) {
|
|
353
|
+
NSLog(@"Warning: result is not a number/boolean for selector %s",
|
|
354
|
+
selectorName);
|
|
355
|
+
break;
|
|
356
|
+
}
|
|
357
|
+
short valueShort = static_cast<short>(value);
|
|
358
|
+
[invocation setReturnValue:&valueShort];
|
|
359
|
+
break;
|
|
360
|
+
}
|
|
361
|
+
case 'l': {
|
|
362
|
+
int64_t value = 0;
|
|
363
|
+
if (!asSigned64(value)) {
|
|
364
|
+
NSLog(@"Warning: result is not a number/boolean for selector %s",
|
|
365
|
+
selectorName);
|
|
366
|
+
break;
|
|
367
|
+
}
|
|
368
|
+
long valueLong = static_cast<long>(value);
|
|
369
|
+
[invocation setReturnValue:&valueLong];
|
|
370
|
+
break;
|
|
371
|
+
}
|
|
372
|
+
case 'q': {
|
|
373
|
+
int64_t value = 0;
|
|
374
|
+
if (!asSigned64(value)) {
|
|
375
|
+
NSLog(@"Warning: result is not a number/boolean for selector %s",
|
|
376
|
+
selectorName);
|
|
377
|
+
break;
|
|
378
|
+
}
|
|
379
|
+
long long valueLongLong = static_cast<long long>(value);
|
|
380
|
+
[invocation setReturnValue:&valueLongLong];
|
|
381
|
+
break;
|
|
382
|
+
}
|
|
383
|
+
case 'C': {
|
|
384
|
+
uint64_t value = 0;
|
|
385
|
+
if (!asUnsigned64(value)) {
|
|
386
|
+
NSLog(@"Warning: result is not a number/boolean for selector %s",
|
|
387
|
+
selectorName);
|
|
388
|
+
break;
|
|
389
|
+
}
|
|
390
|
+
unsigned char valueChar = static_cast<unsigned char>(value);
|
|
391
|
+
[invocation setReturnValue:&valueChar];
|
|
392
|
+
break;
|
|
393
|
+
}
|
|
394
|
+
case 'I': {
|
|
395
|
+
uint64_t value = 0;
|
|
396
|
+
if (!asUnsigned64(value)) {
|
|
397
|
+
NSLog(@"Warning: result is not a number/boolean for selector %s",
|
|
398
|
+
selectorName);
|
|
399
|
+
break;
|
|
400
|
+
}
|
|
401
|
+
unsigned int valueInt = static_cast<unsigned int>(value);
|
|
402
|
+
[invocation setReturnValue:&valueInt];
|
|
403
|
+
break;
|
|
404
|
+
}
|
|
405
|
+
case 'S': {
|
|
406
|
+
uint64_t value = 0;
|
|
407
|
+
if (!asUnsigned64(value)) {
|
|
408
|
+
NSLog(@"Warning: result is not a number/boolean for selector %s",
|
|
409
|
+
selectorName);
|
|
410
|
+
break;
|
|
411
|
+
}
|
|
412
|
+
unsigned short valueShort = static_cast<unsigned short>(value);
|
|
413
|
+
[invocation setReturnValue:&valueShort];
|
|
414
|
+
break;
|
|
415
|
+
}
|
|
416
|
+
case 'L': {
|
|
417
|
+
uint64_t value = 0;
|
|
418
|
+
if (!asUnsigned64(value)) {
|
|
419
|
+
NSLog(@"Warning: result is not a number/boolean for selector %s",
|
|
420
|
+
selectorName);
|
|
421
|
+
break;
|
|
422
|
+
}
|
|
423
|
+
unsigned long valueLong = static_cast<unsigned long>(value);
|
|
424
|
+
[invocation setReturnValue:&valueLong];
|
|
425
|
+
break;
|
|
426
|
+
}
|
|
427
|
+
case 'Q': {
|
|
428
|
+
uint64_t value = 0;
|
|
429
|
+
if (!asUnsigned64(value)) {
|
|
430
|
+
NSLog(@"Warning: result is not a number/boolean for selector %s",
|
|
431
|
+
selectorName);
|
|
432
|
+
break;
|
|
433
|
+
}
|
|
434
|
+
unsigned long long valueLongLong =
|
|
435
|
+
static_cast<unsigned long long>(value);
|
|
436
|
+
[invocation setReturnValue:&valueLongLong];
|
|
437
|
+
break;
|
|
438
|
+
}
|
|
439
|
+
case 'f': {
|
|
440
|
+
double value = 0;
|
|
441
|
+
if (!asDouble(value)) {
|
|
442
|
+
NSLog(@"Warning: result is not a number/boolean for selector %s",
|
|
443
|
+
selectorName);
|
|
444
|
+
break;
|
|
445
|
+
}
|
|
446
|
+
float valueFloat = static_cast<float>(value);
|
|
447
|
+
[invocation setReturnValue:&valueFloat];
|
|
448
|
+
break;
|
|
449
|
+
}
|
|
450
|
+
case 'd': {
|
|
451
|
+
double value = 0;
|
|
452
|
+
if (!asDouble(value)) {
|
|
453
|
+
NSLog(@"Warning: result is not a number/boolean for selector %s",
|
|
454
|
+
selectorName);
|
|
455
|
+
break;
|
|
456
|
+
}
|
|
457
|
+
[invocation setReturnValue:&value];
|
|
458
|
+
break;
|
|
459
|
+
}
|
|
460
|
+
case 'B': {
|
|
461
|
+
bool value = false;
|
|
462
|
+
if (result.IsBoolean()) {
|
|
463
|
+
value = result.As<Napi::Boolean>().Value();
|
|
464
|
+
} else if (result.IsNumber()) {
|
|
465
|
+
value = result.As<Napi::Number>().Int32Value() != 0;
|
|
466
|
+
} else {
|
|
467
|
+
NSLog(@"Warning: result is not a boolean/number for selector %s",
|
|
468
|
+
selectorName);
|
|
469
|
+
break;
|
|
470
|
+
}
|
|
471
|
+
[invocation setReturnValue:&value];
|
|
472
|
+
break;
|
|
473
|
+
}
|
|
474
|
+
case '@': {
|
|
475
|
+
// Log the result
|
|
476
|
+
NSLog(@"Result for selector %s: %s", selectorName, result.IsObject() ? "Object" : "nil");
|
|
477
|
+
|
|
478
|
+
if (result.IsObject()) {
|
|
479
|
+
Napi::Object resultObj = result.As<Napi::Object>();
|
|
480
|
+
if (resultObj.InstanceOf(ObjcObject::constructor.Value())) {
|
|
481
|
+
ObjcObject *objcObj = Napi::ObjectWrap<ObjcObject>::Unwrap(resultObj);
|
|
482
|
+
id objcValue = objcObj->objcObject;
|
|
483
|
+
NSLog(@"ObjcObject: %@", objcValue);
|
|
484
|
+
[invocation setReturnValue:&objcValue];
|
|
485
|
+
} else {
|
|
486
|
+
NSLog(@"Warning: result object is not an ObjcObject instance");
|
|
487
|
+
}
|
|
488
|
+
} else {
|
|
489
|
+
NSLog(@"Warning: result is not an object (type: %d)", result.Type());
|
|
490
|
+
}
|
|
491
|
+
break;
|
|
492
|
+
}
|
|
493
|
+
default:
|
|
494
|
+
NSLog(@"Warning: Unsupported return type '%c' for selector %s", typeCode,
|
|
495
|
+
selectorName);
|
|
496
|
+
break;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// MARK: - Return Value Extraction from NSInvocation
|
|
501
|
+
|
|
502
|
+
// Get return value from NSInvocation and convert to JS
|
|
503
|
+
inline Napi::Value GetInvocationReturnAsJS(Napi::Env env,
|
|
504
|
+
NSInvocation *invocation,
|
|
505
|
+
NSMethodSignature *methodSignature) {
|
|
506
|
+
SimplifiedTypeEncoding returnType([methodSignature methodReturnType]);
|
|
507
|
+
|
|
508
|
+
switch (returnType[0]) {
|
|
509
|
+
case 'c': {
|
|
510
|
+
char result;
|
|
511
|
+
[invocation getReturnValue:&result];
|
|
512
|
+
return Napi::Number::New(env, result);
|
|
513
|
+
}
|
|
514
|
+
case 'i': {
|
|
515
|
+
int result;
|
|
516
|
+
[invocation getReturnValue:&result];
|
|
517
|
+
return Napi::Number::New(env, result);
|
|
518
|
+
}
|
|
519
|
+
case 's': {
|
|
520
|
+
short result;
|
|
521
|
+
[invocation getReturnValue:&result];
|
|
522
|
+
return Napi::Number::New(env, result);
|
|
523
|
+
}
|
|
524
|
+
case 'l': {
|
|
525
|
+
long result;
|
|
526
|
+
[invocation getReturnValue:&result];
|
|
527
|
+
return Napi::Number::New(env, result);
|
|
528
|
+
}
|
|
529
|
+
case 'q': {
|
|
530
|
+
long long result;
|
|
531
|
+
[invocation getReturnValue:&result];
|
|
532
|
+
return Napi::Number::New(env, result);
|
|
533
|
+
}
|
|
534
|
+
case 'C': {
|
|
535
|
+
unsigned char result;
|
|
536
|
+
[invocation getReturnValue:&result];
|
|
537
|
+
return Napi::Number::New(env, result);
|
|
538
|
+
}
|
|
539
|
+
case 'I': {
|
|
540
|
+
unsigned int result;
|
|
541
|
+
[invocation getReturnValue:&result];
|
|
542
|
+
return Napi::Number::New(env, result);
|
|
543
|
+
}
|
|
544
|
+
case 'S': {
|
|
545
|
+
unsigned short result;
|
|
546
|
+
[invocation getReturnValue:&result];
|
|
547
|
+
return Napi::Number::New(env, result);
|
|
548
|
+
}
|
|
549
|
+
case 'L': {
|
|
550
|
+
unsigned long result;
|
|
551
|
+
[invocation getReturnValue:&result];
|
|
552
|
+
return Napi::Number::New(env, result);
|
|
553
|
+
}
|
|
554
|
+
case 'Q': {
|
|
555
|
+
unsigned long long result;
|
|
556
|
+
[invocation getReturnValue:&result];
|
|
557
|
+
return Napi::Number::New(env, result);
|
|
558
|
+
}
|
|
559
|
+
case 'f': {
|
|
560
|
+
float result;
|
|
561
|
+
[invocation getReturnValue:&result];
|
|
562
|
+
return Napi::Number::New(env, result);
|
|
563
|
+
}
|
|
564
|
+
case 'd': {
|
|
565
|
+
double result;
|
|
566
|
+
[invocation getReturnValue:&result];
|
|
567
|
+
return Napi::Number::New(env, result);
|
|
568
|
+
}
|
|
569
|
+
case 'B': {
|
|
570
|
+
bool result;
|
|
571
|
+
[invocation getReturnValue:&result];
|
|
572
|
+
return Napi::Boolean::New(env, result);
|
|
573
|
+
}
|
|
574
|
+
case 'v':
|
|
575
|
+
return env.Undefined();
|
|
576
|
+
case '*': {
|
|
577
|
+
char *result = nullptr;
|
|
578
|
+
[invocation getReturnValue:&result];
|
|
579
|
+
if (result == nullptr) {
|
|
580
|
+
return env.Null();
|
|
581
|
+
}
|
|
582
|
+
return Napi::String::New(env, result);
|
|
583
|
+
}
|
|
584
|
+
case '@':
|
|
585
|
+
case '#': {
|
|
586
|
+
id result = nil;
|
|
587
|
+
[invocation getReturnValue:&result];
|
|
588
|
+
if (result == nil) {
|
|
589
|
+
return env.Null();
|
|
590
|
+
}
|
|
591
|
+
return ObjcObject::NewInstance(env, result);
|
|
592
|
+
}
|
|
593
|
+
case ':': {
|
|
594
|
+
SEL result = nullptr;
|
|
595
|
+
[invocation getReturnValue:&result];
|
|
596
|
+
if (result == nullptr) {
|
|
597
|
+
return env.Null();
|
|
598
|
+
}
|
|
599
|
+
NSString *selectorString = NSStringFromSelector(result);
|
|
600
|
+
if (selectorString == nil) {
|
|
601
|
+
return env.Null();
|
|
602
|
+
}
|
|
603
|
+
return Napi::String::New(env, [selectorString UTF8String]);
|
|
604
|
+
}
|
|
605
|
+
default:
|
|
606
|
+
Napi::TypeError::New(env, "Unsupported return type (post-invoke)")
|
|
607
|
+
.ThrowAsJavaScriptException();
|
|
608
|
+
return env.Null();
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
#endif // TYPE_CONVERSION_H
|