最近研究在Nodejs中调用DLL,上网搜索发现主要有两种方法:
使用Nodeffi调用C风格接口的DLL,但是无法调用C++风格导出类的DLL。
使用Nodejs C++ Addon 插件,该方法可直接与C++代码交互,理论上可以调用C++风格导出类的DLL。
下面研究Nodejs Addon C++插件的编写方法。
什么是Nodejs C++ Addon Node.js插件(Addons)是C/C++编写的动态链接对象,这些对象可以被Node.js的require()函数引用,并可以像普通的Node.js模块一样使用。Addons主要用于提供一个Node.js中运行的JavaScript和C/C++库之间的接口。
插件(Addons)是动态链接的共享对象,它提供了C/C++类库的调用能力。实现插件的方法比较复杂,涉及到以下元组件及API:
V8:C++库,Node.js用于提供JavaScript执行环境。V8提供了对象创建、函数调用等执行机制,V8相关API包含在了v8.h头文件中(位于Node.js源码树的deps/v8/include/v8.h),也可以查看在线文档。
libuv:C库,实现了Node.js中的事件循环、工作线程及在不同平台中异步行为的相关功能。也可以做为是一个跨平台的抽象库,提供了简单的、类POSIX的对主要操作系统的常见系统任务功能,如:与文件系统、套接字、计时器、系统事件的交互等。libuv还提供了一个类pthreads的线程池抽象对象,可用于更复杂的、超越标准事件循环的异步插件的控制功能。
内部Node.js库:Node.js自身提供了一定义数量的C/C++API的插件可以使用 - 其中最重要的可能是node::ObjectWrap类
Node.js静态链接库:Node.js自身还包含了一部分静态链接库,如OpenSSL。这些位于Node.js源码树的deps/目录下,只有V8和OpenSSL提供了符号出口,可以供Node.js和基它插件所使用。详见Node.js依赖链接
Node Addon插件编写方法 Node Addon插件的编写需要解决两个关键问题:
当数据流向 javaScript -> C++时,如何将javascript类型数据包装成C++类型数据,供C++代码使用。
当数据流向 C++ -> JavaScript时,如何将C++类型数据包装成JavaScript类型数据,供JavaScript代码使用。
这两个关键问题的分析请参见淘宝前端团队成员发表的文章“Node.js 和 C++ 之间的类型转换[3] ”。解决这两个关键问题后,Node Addon插件编写难度就不大了。
Node Addon插件调用C++导出类DLL方法测试 现有一个采用成熟方法导出类接口的DLL[4] ,如何在Node Addon插件中调用该DLL呢?下面nodejs官网Node Addon插件例子Factory of wrapped objects为例进行讲解。
Factory of wrapped objects例子在Addon插件中包装了一个MyObject类,现在就在MyObject类调用DLL导出类的接口方法。修改MyObject.h代码,增加DLL导出类接口方法,增加类接口成员变量IExport和DLL句柄变量hDll,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 // myobject.h #ifndef MYOBJECT_H #define MYOBJECT_H #include <node.h> #include <iostream> #include <windows.h> #include <node_object_wrap.h> #include "MatureApproach.h" namespace demo { typedef IExport*(*TYPE_fnCreateExportObj) (void);//定义函数指针 typedef void(*TYPE_fnDestroyExportObj) (IExport*);//定义函数指针 class MyObject : public node::ObjectWrap { public: static void Init(v8::Isolate* isolate); static void NewInstance(const v8::FunctionCallbackInfo<v8::Value>& args); private: explicit MyObject(double value = 0); ~MyObject(); static void New(const v8::FunctionCallbackInfo<v8::Value>& args); static void PlusOne(const v8::FunctionCallbackInfo<v8::Value>& args); static void Hi(const v8::FunctionCallbackInfo<v8::Value>& args); static void Test(const v8::FunctionCallbackInfo<v8::Value>& args); static v8::Persistent<v8::Function> constructor; HMODULE hDll; IExport* pExport; double value_; }; } // namespace demo #endif
接下来将在MyObject类的构造函数中动态加载DLL,创建DLL导出类对象,在析构函数中析构DLL导出类对象,动态卸载DLL,在MyObject成员方法中调用DLL导出类方法,代码如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 // myobject.cc #include <node.h> #include "myobject.h" namespace demo { using v8::Context; using v8::Function; using v8::FunctionCallbackInfo; using v8::FunctionTemplate; using v8::Isolate; using v8::Local; using v8::Number; using v8::Object; using v8::Persistent; using v8::String; using v8::Value; Persistent<Function> MyObject::constructor; MyObject::MyObject(double value) : value_(value){ MyObject::hDll = LoadLibrary("MatureApproach.dll"); //加载动态链接库DllDemo.dll文件; TYPE_fnCreateExportObj fnCreateExportObj = (TYPE_fnCreateExportObj)GetProcAddress(MyObject::hDll, "CreateExportObj"); MyObject::pExport = fnCreateExportObj(); } MyObject::~MyObject() { TYPE_fnDestroyExportObj fnDestroyExportObj = (TYPE_fnDestroyExportObj)GetProcAddress(MyObject::hDll, "DestroyExportObj"); fnDestroyExportObj(MyObject::pExport); FreeLibrary(MyObject::hDll); } void MyObject::Init(Isolate* isolate) { // Prepare constructor template Local<FunctionTemplate> tpl = FunctionTemplate::New(isolate, New); tpl->SetClassName(String::NewFromUtf8(isolate, "MyObject")); tpl->InstanceTemplate()->SetInternalFieldCount(3); // Prototype NODE_SET_PROTOTYPE_METHOD(tpl, "plusOne", PlusOne); NODE_SET_PROTOTYPE_METHOD(tpl, "hi", Hi); NODE_SET_PROTOTYPE_METHOD(tpl, "test", Test); constructor.Reset(isolate, tpl->GetFunction()); } void MyObject::New(const FunctionCallbackInfo<Value>& args) { Isolate* isolate = args.GetIsolate(); if (args.IsConstructCall()) { // Invoked as constructor: `new MyObject(...)` double value = args[0]->IsUndefined() ? 0 : args[0]->NumberValue(); MyObject* obj = new MyObject(value); obj->Wrap(args.This()); args.GetReturnValue().Set(args.This()); } else { // Invoked as plain function `MyObject(...)`, turn into construct call. const int argc = 1; Local<Value> argv[argc] = { args[0] }; Local<Function> cons = Local<Function>::New(isolate, constructor); Local<Context> context = isolate->GetCurrentContext(); Local<Object> instance = cons->NewInstance(context, argc, argv).ToLocalChecked(); args.GetReturnValue().Set(instance); } } void MyObject::NewInstance(const FunctionCallbackInfo<Value>& args) { Isolate* isolate = args.GetIsolate(); const unsigned argc = 1; Local<Value> argv[argc] = { args[0] }; Local<Function> cons = Local<Function>::New(isolate, constructor); Local<Context> context = isolate->GetCurrentContext(); Local<Object> instance = cons->NewInstance(context, argc, argv).ToLocalChecked(); args.GetReturnValue().Set(instance); } void MyObject::PlusOne(const FunctionCallbackInfo<Value>& args) { Isolate* isolate = args.GetIsolate(); MyObject* obj = ObjectWrap::Unwrap<MyObject>(args.Holder()); obj->value_ += 1; args.GetReturnValue().Set(Number::New(isolate, obj->value_)); } void MyObject::Hi(const FunctionCallbackInfo<Value>& args) { Isolate* isolate = args.GetIsolate(); MyObject* obj = ObjectWrap::Unwrap<MyObject>(args.Holder()); Local<String> str=v8::String::NewFromUtf8(isolate, obj->pExport->Hi().data()); args.GetReturnValue().Set(str); } void MyObject::Test(const FunctionCallbackInfo<Value>& args) { Isolate* isolate = args.GetIsolate(); MyObject* obj = ObjectWrap::Unwrap<MyObject>(args.Holder()); Local<String> str=v8::String::NewFromUtf8(isolate, obj->pExport->Test().data()); args.GetReturnValue().Set(str); } } // namespace demo
参考链接
Node.js C/C++插件(Addons) , by IT笔录
Node.js v8.16.1 Documentation ,by nodejs
type-casts-between-node-and-cpp ,by 淘宝前端团队
DLL导出类和函数 ,by jackhuang