objc-js 0.0.8 → 0.0.9
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 +33 -0
- package/build/Release/nobjc_native.node +0 -0
- package/package.json +8 -3
- package/src/native/ObjcObject.h +34 -0
- package/src/native/ObjcObject.mm +150 -0
- package/src/native/bridge.h +391 -0
- package/src/native/nobjc.mm +113 -0
- package/src/native/protocol-impl.h +69 -0
- package/src/native/protocol-impl.mm +833 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
#include "ObjcObject.h"
|
|
2
|
+
#include "protocol-impl.h"
|
|
3
|
+
#include <Foundation/Foundation.h>
|
|
4
|
+
#include <dlfcn.h>
|
|
5
|
+
#include <napi.h>
|
|
6
|
+
|
|
7
|
+
Napi::Value LoadLibrary(const Napi::CallbackInfo &info) {
|
|
8
|
+
Napi::Env env = info.Env();
|
|
9
|
+
if (info.Length() != 1 || !info[0].IsString()) {
|
|
10
|
+
throw Napi::TypeError::New(env, "Expected a single string argument");
|
|
11
|
+
}
|
|
12
|
+
std::string libPath = info[0].As<Napi::String>().Utf8Value();
|
|
13
|
+
void *handle = dlopen(libPath.c_str(), RTLD_LAZY | RTLD_GLOBAL);
|
|
14
|
+
if (!handle) {
|
|
15
|
+
throw Napi::Error::New(env, dlerror());
|
|
16
|
+
}
|
|
17
|
+
return env.Undefined();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
Napi::Value GetClassObject(const Napi::CallbackInfo &info) {
|
|
21
|
+
Napi::Env env = info.Env();
|
|
22
|
+
if (info.Length() != 1 || !info[0].IsString()) {
|
|
23
|
+
throw Napi::TypeError::New(env, "Expected a single string argument");
|
|
24
|
+
}
|
|
25
|
+
std::string className = info[0].As<Napi::String>().Utf8Value();
|
|
26
|
+
Class cls =
|
|
27
|
+
NSClassFromString([NSString stringWithUTF8String:className.c_str()]);
|
|
28
|
+
if (cls == nil) {
|
|
29
|
+
return env.Undefined();
|
|
30
|
+
}
|
|
31
|
+
return ObjcObject::NewInstance(env, cls);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
Napi::Value GetPointer(const Napi::CallbackInfo &info) {
|
|
35
|
+
Napi::Env env = info.Env();
|
|
36
|
+
if (info.Length() != 1 || !info[0].IsObject()) {
|
|
37
|
+
throw Napi::TypeError::New(env, "Expected a single ObjcObject argument");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
Napi::Object obj = info[0].As<Napi::Object>();
|
|
41
|
+
if (!obj.InstanceOf(ObjcObject::constructor.Value())) {
|
|
42
|
+
throw Napi::TypeError::New(env, "Argument must be an ObjcObject instance");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
ObjcObject *objcObj = Napi::ObjectWrap<ObjcObject>::Unwrap(obj);
|
|
46
|
+
uintptr_t ptrValue = reinterpret_cast<uintptr_t>(objcObj->objcObject);
|
|
47
|
+
|
|
48
|
+
// Create a Buffer to hold the pointer (8 bytes on 64-bit macOS)
|
|
49
|
+
Napi::Buffer<uint8_t> buffer = Napi::Buffer<uint8_t>::New(env, sizeof(void*));
|
|
50
|
+
|
|
51
|
+
// Write the pointer value to the buffer in little-endian format
|
|
52
|
+
uint8_t* data = buffer.Data();
|
|
53
|
+
for (size_t i = 0; i < sizeof(void*); ++i) {
|
|
54
|
+
data[i] = static_cast<uint8_t>((ptrValue >> (i * 8)) & 0xFF);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return buffer;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
Napi::Value FromPointer(const Napi::CallbackInfo &info) {
|
|
61
|
+
Napi::Env env = info.Env();
|
|
62
|
+
|
|
63
|
+
if (info.Length() != 1) {
|
|
64
|
+
throw Napi::TypeError::New(env, "Expected a single Buffer or BigInt argument");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
uintptr_t ptrValue = 0;
|
|
68
|
+
|
|
69
|
+
if (info[0].IsBuffer()) {
|
|
70
|
+
// Read pointer from Buffer
|
|
71
|
+
Napi::Buffer<uint8_t> buffer = info[0].As<Napi::Buffer<uint8_t>>();
|
|
72
|
+
if (buffer.Length() != sizeof(void*)) {
|
|
73
|
+
throw Napi::TypeError::New(env, "Buffer must be exactly 8 bytes for a 64-bit pointer");
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
uint8_t* data = buffer.Data();
|
|
77
|
+
for (size_t i = 0; i < sizeof(void*); ++i) {
|
|
78
|
+
ptrValue |= (static_cast<uintptr_t>(data[i]) << (i * 8));
|
|
79
|
+
}
|
|
80
|
+
} else if (info[0].IsBigInt()) {
|
|
81
|
+
// Read pointer from BigInt
|
|
82
|
+
bool lossless = false;
|
|
83
|
+
uint64_t value = info[0].As<Napi::BigInt>().Uint64Value(&lossless);
|
|
84
|
+
if (!lossless) {
|
|
85
|
+
throw Napi::RangeError::New(env, "BigInt value out of range for pointer");
|
|
86
|
+
}
|
|
87
|
+
ptrValue = static_cast<uintptr_t>(value);
|
|
88
|
+
} else {
|
|
89
|
+
throw Napi::TypeError::New(env, "Expected a Buffer or BigInt argument");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (ptrValue == 0) {
|
|
93
|
+
return env.Null();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Convert the pointer value back to an Objective-C object pointer
|
|
97
|
+
id obj = reinterpret_cast<id>(ptrValue);
|
|
98
|
+
|
|
99
|
+
return ObjcObject::NewInstance(env, obj);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
Napi::Object InitAll(Napi::Env env, Napi::Object exports) {
|
|
103
|
+
ObjcObject::Init(env, exports);
|
|
104
|
+
exports.Set("LoadLibrary", Napi::Function::New(env, LoadLibrary));
|
|
105
|
+
exports.Set("GetClassObject", Napi::Function::New(env, GetClassObject));
|
|
106
|
+
exports.Set("GetPointer", Napi::Function::New(env, GetPointer));
|
|
107
|
+
exports.Set("FromPointer", Napi::Function::New(env, FromPointer));
|
|
108
|
+
exports.Set("CreateProtocolImplementation",
|
|
109
|
+
Napi::Function::New(env, CreateProtocolImplementation));
|
|
110
|
+
return exports;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
NODE_API_MODULE(nobjc_native, InitAll)
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#ifndef PROTOCOL_IMPL_H
|
|
2
|
+
#define PROTOCOL_IMPL_H
|
|
3
|
+
|
|
4
|
+
#include <napi.h>
|
|
5
|
+
#include <objc/runtime.h>
|
|
6
|
+
#include <string>
|
|
7
|
+
#include <unordered_map>
|
|
8
|
+
#include <vector>
|
|
9
|
+
|
|
10
|
+
// Forward declarations for Objective-C types
|
|
11
|
+
#ifdef __OBJC__
|
|
12
|
+
@class NSMethodSignature;
|
|
13
|
+
@class NSInvocation;
|
|
14
|
+
#else
|
|
15
|
+
typedef struct NSMethodSignature NSMethodSignature;
|
|
16
|
+
typedef struct NSInvocation NSInvocation;
|
|
17
|
+
#endif
|
|
18
|
+
|
|
19
|
+
// MARK: - Data Structures
|
|
20
|
+
|
|
21
|
+
// Data passed from native thread to JS thread for invocation handling
|
|
22
|
+
struct InvocationData {
|
|
23
|
+
NSInvocation *invocation;
|
|
24
|
+
std::string selectorName;
|
|
25
|
+
std::string typeEncoding;
|
|
26
|
+
// The invocation itself stores the return value, so we don't need separate storage
|
|
27
|
+
// BlockingCall ensures the callback completes before returning, so no sync primitives needed
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// Stores information about a protocol implementation instance
|
|
31
|
+
struct ProtocolImplementation {
|
|
32
|
+
std::unordered_map<std::string, Napi::ThreadSafeFunction> callbacks;
|
|
33
|
+
std::unordered_map<std::string, Napi::FunctionReference> jsCallbacks; // Original JS functions for direct calls
|
|
34
|
+
std::unordered_map<std::string, std::string> typeEncodings;
|
|
35
|
+
std::string className;
|
|
36
|
+
napi_env env; // Store the environment for direct calls
|
|
37
|
+
pthread_t js_thread; // Store the JS thread ID
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// Global map: instance pointer -> implementation details
|
|
41
|
+
// This keeps JavaScript callbacks alive for the lifetime of the Objective-C object
|
|
42
|
+
extern std::unordered_map<void *, ProtocolImplementation> g_implementations;
|
|
43
|
+
|
|
44
|
+
// MARK: - Function Declarations
|
|
45
|
+
|
|
46
|
+
// Main entry point: creates a new Objective-C class that implements a protocol
|
|
47
|
+
Napi::Value CreateProtocolImplementation(const Napi::CallbackInfo &info);
|
|
48
|
+
|
|
49
|
+
// Override respondsToSelector to return YES for implemented methods
|
|
50
|
+
BOOL RespondsToSelector(id self, SEL _cmd, SEL selector);
|
|
51
|
+
|
|
52
|
+
// Method signature provider for message forwarding
|
|
53
|
+
NSMethodSignature* MethodSignatureForSelector(id self, SEL _cmd, SEL selector);
|
|
54
|
+
|
|
55
|
+
// Forward invocation handler for dynamic method dispatch
|
|
56
|
+
void ForwardInvocation(id self, SEL _cmd, NSInvocation *invocation);
|
|
57
|
+
|
|
58
|
+
// Helper: Parses an Objective-C method signature to extract argument types
|
|
59
|
+
std::vector<std::string> ParseMethodSignature(const char *typeEncoding);
|
|
60
|
+
|
|
61
|
+
// Helper: Converts an Objective-C value to a JavaScript value
|
|
62
|
+
Napi::Value ConvertObjCValueToJS(Napi::Env env, void *value,
|
|
63
|
+
const char *typeEncoding);
|
|
64
|
+
|
|
65
|
+
// Deallocation implementation to clean up when instance is destroyed
|
|
66
|
+
void DeallocImplementation(id self, SEL _cmd);
|
|
67
|
+
|
|
68
|
+
#endif // PROTOCOL_IMPL_H
|
|
69
|
+
|