使用Nodejs的addon导入cpp生成的dll时出现的问题记录

发布时间 2023-09-06 15:53:57作者: 麦块程序猿

在使用Nodejs的addon导入自己编写的cpp的dll时出现的一系列问题记录

标签: __declspecNapiLoadLibraryAGetLastErrordumpbin /exports

正常创建一个使用Napi的nodejs addon项目(网上都有,在这里不赘述),主要代码如下:

#include <napi.h>
#include <iostream>
#include <atlstr.h>

#include "Static_Function.h"

using namespace std;

//定义调用函数签名模板
typedef bool (__stdcall *DetectorInitializeFun)( );
//例如表示返回值是bool,参数列表的两个 int
typedef bool (__stdcall *DetectorInitializeFun2)(int,int);

Napi::Boolean DetectorInitialize_test(const Napi::CallbackInfo &info){
    Napi::Env env = info.Env();
    //1、获取从js传入的参数(方便修改dll名称)
    string dllPath = info[0].As<Napi::String>();
    //2、通过LoadLibraryA载入(无法通过LoadLibrary,因为他的参数是字符串常量,而我的参数是从js传过来的)
    HINSTANCE hinstLib = LoadLibraryA(dllPath.c_str());
    DWORD error_id = GetLastError();
    cout << "(1)  dllPath:[" << dllPath << "]    hinstLib:[" << hinstLib << "]    error_id:[" << error_id << "]" << endl;
    if (hinstLib != NULL){
        //3、通过符号查找导出函数
        DetectorInitializeFun ProcAddress = (DetectorInitializeFun)GetProcAddress(hinstLib, "DetectorInitialize");
        error_id = GetLastError();
        cout << "(2)" << "    error_id:[" << error_id << "]" << endl;
        if (ProcAddress != NULL){
            cout << "(3)" << endl;
            //4、调用导出函数
            ProcAddress();
            //5、释放导入的库
            FreeLibrary(hinstLib);
            return Napi::Boolean::New(env, true);
        }
        FreeLibrary(hinstLib);
    }
    cout << "(4)" << endl;
    return Napi::Boolean::New(env, false);
}

Napi::Object Initialize(Napi::Env env, Napi::Object exports){
    exports.Set(Napi::String::New(env, "DetectorInitialize_test"), Napi::Function::New(env, DetectorInitialize_test));
    return exports;
}

NODE_API_MODULE(NODE_GYP_MODULE_NAME, Initialize)

问题记录

  • 1、无法通过LoadlibraryA导入dll
cout << "(1)  dllPath:[" << dllPath << "]    hinstLib:[" << hinstLib << "]    error_id:[" << error_id << "]" << endl;

​ 输出结果是 (1) dllPath:[DllTest.dll] hinstLib:[00000000] error_id:[193]

(在这里不赘述路径不正确情况)

​ 通过查询原因是导入的DllTest.dll是64位,而当前项目是32位(当前nodejs是32位),解决办法:重新将dll编译为32位,或者切换nodejs为64位(注:切换之后需要重新npm install,保证你的node_modules都变成了64位)

  • 2、成功载入DllTest_x86.dll,但是无法调用内部方法

​ 可以看到我的代码中是这样调用的

DetectorInitializeFun ProcAddress = (DetectorInitializeFun)GetProcAddress(hinstLib, "DetectorInitialize");

​ 通过查阅相关资料,需要使用dumpbin /exports TestDll_x86.dll查询dll导出的符号表,使用符号进行调用,而不是导出的函数名称

​ 通过dumpbin,得到结果如下

Microsoft (R) COFF/PE Dumper Version 14.36.32532.0
Copyright (C) Microsoft Corporation.  All rights reserved.


Dump of file .\DllTest_x86.dll

File Type: DLL

  Section contains the following exports for DllTest.dll

    00000000 characteristics
    FFFFFFFF time date stamp
        0.00 version
           1 ordinal base
           1 number of functions
           1 number of names

    ordinal hint RVA      name

          1    0 000111F9 ?DetectorInitialize@@YA_NXZ = @ILT+500(?DetectorInitialize@@YA_NXZ)

  Summary

        1000 .00cfg
        1000 .data
        1000 .idata
        1000 .msvcjmc
        3000 .rdata
        1000 .reloc
        1000 .rsrc
        8000 .text
       10000 .textbss

​ 会发现其中一段0 000111F9 ?DetectorInitialize@@YA_NXZ = @ILT+500(?DetectorInitialize@@YA_NXZ),这就是被导出的符号列表,只有这个,才代表你的dll导出了函数供你调用,符号是 ?DetectorInitialize@@YA_NXZ,需要把代码改成:

DetectorInitializeFun ProcAddress = (DetectorInitializeFun)GetProcAddress(hinstLib, "?DetectorInitialize@@YA_NXZ");

​ 最后在下面通过ProcAddress();调用即可。

  • 3、如何定义导入的函数签名与编写具备导出性质的dll

    • 通过下面方式定义函数签名,用来表示导入的函数模板
    //例如表示返回值是bool,参数列表的两个int
    typedef bool (__stdcall *DetectorInitializeFun2)(int,int);
    
    • 通过如下方式可以定义导出dll的函数

    下面的代码是visual studio自动生成的dll项目的头文件,需要在函数声明前使用__declspec(dllexport)来表示导出函数

    #ifndef PCH_H
    #define PCH_H
    
    // 添加要在此处预编译的标头
    #include "framework.h"
    
    #ifdef DETECTOR_EXPORTS
    #define DETECTOR_API __declspec(dllexport)
    #else
    #define DETECTOR_API __declspec(dllimport)
    #endif
    
    
    DETECTOR_API bool DetectorInitialize();
    
    #endif //PCH_H
    

    实际上不需要按照上面的DETECTOR_API bool DetectorInitialize();来定义,可以用__declspec(dllexport)bool DetectorInitialize();来定义。之所以使用宏定义是为了方便管理,而不用到处都是__declspec(dllexport)