一种在nodejs中调用c++的简单方法

背景:

    目前nodejs调用c++主流的有两种方法,分别是addons和ffi

    addons是nodejs官方的c++扩展实现方案,但是由于需要使用模版,并且要对v8引擎有一定的了解,入门门槛较高。

    ffi是nodejs直接调用so库的一种实现,可以调用纯c的接口。

    以上两种方法有一个共同的缺陷,就是当nodejs调用的时候,如果不能立即返回的话,c++代码会卡住单线程的js代码。

    在这种情况下,如果c++代码没有io操作而只是纯计算的话是没有问题的,但是如果有同步io操作,势必会降低nodejs的执行效率。这时的解决方案有两个思路

    1.在js代码中使用child_process模块创建一个子进程,子进程再调用c扩展(此时子进程会被扩展卡住),保证主进程可以充分利用cpu。

    2.js在c代码中注册回调,c代码中维护多线程,不让同步io操作卡住函数返回,在处理完成后调用回调函数

    这两种方案的实现成本都比较高,所以在c代码非纯cpu计算的情况下,笔者提出了一种方案,使得nodejs调用c的开发成本降低,并且能够保证js的单核利用率不受影响。


主体思路:

    通过child_process直接创建一个子进程,这个子进程是c的可执行程序,主进程(js)和子进程(c)通过管道进行通信,

    这样主进程在需要调用一个c函数的时候会向子进程发送一个消息,并不会卡住主进程js继续执行,在c程序执行完成之后将消息返回回来。

    举一个例子:我有一段c代码,这时一段机器学习代码,可能会要访问gpu,我想要用js去调用这段c代码,如果我简单的用addons来封装,做一个同步调用,那么在gpu进行运算的时候,js的线程会被卡住,cpu没有得到利用,如果是后台服务的话会导致并发处理能力降低。这时就非常适合用上面的方式创建一个子进程,js和c程序通过管道消息来协同工作。

    当然,这种模式是有适用范围的,前提是你的c程序的分离成子进程后通信的耗时相比于c执行的耗时在一个可接受的范围内。

    比如,如果你的c程序只是简单的逻辑运算,每一次计算都只要微秒级别的,但是由于创建了一个子进程,进程切换和通信带来了毫秒级别的代价,这时就不划算了。但是如上面的例子,如果你的c程序中出现了大量的io操作或者需要协处理器工作的情况,并且时间较高(100毫秒级别左右),这时进程切换和通信带来的开销就可以忽略。

实现:

    笔者为这种模式实现了一个简单的框架

    https://github.com/freelw/nodejscallc

    下面介绍一下使用方法

    在clone到本地之后你的目录结构应该是这样


 首先我们执行

npm install

来安装需要的依赖

之后我们需要定义一个接口描述文件,json格式

{
    "func_name" : "test",
    "req_params" : [
        {
            "name" : "param_long",
            "type" : "long"
        },
        {
            "name" : "param_string",
            "type" : "string"
        }
    ],
    "rsp_params" : [
        {
            "name" : "rsp_param_string",
            "type" : "string"
        },
        {
            "name" : "rsp_param_long",
            "type" : "long"
        }
    ]
}

这个接口描述文件主要分为3部分

1.func_name,表示你所要调用的函数名称

2.req_params, 是输入参数列表,列表中的每个元素有两个字段,name表示参数名,type表示参数类型,目前仅支持long和string

3.rsp_params,是返回参数列表,内容同2

在这个例子中,有两个输入参数 param_long long类型, param_string string类型

有两个输出参数rsp_param_string string类型, rsp_param_long long类型

我们把这个文件保存为desc.json

然后我们执行初始化工具来生成调用函数所需的js代码和c代码

node init.js -i desc.json

这时便会按照你的接口描述文件创建三个文件

由于func_name是test,所以生成的js和cpp文件的前缀是test

test_proxy.js是js的函数调用代理,这里的代码是用户完全不用修改的

test_imp.cpp是c函数的实现部分,用户需要实现test函数


在这里我们假设需要实现一个逻辑,将param_string从rsp_param_string输出

并且需要rsp_param_long=param_long+1

于是我们有以下代码


这时我们进入build/c/下面进行编译

cd build/c
make

这时会在release下面生成一些文件

test_proxy.js是刚才js文件夹下生成的,make执行是自动拷贝到此处

test是test_imp.cpp编译后生成到可执行文件

我们现在写一个测试脚本来调用以下我们写的函数

const test = new (require('./build/release/test_proxy'))();

test.do({
    param_long: 123,
    param_string: 'teststring',
}, (rsp) => {
    console.log('rsp : ', rsp);
});

这里面test是new出来的一个实例,对应着一个创建出来的c程序进程, test.do可以被调用多次,如果c程序中有全局变量在每次调用时改变, 那么就会影响之后的调用结果, 就是说每new一次是一个执行session,这种接口很适合加载需要很久,执行很快的程序,不用每次创建进程进行初始化。另外,如果你想要让c程序多进程,只要new多个实例就好。

执行

node test.js
rsp :  { rsp_param_string: 'teststring', rsp_param_long: 124 }

调用成功

所以整个过程就是这样

“定义接口文件”-->“通过工具生成js和c文件”-->“补充c文件中的实现逻辑”-->“编译c文件”-->“调用js文件”

本框架还有一些欠缺,比如c程序在向js传递消息的时候是使用的同步的write方法,会导致c程序的cpu使用率达不到极限,笔者之后会使用非阻塞io的模型优化这块代码。

如果你喜欢这篇文章,请麻烦帮我star哦。


Node.js是一个使用JavaScript编写的服务器端应用程序环境,它使用事件驱动、非阻塞I/O模型,使得它非常适合在高并发场景下运行。在一些场景下,我们需要将Node.js与一些C/C++语言开发的库进行交互,这时我们可以通过node-ffi或者node-ffi-napi这样的扩展来实现。这些扩展可以让我们在Node.js加载动态链接库(DLL),并且调用DLL函数。 Node.js加载DLL的方式和C/C++一样,需要通过动态链接库的名称来加载对应的DLL文件。一般来说,我们可以在Node.js使用node-ffi模块来完成动态链接库的加载和函数调用。 node-ffi提供了一个简单而直接地API,允许我们从JavaScript代码调用动态链接库函数。要使用node-ffi,我们需要做以下几步: 1. 安装node-ffi:在命令行运行npm install ffi --save命令。 2. 加载DLL文件:使用ffi.Library方法来加载DLL文件。 3. 定义函数类型:使用ffi.Function和ffi.Callback方法来定义函数类型。 4. 调用DLL函数:使用ffi.Library对象函数名来调用DLL函数。 下面是一个使用node-ffi模块加载DLL文件并调用DLL函数的示例: ``` const ffi = require('ffi'); // 加载DLL文件 const win32 = ffi.Library('kernel32', { 'GetLastError': [ 'int32', [] ] }); // 调用DLL函数,返回错误码 console.log(win32.GetLastError()); ``` 在这个示例,我们使用ffi.Library方法加载了kernel32.dll文件,然后使用GetLastError函数名来调用DLL函数。注意:在不同的操作系统环境DLL文件名称和函数名可能有所不同。 总的来说,Node.js通过node-ffi和node-ffi-napi这些扩展提供了一种简单、快捷的方式来将JavaScript代码与C/C++语言开发的库进行交互。使用这些扩展,我们可以轻松地在Node.js加载DLL文件,调用DLL函数,实现更为复杂的应用程序。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值