我是靠谱客的博主 忧郁蜗牛,最近开发中收集的这篇文章主要介绍NodeJS中使用N-API(NODE-API)去调用已经存在的dll.,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

在之前的一篇文章中,介绍了编写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.所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(45)

评论列表共有 0 条评论

立即
投稿
返回
顶部