概述
在之前的一篇文章中,介绍了编写NodeJS AddOn的三种方式,N-API已经是AddOn的未来,N-API摆脱了Node各个大版本引用的chrominum V8不一致导致的API修改的问题。在之前的文章中介绍了N-API是Node team自己维护的,N-API内部会做V8中api匹配的问题。下面用一个例子来学习写一个N-API来调用已经存在的dll.
1. 写一个dll
利用机器上安装的vs2017(各个版本都可以),创建一个c++的动态链接库工程。
在工程里创建一个接口文件InterfaceHeader.h
#pragma once
class InterfaceHeader
{
public:
virtual ~InterfaceHeader() = default;
public:
virtual void SetMessage(char const *&& pMsg) = 0;
virtual char* PrintMessage() = 0;
};
在动态链接库工程里面创建一个类(ExportHelloWorld)实现这个接口,例如如下声明:
#pragma once
#include "InterfaceHeader.h"
#ifdef EXPORT_API
#define EXPORT_CLASS __declspec(dllexport)
#else
#define EXPORT_CLASS __declspec(dllimport)
#endif
class ExportHelloWorld: public InterfaceHeader
{
public:
ExportHelloWorld();
ExportHelloWorld(const char* msg);
virtual ~ExportHelloWorld();
private:
char m_Msg[MAX_PATH];
public:
void SetMessage( const char *&& pMsg) override;
char* PrintMessage() override;
};
创建输出API函数声明
extern "C" {
EXPORT_CLASS InterfaceHeader* CreateHeader();
EXPORT_CLASS void InvokeHeader();
}
在cpp文件里面实现API和相关的类(ExportHelloWorld),
#include "pch.h"
#include "ExportHelloWorld.h"
ExportHelloWorld::ExportHelloWorld(): m_Msg{0}
{
}
ExportHelloWorld::ExportHelloWorld(const char* msg)
{
strcpy_s(m_Msg, msg);
}
ExportHelloWorld::~ExportHelloWorld()
{
m_Msg[0] = 0;
}
void ExportHelloWorld::SetMessage(char const *&& pMsg)
{
if (strlen(pMsg) > sizeof(m_Msg))
memcpy(m_Msg, pMsg, sizeof(m_Msg) - 1);
else
strcpy_s(m_Msg, pMsg);
}
char* ExportHelloWorld::PrintMessage()
{
return m_Msg;
}
InterfaceHeader* g_header = nullptr;
InterfaceHeader* CreateHeader()
{
return new ExportHelloWorld();
}
void InvokeHeader()
{
if (!!g_header)
g_header->PrintMessage();
}
编译,生成动态链接库。
2. 撰写N-API add on module
2.1 写code调用刚才产生的dll.
#include "pch.h"
#include <node_api.h>
#include "Common.h"
#include <cstring>
#include <thread>
#include <Windows.h>
#include <WinBase.h>
#include <tchar.h>
#include "../Extention.dll/InterfaceHeader.h"
// we can remove the include_dirs information in binding.gyp
//#include "InterfaceHeader.h"
// we should add include_dirs information in binding.gyp
#include <fstream>
//these variables are shared by multiple call, so limit one call from js at same time.
napi_deferred Deferred;
napi_async_work async_worker;
PromiseDeferred data;
int g_index = 0;
HMODULE g_hModule = nullptr;
// near the top of your CPP file
EXTERN_C IMAGE_DOS_HEADER __ImageBase;
void func() {
char dir[MAX_PATH] = { 0 };
GetCurrentDirectoryA(MAX_PATH, dir);
std::string str(dir);
str.append("\extention.dll.dll");
g_hModule = LoadLibrary(str.c_str());
}
int do_something_async(napi_env env);
//nodejs调用的函数,在函数中产生promise,并返回
napi_value Test(napi_env env, napi_callback_info info)
{
size_t argc = 2;
napi_value argv[2];
napi_value _this;
void* data;
NAPI_CALL(env,
napi_get_cb_info(env, info, &argc, argv, &_this, &data));
NAPI_ASSERT(env, argc >= 2, "Not enough arguments, expected 2.");
napi_valuetype tp;
NAPI_CALL(env, napi_typeof(env, argv[0], &tp));
NAPI_ASSERT(env, tp == napi_number, "First parameter must be a number");
NAPI_CALL(env, napi_typeof(env, argv[1], &tp));
NAPI_ASSERT(env, tp == napi_object, "Second paramter must be an object");
napi_value promise;
NAPI_CALL(env, napi_create_promise(env, &Deferred, &promise));
do_something_async(env);
return promise;
}
//异步调用其他函数,实际上是在v8 isolate引擎的线程池里执行
int do_something_async(napi_env env)
{
const char p[] = "Hello World";
napi_value result;
napi_create_string_utf8(env, p, strlen(p), &result);
napi_value obj;
NAPI_CALL(env, napi_create_object(env, &obj));
data.pDeferred = nullptr;
data.index = g_index;
g_index++;
napi_value resource;
NAPI_CALL(env, napi_create_string_utf8(env, "TestResource",
NAPI_AUTO_LENGTH, &resource));
NAPI_CALL(env, napi_create_async_work(env, obj, resource,
async_executor, async_complete, &data, &async_worker));
NAPI_CALL(env, napi_queue_async_work(env, async_worker));
return 0;
}
typedef InterfaceHeader* (WINAPI *pFuncCreateExtention)();
// 异步执行函数,要按照规则说明去写该函数
void async_executor(napi_env env,
void* data)
{
auto pIndex = static_cast<PromiseDeferred*>(data);
func();
if (!!!g_hModule)
{
char err[] = "Error load extention.dll.dll";
std::string strErr(err);
char err1[256] = { 0 };
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(),
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), err1, 255, NULL);
strErr.append("-" + std::string(err1));
pIndex->content = std::make_shared<std::string>(strErr);
pIndex->contentLength = strlen(err);
}
else
{
auto ctrl = reinterpret_cast<pFuncCreateExtention>(GetProcAddress(g_hModule, "CreateHeader"));
auto extention = ctrl();
char msg[] = "Welcome Shirly Qu!";
extention->SetMessage(std::move(msg));
auto pMsg = extention->PrintMessage();
pIndex->content = std::make_shared<std::string>(pMsg);
pIndex->contentLength = strlen(pMsg);
FreeLibrary(g_hModule);
g_hModule = nullptr;
}
Sleep(5000);
}
//异步执行结束之后执行的函数。在该函数中resolve/reject promise
void async_complete(napi_env env,
napi_status status,
void* data)
{
auto pIndex = static_cast<PromiseDeferred*>(data);
napi_value index;
NAPI_CALL_RETURN_VOID(env, napi_create_int32(env, pIndex->index, &index));
napi_value content;
NAPI_CALL_RETURN_VOID(env, napi_create_string_utf8(env, pIndex->content.get()->c_str(), NAPI_AUTO_LENGTH, &content));
napi_value resolvedObj;
NAPI_CALL_RETURN_VOID(env, napi_create_object(env, &resolvedObj));
NAPI_CALL_RETURN_VOID(env, napi_set_named_property(env, resolvedObj, "Index", index));
NAPI_CALL_RETURN_VOID(env, napi_set_named_property(env, resolvedObj, "Content", content));
NAPI_CALL_RETURN_VOID(env, napi_resolve_deferred(env, Deferred, resolvedObj));
NAPI_CALL_RETURN_VOID(env, napi_delete_async_work(env, async_worker));
}
//初始化addon,exports Test函数。
napi_value Init(napi_env env, napi_value exports)
{
napi_property_descriptor des[] = {
DECLARE_NAPI_PROPERTY("TestPromise_API", Test)
};
NAPI_CALL(env,
napi_define_properties(env, exports, sizeof(des) / sizeof(*des), des));
return exports;
}
NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)
在code中引入了common这个头文件,里面声明和实现了NAPI_CALL等函数,
#pragma once
#include <js_native_api.h>
// Empty value so that macros here are able to return NULL or void
#define NAPI_RETVAL_NOTHING
// Intentionally blank #define
#define GET_AND_THROW_LAST_ERROR(env)
do {
const napi_extended_error_info *error_info;
napi_get_last_error_info((env), &error_info);
bool is_pending;
napi_is_exception_pending((env), &is_pending);
/* If an exception is already pending, don't rethrow it */
if (!is_pending) {
const char* error_message = error_info->error_message != NULL ?
error_info->error_message :
"empty error message";
napi_throw_error((env), NULL, error_message);
}
} while (0)
#define NAPI_ASSERT_BASE(env, assertion, message, ret_val)
do {
if (!(assertion)) {
napi_throw_error(
(env),
NULL,
"assertion (" #assertion ") failed: " message);
return ret_val;
}
} while (0)
// Returns NULL on failed assertion.
// This is meant to be used inside napi_callback methods.
#define NAPI_ASSERT(env, assertion, message)
NAPI_ASSERT_BASE(env, assertion, message, NULL)
// Returns empty on failed assertion.
// This is meant to be used inside functions with void return type.
#define NAPI_ASSERT_RETURN_VOID(env, assertion, message)
NAPI_ASSERT_BASE(env, assertion, message, NAPI_RETVAL_NOTHING)
#define NAPI_CALL_BASE(env, the_call, ret_val)
do {
if ((the_call) != napi_ok) {
GET_AND_THROW_LAST_ERROR((env));
return ret_val;
}
} while (0)
// Returns NULL if the_call doesn't return napi_ok.
#define NAPI_CALL(env, the_call)
NAPI_CALL_BASE(env, the_call, NULL)
// Returns empty if the_call doesn't return napi_ok.
#define NAPI_CALL_RETURN_VOID(env, the_call)
NAPI_CALL_BASE(env, the_call, NAPI_RETVAL_NOTHING)
#define DECLARE_NAPI_PROPERTY(name, func)
{ (name), NULL, (func), NULL, NULL, NULL, napi_default, NULL }
#define DECLARE_NAPI_GETTER(name, func)
{ (name), NULL, NULL, (func), NULL, NULL, napi_default, NULL }
2. 撰写binding.gyp.
{
"variables":{
#"arch%":"win32"
# --arch=ia32, node must be x86 version
"arch%":"x64"
# --arch=x64, node must be x64 version
},
"targets": [
{
"target_name": "test",
"sources": [
"pch.cpp",
"Common.cpp"
],
"include_dirs":[ # when inlcude header file with accurate path, we can not inlcude this "include_dirs" information
".",
"../Extention.dll"
]
},
{
"target_name": "merge_binaries",
"type": 'none',
"dependencies": ['test'],# test is built before this target is built
"copies":[
{
"destination": "<(module_root_dir)/build/release/<(arch)",
"files":[
"../<(arch)/release/Extention.dll.dll"
]
}
]
},
{
"target_name": "copy_binaries",
"type": 'none',
"dependencies": ['merge_binaries'], # target merge_binaries is built before this target is built
"copies":[
{
"destination": "<(module_root_dir)/../../../../ExpressApp",
"files":[
"<(module_root_dir)/build/release/<(arch)/Extention.dll.dll",
"<(module_root_dir)/build/release/test.node"
]
}
]
}
]
}
在文件中做了简单的配置,其中arch%定义的时候是根据platform是x86还是x64来定的,实际上与nodejs运行程序的版本是32还是64对应的。如果是32版本,那么在步骤1中建立的dll就需要是32版本,运行node-gyp的时候,参数--arch=ia32,如果是64的nodeJS,那么dll也就是64版本的,运行node-gyp的时候,参数--arch=x64。
另外我运行的命令是node-gyp rebuild --verbose --arch=x64,
具体参数的说明可以参考下面几个文档:
binding.gyp的说明
node-gyp的使用
3. nodeJS调用改addon。
const {logMessage} = require('../common/log');
const testAPI = require("../test.node");
if(!!testAPI)
{
testAPI.TestPromise_API(10, {}).then((data)=>{
logMessage(`get data from node addon api ${data.Index}-${data.Content}`);
})
logMessage('start test api');
}
根据binding.gyp的配置,我输出的是test.node.另外函数TestPromise_API是输出的函数名,也就是addon里面的Test函数,是AddOn初始化函数里面的命名的。
最后
以上就是高高悟空为你收集整理的NodeJS中使用N-API(NODE-API)去调用已经存在的dll.的全部内容,希望文章能够帮你解决NodeJS中使用N-API(NODE-API)去调用已经存在的dll.所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复