Skip to content

Commit

Permalink
feat: add interop.stringFromCString (#228)
Browse files Browse the repository at this point in the history
Convert a char* into a JS string (~10x faster than doing NSString.stringWithUTF8String().toString())
  • Loading branch information
edusperoni authored Oct 6, 2023
1 parent d67588c commit 185c12d
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 0 deletions.
1 change: 1 addition & 0 deletions NativeScript/runtime/Interop.h
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ class Interop {
static void InitializeStruct(v8::Local<v8::Context> context, void* destBuffer, std::vector<StructField> fields, v8::Local<v8::Value> inititalizer, ptrdiff_t& position);
static void RegisterInteropType(v8::Local<v8::Context> context, v8::Local<v8::Object> types, std::string name, PrimitiveDataWrapper* wrapper, bool autoDelete = true);
static void RegisterBufferFromDataFunction(v8::Local<v8::Context> context, v8::Local<v8::Object> interop);
static void RegisterStringFromCString(v8::Local<v8::Context> context, v8::Local<v8::Object> interop);
static void RegisterHandleOfFunction(v8::Local<v8::Context> context, v8::Local<v8::Object> interop);
static void RegisterAllocFunction(v8::Local<v8::Context> context, v8::Local<v8::Object> interop);
static void RegisterFreeFunction(v8::Local<v8::Context> context, v8::Local<v8::Object> interop);
Expand Down
55 changes: 55 additions & 0 deletions NativeScript/runtime/InteropTypes.mm
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
Pointer::Register(context, interop);
FunctionReference::Register(context, interop);
RegisterBufferFromDataFunction(context, interop);
RegisterStringFromCString(context, interop);
RegisterHandleOfFunction(context, interop);
RegisterAllocFunction(context, interop);
RegisterFreeFunction(context, interop);
Expand Down Expand Up @@ -140,6 +141,60 @@
tns::Assert(success, isolate);
}

void Interop::RegisterStringFromCString(Local<Context> context, Local<Object> interop) {
Local<v8::Function> func;
bool success = v8::Function::New(context, [](const FunctionCallbackInfo<Value>& info) {
Isolate* isolate = info.GetIsolate();
tns::Assert(info.Length() >= 1 && info[0]->IsObject(), isolate);
Local<Object> arg = info[0].As<Object>();
int stringLength = -1;
if(info.Length() >= 2 && !info[1].IsEmpty() && !info[1]->IsNullOrUndefined()) {
auto desiredLength = ToNumber(isolate, info[1]);
if (desiredLength != NAN) {
stringLength = desiredLength;
}
}
tns::Assert(arg->InternalFieldCount() > 0 && arg->GetInternalField(0)->IsExternal(), isolate);

Local<External> ext = arg->GetInternalField(0).As<External>();
BaseDataWrapper* wrapper = static_cast<BaseDataWrapper*>(ext->Value());
tns::Assert(wrapper != nullptr);
char* data = nullptr;
switch (wrapper->Type()) {
case WrapperType::Pointer:
{
PointerWrapper* pointerWrapper = static_cast<PointerWrapper*>(wrapper);
data = static_cast<char*>(pointerWrapper->Data());
}
break;
case WrapperType::Reference:
{
ReferenceWrapper* referenceWrapper = static_cast<ReferenceWrapper*>(wrapper);
if (referenceWrapper->Data() != nullptr) {
data = static_cast<char*>(referenceWrapper->Data());
break;
}
auto wrappedValue = referenceWrapper->Value()->Get(isolate);
auto wrappedWrapper = tns::GetValue(isolate, wrappedValue);
tns::Assert(wrappedWrapper->Type() == WrapperType::Pointer);
data = static_cast<char*>((static_cast<PointerWrapper*>(wrappedWrapper))->Data());
}
default:
break;
}
tns::Assert(data != nullptr);

auto result = v8::String::NewFromUtf8(isolate, data, v8::NewStringType::kNormal, stringLength).ToLocalChecked();
info.GetReturnValue().Set(result);
}).ToLocal(&func);

Isolate* isolate = context->GetIsolate();
tns::Assert(success, isolate);

success = interop->Set(context, tns::ToV8String(isolate, "stringFromCString"), func).FromMaybe(false);
tns::Assert(success, isolate);
}

void Interop::RegisterHandleOfFunction(Local<Context> context, Local<Object> interop) {
Local<v8::Function> func;
bool success = v8::Function::New(context, [](const FunctionCallbackInfo<Value>& info) {
Expand Down
32 changes: 32 additions & 0 deletions TestRunner/app/tests/Marshalling/ReferenceTests.js
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,38 @@ describe(module.id, function () {
expect(TNSGetOutput()).toBe(str);
expect(interop.handleof(result).toNumber() == interop.handleof(ptr).toNumber());
expect(NSString.stringWithUTF8String(result).toString()).toBe(str);
interop.free(ptr);
});

it("interops string from CString", function () {
const str = "test";
const ptr = interop.alloc((str.length + 1) * interop.sizeof(interop.types.uint8));
var reference = new interop.Reference(interop.types.uint8, ptr);
for (ii in str) {
const i = parseInt(ii);
reference[i] = str.charCodeAt(i);
}
reference[str.length] = 0;
expect(interop.stringFromCString(ptr)).toBe(str);
expect(interop.stringFromCString(reference)).toBe(str);
interop.free(ptr);
});

it("interops string from CString with fixed length", function () {
const str = "te\0st";
const ptr = interop.alloc((str.length + 1) * interop.sizeof(interop.types.uint8));
var reference = new interop.Reference(interop.types.uint8, ptr);
for (ii in str) {
const i = parseInt(ii);
reference[i] = str.charCodeAt(i);
}
reference[str.length] = 0;
// no length means it will go until it finds \0
expect(interop.stringFromCString(ptr)).toBe('te');
expect(interop.stringFromCString(ptr, 1)).toBe('t');
expect(interop.stringFromCString(ptr, str.length)).toBe(str);
expect(interop.stringFromCString(reference, str.length)).toBe(str);
interop.free(ptr);
});

it("CString should be passed as its UTF8 encoding and returned as a reference to unsigned characters", function () {
Expand Down

0 comments on commit 185c12d

Please sign in to comment.