Skip to content

Commit 9111dc1

Browse files
committed
Enhance NativeScript FFI with improved type conversions and compatibility features
- Added support for handling C strings as references in TypeConv. - Improved SInt64 and UInt64 type conversions to handle large integers using BigInt. - Enhanced PointerTypeConv to allow for void pointers and improved handling of struct types. - Introduced new utility functions for managing struct encodings and type definitions. - Implemented compatibility shims for UIColor on macOS to ensure consistent behavior. - Updated timer management in Timers.mm to use mutexes for thread safety and prevent memory leaks. - Enabled reference tests in the TestRunner to ensure proper functionality of reference types.
1 parent ccfe268 commit 9111dc1

12 files changed

Lines changed: 1254 additions & 109 deletions

File tree

NativeScript/ffi/CFunction.mm

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -81,14 +81,14 @@
8181

8282
#ifdef ENABLE_JS_RUNTIME
8383
if (strcmp(name, "UIApplicationMain") == 0 || strcmp(name, "NSApplicationMain") == 0) {
84-
void **avaluesPtr = new void*[cif->argc];
84+
void** avaluesPtr = new void*[cif->argc];
8585
memcpy(avaluesPtr, avalues, cif->argc * sizeof(void*));
8686

8787
Tasks::Register([env, cif, func, rvalue, avaluesPtr]() {
88-
void * avalues[cif->argc];
88+
void* avalues[cif->argc];
8989
memcpy(avalues, avaluesPtr, cif->argc * sizeof(void*));
9090
delete[] avaluesPtr;
91-
91+
9292
@try {
9393
ffi_call(&cif->cif, FFI_FN(func->fnptr), rvalue, avalues);
9494
} @catch (NSException* exception) {
@@ -99,7 +99,7 @@
9999
nativeScriptException.ReThrowToJS(env);
100100
}
101101
});
102-
102+
103103
return nullptr;
104104
}
105105
#endif
@@ -115,14 +115,26 @@
115115
}
116116

117117
if (shouldFreeAny) {
118+
void* returnPointerValue = nullptr;
119+
bool returnIsPointer = cif->returnType != nullptr && cif->returnType->type == &ffi_type_pointer;
120+
if (returnIsPointer && rvalue != nullptr) {
121+
returnPointerValue = *((void**)rvalue);
122+
}
123+
118124
for (unsigned int i = 0; i < cif->argc; i++) {
119125
if (shouldFree[i]) {
126+
if (returnPointerValue != nullptr && avalues[i] != nullptr) {
127+
void* argPointerValue = *((void**)avalues[i]);
128+
if (argPointerValue == returnPointerValue) {
129+
continue;
130+
}
131+
}
120132
cif->argTypes[i]->free(env, *((void**)avalues[i]));
121133
}
122134
}
123135
}
124136

125-
return cif->returnType->toJS(env, rvalue);
137+
return cif->returnType->toJS(env, rvalue, kCStringAsReference);
126138
}
127139

128140
CFunction::~CFunction() { cif = nullptr; }

NativeScript/ffi/Cif.mm

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@
100100
const char* argenc = [signature getArgumentTypeAtIndex:i];
101101

102102
auto argTypeInfo = TypeConv::Make(env, &argenc);
103-
this->atypes[i] = argTypeInfo->type;
103+
this->atypes[i] = argTypeInfo->ffiTypeForArgument();
104104

105105
if (i >= skippedArgs) {
106106
this->argTypes.push_back(argTypeInfo);
@@ -162,7 +162,7 @@
162162
free(argEnc);
163163
}
164164

165-
this->atypes[i] = argTypeInfo->type;
165+
this->atypes[i] = argTypeInfo->ffiTypeForArgument();
166166
if (i >= 2) {
167167
this->argTypes.push_back(argTypeInfo);
168168
}
@@ -226,7 +226,7 @@
226226
}
227227

228228
for (int i = 0; i < argc; i++) {
229-
atypes[i + implicitArgs] = argTypes[i]->type;
229+
atypes[i + implicitArgs] = argTypes[i]->ffiTypeForArgument();
230230
shouldFree[i] = false;
231231
}
232232
} else {

NativeScript/ffi/Class.mm

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include "Class.h"
22
#include "ClassBuilder.h"
33
#include "ClassMember.h"
4+
#include "Interop.h"
45
#include "Metadata.h"
56
#include "MetadataReader.h"
67
#include "ObjCBridge.h"
@@ -18,6 +19,49 @@
1819

1920
napi_value JS_NSObject_alloc(napi_env env, napi_callback_info cbinfo);
2021

22+
inline bool tryGetInteropPointerArg(napi_env env, napi_value value, void** out) {
23+
if (out == nullptr || value == nullptr) {
24+
return false;
25+
}
26+
27+
*out = nullptr;
28+
29+
napi_valuetype argType = napi_undefined;
30+
napi_typeof(env, value, &argType);
31+
32+
if (argType == napi_external) {
33+
return napi_get_value_external(env, value, out) == napi_ok && *out != nullptr;
34+
}
35+
36+
if (argType == napi_bigint) {
37+
uint64_t raw = 0;
38+
bool lossless = false;
39+
if (napi_get_value_bigint_uint64(env, value, &raw, &lossless) != napi_ok) {
40+
return false;
41+
}
42+
*out = reinterpret_cast<void*>(raw);
43+
return *out != nullptr;
44+
}
45+
46+
if (argType != napi_object) {
47+
return false;
48+
}
49+
50+
if (Pointer::isInstance(env, value)) {
51+
Pointer* ptr = Pointer::unwrap(env, value);
52+
*out = ptr != nullptr ? ptr->data : nullptr;
53+
return *out != nullptr;
54+
}
55+
56+
if (Reference::isInstance(env, value)) {
57+
Reference* ref = Reference::unwrap(env, value);
58+
*out = ref != nullptr ? ref->data : nullptr;
59+
return *out != nullptr;
60+
}
61+
62+
return false;
63+
}
64+
2165
void ObjCBridgeState::registerClassGlobals(napi_env env, napi_value global) {
2266
MDSectionOffset offset = metadata->classesOffset;
2367
while (offset < metadata->structsOffset) {
@@ -255,6 +299,27 @@ void setupObjCClassDecorator(napi_env env) {
255299
if (jsType == napi_external) {
256300
return jsThis;
257301
} else {
302+
// Backward compatibility: allow `new Class(pointer)` to wrap an existing
303+
// native instance instead of running initializer resolution.
304+
if (argc == 1 && argv[0] != nullptr) {
305+
void* rawPointer = nullptr;
306+
if (tryGetInteropPointerArg(env, argv[0], &rawPointer) && rawPointer != nullptr) {
307+
object = (id)rawPointer;
308+
napi_value constructor = nullptr;
309+
if (napi_get_named_property(env, jsThis, "constructor", &constructor) == napi_ok &&
310+
constructor != nullptr) {
311+
napi_value existing = bridgeState->getObject(env, object, constructor, kUnownedObject);
312+
if (existing != nullptr) {
313+
return existing;
314+
}
315+
}
316+
317+
jsThis = bridgeState->proxyNativeObject(env, jsThis, object);
318+
napi_wrap(env, jsThis, object, nullptr, nullptr, nullptr);
319+
return jsThis;
320+
}
321+
}
322+
258323
bool supercall = class_conformsToProtocol(cls, @protocol(ObjCBridgeClassBuilderProtocol));
259324

260325
if (supercall) {

NativeScript/ffi/Closure.mm

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ void JSBlockCallback(ffi_cif* cif, void* ret, void* args[], void* data) {
214214
for (int i = 0; i < argc; i++) {
215215
const char* argenc = [signature getArgumentTypeAtIndex:i];
216216
auto argTypeInfo = TypeConv::Make(env, &argenc);
217-
this->atypes[i + skipArgs] = argTypeInfo->type;
217+
this->atypes[i + skipArgs] = argTypeInfo->ffiTypeForArgument();
218218
this->argTypes.push_back(argTypeInfo);
219219
}
220220

@@ -228,10 +228,9 @@ void JSBlockCallback(ffi_cif* cif, void* ret, void* args[], void* data) {
228228

229229
closure = (ffi_closure*)ffi_closure_alloc(sizeof(ffi_closure), &fnptr);
230230

231-
ffi_prep_closure_loc(closure, &cif,
232-
isBlock ? JSBlockCallback
233-
: (isMethod ? JSMethodCallback : JSFunctionCallback),
234-
this, fnptr);
231+
ffi_prep_closure_loc(
232+
closure, &cif, isBlock ? JSBlockCallback : (isMethod ? JSMethodCallback : JSFunctionCallback),
233+
this, fnptr);
235234
}
236235

237236
Closure::Closure(MDMetadataReader* reader, MDSectionOffset offset, bool isBlock,
@@ -273,7 +272,7 @@ void JSBlockCallback(ffi_cif* cif, void* ret, void* args[], void* data) {
273272
this->atypes[0] = &ffi_type_pointer;
274273
}
275274
for (int i = 0; i < argTypes.size(); i++) {
276-
this->atypes[i + skipArgs] = argTypes[i]->type;
275+
this->atypes[i + skipArgs] = argTypes[i]->ffiTypeForArgument();
277276
}
278277
}
279278

@@ -286,10 +285,9 @@ void JSBlockCallback(ffi_cif* cif, void* ret, void* args[], void* data) {
286285

287286
closure = (ffi_closure*)ffi_closure_alloc(sizeof(ffi_closure), &fnptr);
288287

289-
ffi_prep_closure_loc(closure, &cif,
290-
isBlock ? JSBlockCallback
291-
: (isMethod ? JSMethodCallback : JSFunctionCallback),
292-
this, fnptr);
288+
ffi_prep_closure_loc(
289+
closure, &cif, isBlock ? JSBlockCallback : (isMethod ? JSMethodCallback : JSFunctionCallback),
290+
this, fnptr);
293291
}
294292

295293
Closure::~Closure() {

NativeScript/ffi/Enum.mm

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
#include "Enum.h"
22
#import <Foundation/Foundation.h>
3-
#include "ObjCBridge.h"
43
#include <algorithm>
54
#include <cstring>
65
#include <unordered_set>
76
#include <vector>
7+
#include "ObjCBridge.h"
88

99
namespace nativescript {
1010

1111
namespace {
1212
inline void defineConstantIfMissing(napi_env env, napi_value object, const std::string& name,
13-
napi_value value) {
13+
napi_value value,
14+
napi_property_attributes attributes = napi_enumerable) {
1415
bool hasProperty = false;
1516
napi_has_named_property(env, object, name.c_str(), &hasProperty);
1617
if (hasProperty) {
@@ -23,7 +24,7 @@ inline void defineConstantIfMissing(napi_env env, napi_value object, const std::
2324
.getter = nullptr,
2425
.setter = nullptr,
2526
.value = value,
26-
.attributes = napi_enumerable,
27+
.attributes = attributes,
2728
.data = nullptr,
2829
};
2930
napi_define_properties(env, object, 1, &prop);
@@ -35,8 +36,8 @@ inline bool startsWith(const std::string& value, const std::string& prefix) {
3536

3637
inline std::string stripEnumSuffix(const std::string& enumName) {
3738
static const std::vector<std::string> suffixes = {
38-
"Options", "Option", "Enums", "Enum", "Result", "Direction", "Orientation", "Style",
39-
"Mask", "Type", "Status", "Modes", "Mode", "s"};
39+
"Options", "Option", "Enums", "Enum", "Result", "Direction", "Orientation",
40+
"Style", "Mask", "Type", "Status", "Modes", "Mode", "s"};
4041

4142
for (const auto& suffix : suffixes) {
4243
if (enumName.size() > suffix.size() &&
@@ -48,7 +49,8 @@ inline bool startsWith(const std::string& value, const std::string& prefix) {
4849
return enumName;
4950
}
5051

51-
inline bool isNSComparisonResultOrderingName(const std::string& enumName, const std::string& member) {
52+
inline bool isNSComparisonResultOrderingName(const std::string& enumName,
53+
const std::string& member) {
5254
if (enumName != "NSComparisonResult") {
5355
return false;
5456
}
@@ -75,7 +77,8 @@ inline bool isNSComparisonResultOrderingName(const std::string& enumName, const
7577

7678
napi_value member;
7779
napi_create_int64(env, value, &member);
78-
defineConstantIfMissing(env, global, memberName, member);
80+
defineConstantIfMissing(env, global, memberName, member,
81+
(napi_property_attributes)(napi_enumerable | napi_configurable));
7982
}
8083

8184
napi_property_descriptor prop = {
@@ -169,7 +172,8 @@ inline bool isNSComparisonResultOrderingName(const std::string& enumName, const
169172

170173
for (const auto& alias : uniqueAliases) {
171174
defineConstantIfMissing(env, result, alias, member);
172-
defineConstantIfMissing(env, global, alias, member);
175+
defineConstantIfMissing(env, global, alias, member,
176+
(napi_property_attributes)(napi_enumerable | napi_configurable));
173177
}
174178

175179
std::string reverseCanonical = uniqueAliases.size() > 1 ? uniqueAliases[1] : memberName;

0 commit comments

Comments
 (0)