Node_C++_Addon插件编写方法

最近研究在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

参考链接

  1. Node.js C/C++插件(Addons), by IT笔录
  2. Node.js v8.16.1 Documentation,by nodejs
  3. type-casts-between-node-and-cpp,by 淘宝前端团队
  4. DLL导出类和函数,by jackhuang