#include "edge.h" #using ClrFunc::ClrFunc() { // empty } NAN_METHOD(clrFuncProxy) { DBG("clrFuncProxy"); NanEscapableScope(); Handle correlator = Handle::Cast(args[2]); ClrFuncWrap* wrap = (ClrFuncWrap*)(correlator->Value()); ClrFunc^ clrFunc = wrap->clrFunc; NanReturnValue(clrFunc->Call(args[0], args[1])); } NAN_WEAK_CALLBACK(clrFuncProxyNearDeath) { DBG("clrFuncProxyNearDeath"); ClrFuncWrap* wrap = (ClrFuncWrap*)(data.GetParameter()); wrap->clrFunc = nullptr; delete wrap; } Handle ClrFunc::Initialize(System::Func^>^ func) { DBG("ClrFunc::Initialize Func> wrapper"); static Persistent proxyFactory; static Persistent proxyFunction; NanEscapableScope(); ClrFunc^ app = gcnew ClrFunc(); app->func = func; ClrFuncWrap* wrap = new ClrFuncWrap; wrap->clrFunc = app; // See https://github.com/tjanczuk/edge/issues/128 for context if (proxyFactory.IsEmpty()) { NanAssignPersistent( proxyFunction, NanNew(clrFuncProxy)->GetFunction()); Handle code = NanNew( "(function (f, ctx) { return function (d, cb) { return f(d, cb, ctx); }; })"); NanAssignPersistent( proxyFactory, Handle::Cast(v8::Script::Compile(code)->Run())); } Handle factoryArgv[] = { NanNew(proxyFunction), NanNew((void*)wrap) }; Local funcProxy = (NanNew(proxyFactory)->Call(NanGetCurrentContext()->Global(), 2, factoryArgv)).As(); NanMakeWeakPersistent(funcProxy, (void*)wrap, &clrFuncProxyNearDeath); return NanEscapeScope(funcProxy); } NAN_METHOD(ClrFunc::Initialize) { DBG("ClrFunc::Initialize MethodInfo wrapper"); NanEscapableScope(); Handle options = args[0]->ToObject(); Assembly^ assembly; System::String^ typeName; System::String^ methodName; try { Handle result; Handle jsassemblyFile = options->Get(NanNew("assemblyFile")); if (jsassemblyFile->IsString()) { // reference .NET code through pre-compiled CLR assembly String::Utf8Value assemblyFile(jsassemblyFile); String::Utf8Value nativeTypeName(options->Get(NanNew("typeName"))); String::Utf8Value nativeMethodName(options->Get(NanNew("methodName"))); typeName = gcnew System::String(*nativeTypeName); methodName = gcnew System::String(*nativeMethodName); assembly = Assembly::UnsafeLoadFrom(gcnew System::String(*assemblyFile)); ClrFuncReflectionWrap^ wrap = ClrFuncReflectionWrap::Create(assembly, typeName, methodName); result = ClrFunc::Initialize( gcnew System::Func^>( wrap, &ClrFuncReflectionWrap::Call)); } else { // reference .NET code throgh embedded source code that needs to be compiled String::Value compilerFile(options->Get(NanNew("compiler"))); cli::array^ buffer = gcnew cli::array(compilerFile.length() * 2); for (int k = 0; k < compilerFile.length(); k++) { buffer[k * 2] = (*compilerFile)[k] & 255; buffer[k * 2 + 1] = (*compilerFile)[k] >> 8; } assembly = Assembly::UnsafeLoadFrom(System::Text::Encoding::Unicode->GetString(buffer)); System::Type^ compilerType = assembly->GetType("EdgeCompiler", true, true); System::Object^ compilerInstance = System::Activator::CreateInstance(compilerType, false); MethodInfo^ compileFunc = compilerType->GetMethod("CompileFunc", BindingFlags::Instance | BindingFlags::Public); if (compileFunc == nullptr) { throw gcnew System::InvalidOperationException( "Unable to access the CompileFunc method of the EdgeCompiler class in the edge.js compiler assembly."); } System::Object^ parameters = ClrFunc::MarshalV8ToCLR(options); System::Func^>^ func = (System::Func^>^)compileFunc->Invoke( compilerInstance, gcnew array { parameters }); result = ClrFunc::Initialize(func); } NanReturnValue(result); } catch (System::Exception^ e) { throwV8Exception(ClrFunc::MarshalCLRExceptionToV8(e)); NanReturnValue(NanUndefined()); } } void edgeAppCompletedOnCLRThread(Task^ task, System::Object^ state) { DBG("edgeAppCompletedOnCLRThread"); ClrFuncInvokeContext^ context = (ClrFuncInvokeContext^)state; context->CompleteOnCLRThread(task); } Handle ClrFunc::MarshalCLRToV8(System::Object^ netdata) { NanEscapableScope(); Handle jsdata; if (netdata == nullptr) { return NanEscapeScope(NanNull()); } System::Type^ type = netdata->GetType(); if (type == System::String::typeid) { jsdata = stringCLR2V8((System::String^)netdata); } else if (type == System::Char::typeid) { jsdata = stringCLR2V8(((System::Char^)netdata)->ToString()); } else if (type == bool::typeid) { jsdata = NanNew((bool)netdata); } else if (type == System::Guid::typeid) { jsdata = stringCLR2V8(netdata->ToString()); } else if (type == System::DateTime::typeid) { System::DateTime ^dt = (System::DateTime^)netdata; if (dt->Kind == System::DateTimeKind::Local) dt = dt->ToUniversalTime(); else if (dt->Kind == System::DateTimeKind::Unspecified) dt = gcnew System::DateTime(dt->Ticks, System::DateTimeKind::Utc); long long MinDateTimeTicks = 621355968000000000; // new DateTime(1970, 1, 1, 0, 0, 0).Ticks; long long value = ((dt->Ticks - MinDateTimeTicks) / 10000); jsdata = NanNew((double)value); } else if (type == System::DateTimeOffset::typeid) { jsdata = stringCLR2V8(netdata->ToString()); } else if (type == System::Uri::typeid) { jsdata = stringCLR2V8(netdata->ToString()); } else if (type == int::typeid) { jsdata = NanNew((int)netdata); } else if (type == System::Int16::typeid) { jsdata = NanNew((int)netdata); } else if (type == System::Int64::typeid) { jsdata = NanNew(((System::IConvertible^)netdata)->ToDouble(nullptr)); } else if (type == double::typeid) { jsdata = NanNew((double)netdata); } else if (type == float::typeid) { jsdata = NanNew((float)netdata); } else if (type->IsPrimitive || type == System::Decimal::typeid) { System::IConvertible^ convertible = dynamic_cast(netdata); if (convertible != nullptr) { jsdata = stringCLR2V8(convertible->ToString()); } else { jsdata = stringCLR2V8(netdata->ToString()); } } else if (type->IsEnum) { jsdata = stringCLR2V8(netdata->ToString()); } else if (type == cli::array::typeid) { cli::array^ buffer = (cli::array^)netdata; if (buffer->Length > 0) { pin_ptr pinnedBuffer = &buffer[0]; jsdata = NanNewBufferHandle((char *)pinnedBuffer, buffer->Length); } else { jsdata = NanNewBufferHandle(0); } } else if (dynamic_cast^>(netdata) != nullptr) { Handle result = NanNew(); for each (System::Collections::Generic::KeyValuePair^ pair in (System::Collections::Generic::IDictionary^)netdata) { result->Set(stringCLR2V8(pair->Key), ClrFunc::MarshalCLRToV8(pair->Value)); } jsdata = result; } else if (dynamic_cast(netdata) != nullptr) { Handle result = NanNew(); for each (System::Collections::DictionaryEntry^ entry in (System::Collections::IDictionary^)netdata) { if (dynamic_cast(entry->Key) != nullptr) result->Set(stringCLR2V8((System::String^)entry->Key), ClrFunc::MarshalCLRToV8(entry->Value)); } jsdata = result; } else if (dynamic_cast(netdata) != nullptr) { Handle result = NanNew(); unsigned int i = 0; for each (System::Object^ entry in (System::Collections::IEnumerable^)netdata) { result->Set(i++, ClrFunc::MarshalCLRToV8(entry)); } jsdata = result; } else if (type == System::Func^>::typeid) { jsdata = ClrFunc::Initialize((System::Func^>^)netdata); } else if (System::Exception::typeid->IsAssignableFrom(type)) { jsdata = ClrFunc::MarshalCLRExceptionToV8((System::Exception^)netdata); } else { jsdata = ClrFunc::MarshalCLRObjectToV8(netdata); } return NanEscapeScope(jsdata); } Handle ClrFunc::MarshalCLRExceptionToV8(System::Exception^ exception) { DBG("ClrFunc::MarshalCLRExceptionToV8"); NanEscapableScope(); Handle result; Handle message; Handle name; if (exception == nullptr) { result = NanNew(); message = NanNew("Unrecognized exception thrown by CLR."); name = NanNew("InternalException"); } else { // Remove AggregateException wrapper from around singleton InnerExceptions if (System::AggregateException::typeid->IsAssignableFrom(exception->GetType())) { System::AggregateException^ aggregate = (System::AggregateException^)exception; if (aggregate->InnerExceptions->Count == 1) exception = aggregate->InnerExceptions[0]; } else if (System::Reflection::TargetInvocationException::typeid->IsAssignableFrom(exception->GetType()) && exception->InnerException != nullptr) { exception = exception->InnerException; } result = ClrFunc::MarshalCLRObjectToV8(exception); message = stringCLR2V8(exception->Message); name = stringCLR2V8(exception->GetType()->FullName); } // Construct an error that is just used for the prototype - not verify efficient // but 'typeof Error' should work in JavaScript result->SetPrototype(v8::Exception::Error(message)); result->Set(NanNew("message"), message); // Recording the actual type - 'name' seems to be the common used property result->Set(NanNew("name"), name); return NanEscapeScope(result); } Handle ClrFunc::MarshalCLRObjectToV8(System::Object^ netdata) { DBG("ClrFunc::MarshalCLRObjectToV8"); NanEscapableScope(); Handle result = NanNew(); System::Type^ type = netdata->GetType(); if (0 == System::String::Compare(type->FullName, "System.Reflection.RuntimeMethodInfo")) { // Avoid stack overflow due to self-referencing reflection elements return NanEscapeScope(result); } for each (FieldInfo^ field in type->GetFields(BindingFlags::Public | BindingFlags::Instance)) { result->Set( stringCLR2V8(field->Name), ClrFunc::MarshalCLRToV8(field->GetValue(netdata))); } for each (PropertyInfo^ property in type->GetProperties(BindingFlags::GetProperty | BindingFlags::Public | BindingFlags::Instance)) { if (enableScriptIgnoreAttribute) { if (property->IsDefined(System::Web::Script::Serialization::ScriptIgnoreAttribute::typeid, true)) { continue; } System::Web::Script::Serialization::ScriptIgnoreAttribute^ attr = (System::Web::Script::Serialization::ScriptIgnoreAttribute^)System::Attribute::GetCustomAttribute( property, System::Web::Script::Serialization::ScriptIgnoreAttribute::typeid, true); if (attr != nullptr && attr->ApplyToOverrides) { continue; } } MethodInfo^ getMethod = property->GetGetMethod(); if (getMethod != nullptr && getMethod->GetParameters()->Length <= 0) { result->Set( stringCLR2V8(property->Name), ClrFunc::MarshalCLRToV8(getMethod->Invoke(netdata, nullptr))); } } return NanEscapeScope(result); } System::Object^ ClrFunc::MarshalV8ToCLR(Handle jsdata) { NanScope(); if (jsdata->IsFunction()) { NodejsFunc^ functionContext = gcnew NodejsFunc(Handle::Cast(jsdata)); System::Func^>^ netfunc = gcnew System::Func^>( functionContext, &NodejsFunc::FunctionWrapper); return netfunc; } else if (node::Buffer::HasInstance(jsdata)) { Handle jsbuffer = jsdata->ToObject(); cli::array^ netbuffer = gcnew cli::array((int)node::Buffer::Length(jsbuffer)); if (netbuffer->Length > 0) { pin_ptr pinnedNetbuffer = &netbuffer[0]; memcpy(pinnedNetbuffer, node::Buffer::Data(jsbuffer), netbuffer->Length); } return netbuffer; } else if (jsdata->IsArray()) { Handle jsarray = Handle::Cast(jsdata); cli::array^ netarray = gcnew cli::array(jsarray->Length()); for (unsigned int i = 0; i < jsarray->Length(); i++) { netarray[i] = ClrFunc::MarshalV8ToCLR(jsarray->Get(i)); } return netarray; } else if (jsdata->IsDate()) { Handle jsdate = Handle::Cast(jsdata); long long ticks = (long long)jsdate->NumberValue(); long long MinDateTimeTicks = 621355968000000000;// (new DateTime(1970, 1, 1, 0, 0, 0)).Ticks; System::DateTime ^netobject = gcnew System::DateTime(ticks * 10000 + MinDateTimeTicks, System::DateTimeKind::Utc); return netobject; } else if (jsdata->IsObject()) { IDictionary^ netobject = gcnew System::Dynamic::ExpandoObject(); Handle jsobject = Handle::Cast(jsdata); Handle propertyNames = jsobject->GetPropertyNames(); for (unsigned int i = 0; i < propertyNames->Length(); i++) { Handle name = Handle::Cast(propertyNames->Get(i)); String::Utf8Value utf8name(name); System::String^ netname = gcnew System::String(*utf8name); System::Object^ netvalue = ClrFunc::MarshalV8ToCLR(jsobject->Get(name)); netobject->Add(netname, netvalue); } return netobject; } else if (jsdata->IsString()) { return stringV82CLR(Handle::Cast(jsdata)); } else if (jsdata->IsBoolean()) { return jsdata->BooleanValue(); } else if (jsdata->IsInt32()) { return jsdata->Int32Value(); } else if (jsdata->IsUint32()) { return jsdata->Uint32Value(); } else if (jsdata->IsNumber()) { return jsdata->NumberValue(); } else if (jsdata->IsUndefined() || jsdata->IsNull()) { return nullptr; } else { throw gcnew System::Exception("Unable to convert V8 value to CLR value."); } } Handle ClrFunc::Call(Handle payload, Handle callback) { DBG("ClrFunc::Call instance"); NanEscapableScope(); try { ClrFuncInvokeContext^ context = gcnew ClrFuncInvokeContext(callback); context->Payload = ClrFunc::MarshalV8ToCLR(payload); Task^ task = this->func(context->Payload); if (task->IsCompleted) { // Completed synchronously. Return a value or invoke callback based on call pattern. context->Task = task; return NanEscapeScope(context->CompleteOnV8Thread()); } else if (context->Sync) { // Will complete asynchronously but was called as a synchronous function. throw gcnew System::InvalidOperationException("The JavaScript function was called synchronously " + "but the underlying CLR function returned without completing the Task. Call the " + "JavaScript function asynchronously."); } else { // Create a GC root around the ClrFuncInvokeContext to ensure it is not garbage collected // while the CLR function executes asynchronously. context->InitializeAsyncOperation(); // Will complete asynchronously. Schedule continuation to finish processing. task->ContinueWith(gcnew System::Action^,System::Object^>( edgeAppCompletedOnCLRThread), context); } } catch (System::Exception^ e) { return NanEscapeScope(throwV8Exception(ClrFunc::MarshalCLRExceptionToV8(e))); } return NanEscapeScope(NanUndefined()); }