diff --git a/NativeScript/runtime/Caches.h b/NativeScript/runtime/Caches.h index 77de8715..ccd1a144 100644 --- a/NativeScript/runtime/Caches.h +++ b/NativeScript/runtime/Caches.h @@ -86,6 +86,7 @@ class Caches { std::unique_ptr> InteropReferenceCtorFunc = std::unique_ptr>(nullptr); std::unique_ptr> PointerCtorFunc = std::unique_ptr>(nullptr); std::unique_ptr> FunctionReferenceCtorFunc = std::unique_ptr>(nullptr); + std::unique_ptr> UnmanagedTypeCtorFunc = std::unique_ptr>(nullptr); private: static std::shared_ptr>> perIsolateCaches_; v8::Isolate* isolate_; diff --git a/NativeScript/runtime/DataWrapper.h b/NativeScript/runtime/DataWrapper.h index f085eca8..aec8933d 100644 --- a/NativeScript/runtime/DataWrapper.h +++ b/NativeScript/runtime/DataWrapper.h @@ -33,6 +33,7 @@ enum class WrapperType { FunctionReferenceType = 1 << 17, ExtVector = 1 << 18, Worker = 1 << 19, + UnmanagedType = 1 << 20, }; struct V8Args { @@ -403,6 +404,34 @@ class ObjCAllocDataWrapper: public BaseDataWrapper { Class klass_; }; +class UnmanagedTypeWrapper: public BaseDataWrapper { +public: + UnmanagedTypeWrapper(uint8_t* data, const TypeEncoding* typeEncoding) + : data_(data), typeEncoding_(typeEncoding), valueTaken_(false) { + } + + const WrapperType Type() { + return WrapperType::UnmanagedType; + } + + uint8_t* Data() { + this->valueTaken_ = true; + return this->data_; + } + + const TypeEncoding* TypeEncoding() { + return this->typeEncoding_; + } + + bool ValueTaken() { + return this->valueTaken_; + } +private: + uint8_t* data_; + const tns::TypeEncoding* typeEncoding_; + bool valueTaken_; +}; + class ObjCDataWrapper: public BaseDataWrapper { public: ObjCDataWrapper(id data, const TypeEncoding* typeEncoding = nullptr) diff --git a/NativeScript/runtime/Interop.h b/NativeScript/runtime/Interop.h index 8f764070..bf1863d4 100644 --- a/NativeScript/runtime/Interop.h +++ b/NativeScript/runtime/Interop.h @@ -26,6 +26,7 @@ struct MethodCall { MetaType metaType, bool provideErrorOutParameter, bool ownsReturnedObject, + bool returnsUnmanaged, bool isInitializer) : context_(context), isPrimitiveFunction_(isPrimitiveFunction), @@ -39,6 +40,7 @@ struct MethodCall { metaType_(metaType), provideErrorOutParameter_(provideErrorOutParameter), ownsReturnedObject_(ownsReturnedObject), + returnsUnmanaged_(returnsUnmanaged), isInitializer_(isInitializer) { } @@ -54,6 +56,7 @@ struct MethodCall { MetaType metaType_; bool provideErrorOutParameter_; bool ownsReturnedObject_; + bool returnsUnmanaged_; bool isInitializer_; }; @@ -63,7 +66,8 @@ struct CMethodCall: MethodCall { void* functionPointer, const TypeEncoding* typeEncoding, V8Args& args, - bool ownsReturnedObject) + bool ownsReturnedObject, + bool returnsUnmanaged) : MethodCall( context, true, @@ -77,6 +81,7 @@ struct CMethodCall: MethodCall { MetaType::Undefined, false, ownsReturnedObject, + returnsUnmanaged, false) { } }; @@ -102,6 +107,7 @@ struct ObjCMethodCall: public MethodCall { meta->type(), meta->hasErrorOutParameter() && args.Length() < meta->encodings()->count - 1, meta->ownsReturnedCocoaObject(), + false, meta->isInitializer()) { } }; @@ -113,7 +119,7 @@ class Interop { static id CallInitializer(v8::Local context, const MethodMeta* methodMeta, id target, Class clazz, V8Args& args); static v8::Local CallFunction(ObjCMethodCall& methodCall); static v8::Local CallFunction(CMethodCall& methodCall); - static v8::Local GetResult(v8::Local context, const TypeEncoding* typeEncoding, BaseCall* call, bool marshalToPrimitive, std::shared_ptr> parentStruct = nullptr, bool isStructMember = false, bool ownsReturnedObject = false, bool isInitializer = false); + static v8::Local GetResult(v8::Local context, const TypeEncoding* typeEncoding, BaseCall* call, bool marshalToPrimitive, std::shared_ptr> parentStruct = nullptr, bool isStructMember = false, bool ownsReturnedObject = false, bool returnsUnmanaged = false, bool isInitializer = false); static void SetStructPropertyValue(v8::Local context, StructWrapper* wrapper, StructField field, v8::Local value); static void InitializeStruct(v8::Local context, void* destBuffer, std::vector fields, v8::Local inititalizer); static void WriteValue(v8::Local context, const TypeEncoding* typeEncoding, void* dest, v8::Local arg); diff --git a/NativeScript/runtime/Interop.mm b/NativeScript/runtime/Interop.mm index b18c40d8..65a51acb 100644 --- a/NativeScript/runtime/Interop.mm +++ b/NativeScript/runtime/Interop.mm @@ -16,6 +16,7 @@ #include "Pointer.h" #include "ExtVector.h" #include "SymbolIterator.h" +#include "UnmanagedType.h" using namespace v8; @@ -715,9 +716,16 @@ *static_cast((void*)((uint8_t*)destBuffer + position)) = result; } -Local Interop::GetResult(Local context, const TypeEncoding* typeEncoding, BaseCall* call, bool marshalToPrimitive, std::shared_ptr> parentStruct, bool isStructMember, bool ownsReturnedObject, bool isInitializer) { +Local Interop::GetResult(Local context, const TypeEncoding* typeEncoding, BaseCall* call, bool marshalToPrimitive, std::shared_ptr> parentStruct, bool isStructMember, bool ownsReturnedObject, bool returnsUnmanaged, bool isInitializer) { Isolate* isolate = context->GetIsolate(); + if (returnsUnmanaged) { + uint8_t* data = call->GetResult(); + UnmanagedTypeWrapper* wrapper = new UnmanagedTypeWrapper(data, typeEncoding); + Local result = UnmanagedType::Create(context, wrapper); + return result; + } + if (typeEncoding->type == BinaryTypeEncodingType::ExtVectorEncoding) { ffi_type* ffiType = FFICall::GetArgumentType(typeEncoding, isStructMember); const TypeEncoding* innerTypeEncoding = typeEncoding->details.extVector.getInnerType(); @@ -909,7 +917,7 @@ const TypeEncoding* typeEncoding = wrapper->ParametersEncoding(); Local context = isolate->GetCurrentContext(); - CMethodCall methodCall(context, functionPointer, typeEncoding, args, false); + CMethodCall methodCall(context, functionPointer, typeEncoding, args, false, false); Local result = Interop::CallFunction(methodCall); info.GetReturnValue().Set(result); @@ -1406,7 +1414,16 @@ } } - Local result = Interop::GetResult(methodCall.context_, methodCall.typeEncoding_, &call, marshalToPrimitive, nullptr, false, methodCall.ownsReturnedObject_, methodCall.isInitializer_); + Local result = Interop::GetResult( + methodCall.context_, + methodCall.typeEncoding_, + &call, + marshalToPrimitive, + nullptr, + false, + methodCall.ownsReturnedObject_, + methodCall.returnsUnmanaged_, + methodCall.isInitializer_); return result; } diff --git a/NativeScript/runtime/MetadataBuilder.mm b/NativeScript/runtime/MetadataBuilder.mm index 157f099d..4a35bd43 100644 --- a/NativeScript/runtime/MetadataBuilder.mm +++ b/NativeScript/runtime/MetadataBuilder.mm @@ -841,7 +841,7 @@ V8VectorArgs vectorArgs(localArgs); Local context = Caches::Get(isolate)->GetContext(); v8::Unlocker unlocker(isolate); - CMethodCall methodCall(context, item->userData_, typeEncoding, vectorArgs, item->meta_->ownsReturnedCocoaObject()); + CMethodCall methodCall(context, item->userData_, typeEncoding, vectorArgs, item->meta_->ownsReturnedCocoaObject(), false); Interop::CallFunction(methodCall); }); @@ -851,7 +851,8 @@ V8FunctionCallbackArgs args(info); const TypeEncoding* typeEncoding = item->meta_->encodings()->first(); Local context = isolate->GetCurrentContext(); - CMethodCall methodCall(context, item->userData_, typeEncoding, args, item->meta_->ownsReturnedCocoaObject()); + const FunctionMeta* funcMeta = item->meta_; + CMethodCall methodCall(context, item->userData_, typeEncoding, args, funcMeta->ownsReturnedCocoaObject(), funcMeta->returnsUnmanaged()); Local result = Interop::CallFunction(methodCall); if (typeEncoding->type != BinaryTypeEncodingType::VoidEncoding) { diff --git a/NativeScript/runtime/ObjectManager.mm b/NativeScript/runtime/ObjectManager.mm index b79977e7..654a2b2d 100644 --- a/NativeScript/runtime/ObjectManager.mm +++ b/NativeScript/runtime/ObjectManager.mm @@ -97,6 +97,13 @@ } break; } + case WrapperType::UnmanagedType: { + UnmanagedTypeWrapper* unmanagedTypeWrapper = static_cast(wrapper); + if (unmanagedTypeWrapper != nullptr) { + delete unmanagedTypeWrapper; + } + break; + } case WrapperType::Block: { BlockWrapper* blockWrapper = static_cast(wrapper); std::free(blockWrapper->Block()); diff --git a/NativeScript/runtime/UnmanagedType.h b/NativeScript/runtime/UnmanagedType.h new file mode 100644 index 00000000..dbe5b9b7 --- /dev/null +++ b/NativeScript/runtime/UnmanagedType.h @@ -0,0 +1,21 @@ +#ifndef UnmanagedType_h +#define UnmanagedType_h + +#include "Common.h" +#include "DataWrapper.h" + +namespace tns { + +class UnmanagedType { +public: + static v8::Local Create(v8::Local context, UnmanagedTypeWrapper* wrapper); +private: + static void ConstructorCallback(const v8::FunctionCallbackInfo& info); + static void TakeUnretainedValueCallback(const v8::FunctionCallbackInfo& info); + static void TakeRetainedValueCallback(const v8::FunctionCallbackInfo& info); + static v8::Local TakeValue(const v8::FunctionCallbackInfo& info, bool retained); +}; + +} + +#endif /* UnmanagedType_h */ diff --git a/NativeScript/runtime/UnmanagedType.mm b/NativeScript/runtime/UnmanagedType.mm new file mode 100644 index 00000000..38d0d3ad --- /dev/null +++ b/NativeScript/runtime/UnmanagedType.mm @@ -0,0 +1,89 @@ +#include "UnmanagedType.h" +#include "NativeScriptException.h" +#include "Caches.h" +#include "Helpers.h" +#include "Interop.h" + +using namespace v8; + +namespace tns { + +Local UnmanagedType::Create(Local context, UnmanagedTypeWrapper* wrapper) { + Isolate* isolate = context->GetIsolate(); + auto cache = Caches::Get(isolate); + if (cache->UnmanagedTypeCtorFunc.get() == nullptr) { + Local ctorFuncTemplate = FunctionTemplate::New(isolate, ConstructorCallback); + ctorFuncTemplate->SetClassName(tns::ToV8String(isolate, "Unmanaged")); + Local proto = ctorFuncTemplate->PrototypeTemplate(); + + Local takeUnretainedValueFuncTemplate = FunctionTemplate::New(isolate, UnmanagedType::TakeUnretainedValueCallback); + Local takeRetainedValueFuncTemplate = FunctionTemplate::New(isolate, UnmanagedType::TakeRetainedValueCallback); + proto->Set(tns::ToV8String(isolate, "takeUnretainedValue"), takeUnretainedValueFuncTemplate); + proto->Set(tns::ToV8String(isolate, "takeRetainedValue"), takeRetainedValueFuncTemplate); + + Local ctorFunc; + bool success = ctorFuncTemplate->GetFunction(context).ToLocal(&ctorFunc); + tns::Assert(success, isolate); + + cache->UnmanagedTypeCtorFunc = std::make_unique>(isolate, ctorFunc); + } + + Local ext = External::New(isolate, wrapper); + + Local ctorFunc = cache->UnmanagedTypeCtorFunc->Get(isolate); + Local result; + Local args[] = { ext }; + bool success = ctorFunc->NewInstance(context, 1, args).ToLocal(&result); + tns::Assert(success, isolate); + + return result; +} + +void UnmanagedType::ConstructorCallback(const FunctionCallbackInfo& info) { + Local ext = info[0].As(); + UnmanagedTypeWrapper* wrapper = static_cast(ext->Value()); + tns::SetValue(info.GetIsolate(), info.This(), wrapper); +} + +void UnmanagedType::TakeUnretainedValueCallback(const FunctionCallbackInfo& info) { + try { + info.GetReturnValue().Set(UnmanagedType::TakeValue(info, false)); + } catch (NativeScriptException& ex) { + ex.ReThrowToV8(info.GetIsolate()); + } +} + +void UnmanagedType::TakeRetainedValueCallback(const FunctionCallbackInfo& info) { + try { + info.GetReturnValue().Set(UnmanagedType::TakeValue(info, true)); + } catch (NativeScriptException& ex) { + ex.ReThrowToV8(info.GetIsolate()); + } +} + +Local UnmanagedType::TakeValue(const FunctionCallbackInfo& info, bool retained) { + Isolate* isolate = info.GetIsolate(); + Local context = isolate->GetCurrentContext(); + + BaseDataWrapper* baseWrapper = tns::GetValue(isolate, info.This()); + UnmanagedTypeWrapper* wrapper = static_cast(baseWrapper); + + if (wrapper->ValueTaken()) { + throw NativeScriptException("Unmanaged value has already been consumed."); + } + + uint8_t* data = wrapper->Data(); + const TypeEncoding* typeEncoding = wrapper->TypeEncoding(); + + BaseCall call((uint8_t*)&data); + Local result = Interop::GetResult(context, typeEncoding, &call, false); + + if (retained) { + id value = static_cast((void*)data); + [value release]; + } + + return result; +} + +} diff --git a/TestRunner/app/tests/ApiTests.js b/TestRunner/app/tests/ApiTests.js index b881ae7f..d307e004 100644 --- a/TestRunner/app/tests/ApiTests.js +++ b/TestRunner/app/tests/ApiTests.js @@ -584,35 +584,35 @@ describe(module.id, function () { expect(TNSMutableObjectGet() instanceof NSObject).toBe(true); }); -// it("returns retained", function () { -// expect(functionReturnsNSRetained().retainCount()).toBe(1); -// expect(functionReturnsCFRetained().retainCount()).toBe(1); -// expect(functionImplicitCreate().retainCount()).toBe(1); - -// var obj = functionExplicitCreateNSObject(); -// expect(obj.retainCount()).toBe(2); -// CFRelease(obj); - -// expect(TNSReturnsRetained.methodReturnsNSRetained().retainCount()).toBe(1); -// expect(TNSReturnsRetained.methodReturnsCFRetained().retainCount()).toBe(1); -// expect(TNSReturnsRetained.newNSObjectMethod().retainCount()).toBe(1); -// }); + it("returns retained", function () { + expect(functionReturnsNSRetained().retainCount()).toBe(1); + expect(functionReturnsCFRetained().retainCount()).toBe(1); + expect(functionImplicitCreate().retainCount()).toBe(1); + + var obj = functionExplicitCreateNSObject(); + expect(obj.retainCount()).toBe(2); + CFRelease(obj); + + expect(TNSReturnsRetained.methodReturnsNSRetained().retainCount()).toBe(1); + expect(TNSReturnsRetained.methodReturnsCFRetained().retainCount()).toBe(1); + expect(TNSReturnsRetained.newNSObjectMethod().retainCount()).toBe(1); + }); -// it("unmanaged", function () { -// var unmanaged = functionReturnsUnmanaged(); -// expect('takeRetainedValue' in unmanaged).toBe(true); -// expect('takeUnretainedValue' in unmanaged).toBe(true); -// expect(functionReturnsUnmanaged().takeRetainedValue().retainCount()).toBe(1); + it("unmanaged", function () { + var unmanaged = functionReturnsUnmanaged(); + expect('takeRetainedValue' in unmanaged).toBe(true); + expect('takeUnretainedValue' in unmanaged).toBe(true); + expect(functionReturnsUnmanaged().takeRetainedValue().retainCount()).toBe(1); -// var value = functionReturnsUnmanaged().takeUnretainedValue(); -// expect(value.retainCount()).toBe(2); -// CFRelease(value); + var value = functionReturnsUnmanaged().takeUnretainedValue(); + expect(value.retainCount()).toBe(2); + CFRelease(value); -// unmanaged.takeRetainedValue(); -// expect(function() { -// unmanaged.takeUnretainedValue(); -// }).toThrow(); -// }); + unmanaged.takeRetainedValue(); + expect(function() { + unmanaged.takeUnretainedValue(); + }).toThrow(); + }); it('methods can be recursively called', function() { var result = TNSTestNativeCallbacks.callRecursively(function() { diff --git a/v8ios.xcodeproj/project.pbxproj b/v8ios.xcodeproj/project.pbxproj index 24bf0dba..c6f2b1f8 100644 --- a/v8ios.xcodeproj/project.pbxproj +++ b/v8ios.xcodeproj/project.pbxproj @@ -213,6 +213,8 @@ C266569E22B282BA00EE15CC /* FunctionReference.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C266569C22B282BA00EE15CC /* FunctionReference.cpp */; }; C266569F22B282BA00EE15CC /* FunctionReference.h in Headers */ = {isa = PBXBuildFile; fileRef = C266569D22B282BA00EE15CC /* FunctionReference.h */; }; C26656B322B3768C00EE15CC /* InteropTypes.mm in Sources */ = {isa = PBXBuildFile; fileRef = C26656B222B3768C00EE15CC /* InteropTypes.mm */; }; + C275F477253B37AB00A997D5 /* UnmanagedType.mm in Sources */ = {isa = PBXBuildFile; fileRef = C275F475253B37AB00A997D5 /* UnmanagedType.mm */; }; + C275F478253B37AB00A997D5 /* UnmanagedType.h in Headers */ = {isa = PBXBuildFile; fileRef = C275F476253B37AB00A997D5 /* UnmanagedType.h */; }; C27E5D8522F2FDDB00498ED0 /* KnownUnknownClassPair.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C27E5D8322F2FDDB00498ED0 /* KnownUnknownClassPair.cpp */; }; C27E5D8622F2FDDB00498ED0 /* KnownUnknownClassPair.h in Headers */ = {isa = PBXBuildFile; fileRef = C27E5D8422F2FDDB00498ED0 /* KnownUnknownClassPair.h */; }; C27E5DA922F31F3D00498ED0 /* NativeScript.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C2DDEB32229EAB3B00345BFE /* NativeScript.framework */; }; @@ -738,6 +740,8 @@ C266569C22B282BA00EE15CC /* FunctionReference.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = FunctionReference.cpp; sourceTree = ""; }; C266569D22B282BA00EE15CC /* FunctionReference.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FunctionReference.h; sourceTree = ""; }; C26656B222B3768C00EE15CC /* InteropTypes.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = InteropTypes.mm; sourceTree = ""; }; + C275F475253B37AB00A997D5 /* UnmanagedType.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = UnmanagedType.mm; sourceTree = ""; }; + C275F476253B37AB00A997D5 /* UnmanagedType.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = UnmanagedType.h; sourceTree = ""; }; C27E5D8322F2FDDB00498ED0 /* KnownUnknownClassPair.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = KnownUnknownClassPair.cpp; sourceTree = ""; }; C27E5D8422F2FDDB00498ED0 /* KnownUnknownClassPair.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KnownUnknownClassPair.h; sourceTree = ""; }; C27E5D8B22F31CC900498ED0 /* AppWithModules.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AppWithModules.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -1888,6 +1892,8 @@ C23E8F7422CDE88D0078FD4C /* WorkerWrapper.mm */, C2D215FF248AAAD900EDC646 /* Constants.h */, C2D215FE248AAAD900EDC646 /* Constants.cpp */, + C275F476253B37AB00A997D5 /* UnmanagedType.h */, + C275F475253B37AB00A997D5 /* UnmanagedType.mm */, ); path = runtime; sourceTree = ""; @@ -2004,6 +2010,7 @@ C2B30FDB238418CA009D6A46 /* status.h in Headers */, C247C36D22F828E3001D2CA2 /* HeapProfiler.h in Headers */, C247C35122F828E3001D2CA2 /* v8-inspector-impl.h in Headers */, + C275F478253B37AB00A997D5 /* UnmanagedType.h in Headers */, C247C39B22F828E3001D2CA2 /* address-region.h in Headers */, C247C33922F828E3001D2CA2 /* trace-event.h in Headers */, C2229974235449B400C1DFD6 /* InspectorServer.h in Headers */, @@ -2650,6 +2657,7 @@ C247C34822F828E3001D2CA2 /* custom-preview.cc in Sources */, C247C35D22F828E3001D2CA2 /* CSS.cpp in Sources */, C2D7E9D623F42C1100DB289C /* PromiseProxy.cpp in Sources */, + C275F477253B37AB00A997D5 /* UnmanagedType.mm in Sources */, C266569E22B282BA00EE15CC /* FunctionReference.cpp in Sources */, C247C36222F828E3001D2CA2 /* Page.cpp in Sources */, C2229973235449B400C1DFD6 /* InspectorServer.mm in Sources */,