使用ffi-napi和NodeJS Buffer创建NodeJs addon

AI权益加码!Claude Code、Cursor等20+工具免费用! 购周边限时加赠Coding Plan Lite,畅享主流AI工具!学习进阶更高效! 阅读详情

创建NodeJS AddOn的方式很多,在NodeJS开发出c/c++ addon with NAPI之后,大家基本上都用NAPI写addon了,最基本的原因就是NAPI可以脱离v8修改的依赖,NAPI对v8中的api进行了封装,不同版本的Node会自动适用v8底层的API变化。

node-ffi-napi是一个开源的module,该模块内部通过nodejs napi加载我们自己写的dll中输出的函数(输出函数必须是extern 'c'的),由于该模块在内部使用了napi,所以我们在下载该模块的时候,会自动对模块进行编译,需要提前安装很多的模块,具体可以参考在不同的系统上怎样编译一个addon。

在使用node-ffi-napi的时候,一般也会使用ref-napi, ref-array-napi, ref-struct-napi,具体这些模块的使用可以参考npm上的readme.

Buffer是NodeJs为管理内存而设计的,Buffer是固定内存长度的byte array,是继承与Javascript 的Uint8Array, 你可以跟JS中的DataView一样去操作Buffer,具体使用可以参考NodeJS网站的说明。

下面介绍一下我在适用ffi-napi和Buffer的几个特殊的例子:

1.dll中的调用约定:

       在定义dll输出函数的时候使用extern 'c', 也就是编译用的c规则,如果是extern 'c++'的话,就是用的c++规则,就会有函数重命名的问题;默认使用的是__cdecl,不要使用__stdcall,因为__stdcall会带出函数参数信息,一般自定义的都不会使用__stdcall,只有windows API定义这样,这样不利于动态输入参数。 

具体可以参考以及微软的说明

// api.h
#define DllAPI __declspec(dllexport)
extern "C" {
    DllAPI void GetTmpStruct(void* buffer);
}

2.在dll中设置回调函数,在c++代码中调用 typescript中的函数,有些特殊情况是一个node程序调用Addon,在addon里面需要调用回调函数,也就是typescript 的code, 需要怎么处理呢?

下面是addon中C++的code:

typedef void (* JsCallBack)(int status, int extendstatus);  //callback
void SetTestCallBack(void* wrapper, JsCallBack pfunc)
	{
		wrapper* p = static_cast<wrapper*> (wrapper);
		p->m_pFuncCallBack = pfunc;
	}

代码段中SetTestCallBack是输出函数,在typescript中通过ffi加载调用。JSCallBack是函数定义类型,也就是在typescript中的定义的函数格式要满足上面的条件.

那么在typescript中要怎么定义呢?

import * as ffi from 'ffi-napi'
import * as ref from 'ref-napi'
class Testify{
    //this callback must be saved in object, so that it will not be removed by nodejs
    private callback: any;
    constructor() {
        if (!Testify.libObj) {
            Testify.InitDll();
        }
        
        //set callback function for addon
        SetTestCallBack(null, (status, extendstatus) => {
            console.log(`status=${status}, extendstatus= ${extendstatus}`);
        })
    }

    public static InitDll(): void {
        Testify.libObj = ffi.Library(this.dllPath, {
            'SetTestCallBack': [ref.types.void, [lpVoid, ffi.Function(ref.types.void, [ref.types.int, ref.types.int])]]
        }
    }
    
    public SetTestCallBack(wrapper: any, pCallBack: (status: number, extendstatus: number) => void) {
        this.callback = ffi.Callback(ref.types.void, [ref.types.int, ref.types.int], pCallBack);

        Testify.libObj.SetTestCallBack(wrapper, this.callback);
    }

}

在上面的例子中可以看到,在Testify构造函数中加载了dll并且设置了回调函数,这个回调函数(callback变量)需要存储下来,不要做临时变量,防止被自动回收,addon调用的时候,就会出exception.

3. 设置的回调函数中参数里面含有c++ 的函数,这种状况一般是我们在为老的c++程序写addon的时候会用到,当然也可以对c++函数进行分解,防止出现这种状况,但是如果我们不想动的话,也可以保持原样,例如 c++中定义的回调函数如下:

typedef int (__stdcall* LPMessageBox)(long p, char* text);
typedef void (* JsCallBack)(LPMessageBox pFunc, long p);  //callback type

void SetTestCallBack(void* wrapper, JsCallBack pfunc)
	{
		ActiveXWrapper* p = static_cast<ActiveXWrapper*> (wrapper);
		p->m_pFuncCallBack = pfunc;
	}

int cbMessageBox(long param, byte* value)
{
	::MessageBox(nullptr, (LPCTSTR)value, _T(""), MB_OK);
	return 100;
}

void DeleteScriptObj(void* wrapper, void* script)
	{
		wrapper* p = static_cast<wrapper*> (wrapper);
		LPMessageBox msgbox = reinterpret_cast<LPMessageBox>(cbMessageBox);
		p->m_pFuncCallBack(msgbox, 123);
		p->DeleteScriptObject();
	}

上面中LPMessageBox是c++这边的定义, JSCallBack是typescript的回调函数,从上面的代码段可以看出,调用DeleteScriptObj的时候,会调用typescript的回调函数m_pFuncCallBack, 在调用回调的时候有参数msgbox,改参数是一个函数,那么在typescript中怎么去定义和设置呢?

//定义msgbox类型,跟c++对上
let messageBoxFunctionType = ffi.Function(ref.types.int, [ref.types.long, ref.refType(ref.types.char)]);

//Loadlibrary的时候设置callback类型
'SetTestCallBack': [ref.types.void, [lpVoid, ffi.Function(ref.types.void, [messageBoxFunctionType, ref.types.long])]],


//在构造函数中设置addon的回调函数
this.initcb = this.InitCallBack.bind(this, this.wrapper);
this.SetTestCallBack(this.wrapper, this.initcb);

//回调函数
public InitCallBack(wrapper: any, messageBox: any, addonVal: number) {
        //var buf = Buffer.alloc(9, 'abcedfghi', 'ucs-2');
        var str = 'abcdefghi';
        let len = Buffer.byteLength(str, 'ucs-2');
        let buf2 = Buffer.alloc(len + 2, 0);
        ref.writeCString(buf2, 0, str, 'ucs-2');
        let rs = messageBox(150, buf2);  //调用c++ dll中的函数
        //return 20;
    }

4.有的时候我们在c++的DLL里面的内存是外面(调用dll的客户端)开辟的,然后在dll和客户端都可以使用指针对象。比如:

struct pointerStruct
	{
		int instance;
		char name[50];
	};
typedef void (*JsCreateObject)(pointerStruct** p);
void SetCallBackToCreateObject(JsCreateObject pfunc)
	{
		pointerStruct* p = nullptr;
		pfunc(&p);
		::MessageBoxA(nullptr, p->name, "", MB_OK);
	}

在上面的c++的code代码段里面,可以发现临时变量p是个指针,需要在回调函数pfunc里面去生成。那么在typescript又怎么处理呢?

//定义该类型,用来对指针变量赋值
export class pointerType implements ref.Type {
    size: number;
    indirection: number;
    tp: ref.Type;
    name?: string | undefined;
    alignment?: number | undefined;
    /**
     * type: base type, such as the pointer to a char array, or the pointer to a int, ushort and so on
     */
    constructor(type: ref.Type, byteSize?: number) {
        this.tp = type;
        this.size = byteSize ? byteSize : this.tp.size;
        this.indirection = 1;
    }

    get(buffer: Buffer, offset: number) {
        switch (this.tp) {
            case ref.types.char:
            case ref.types.uchar:
                return ref.readCString(buffer, offset);
            case ref.types.int:
                {
                    return ref.endianness === 'LE' ? buffer.readIntLE(offset, this.size) : buffer.readIntBE(offset, this.size);
                }
            case ref.types.ushort:
                {
                    return ref.endianness === 'LE' ? buffer.readUInt16LE(offset) : buffer.readUInt16BE(offset);
                }
            case ref.types.byte:
                {
                    return buffer.readUInt8(offset);
                }
            default:
                return null;
        }
    }
    set(buffer: Buffer, offset: number, value: number | Buffer): void {
        if (typeof value === 'number') {
            switch (this.tp) {
                case ref.types.ushort:
                    ref.endianness === 'LE' ? buffer.writeUInt16LE(value, offset) : buffer.writeUInt16BE(value, offset);
                    break;
                case ref.types.int:
                    ref.endianness === 'LE' ? buffer.writeInt32LE(value, offset) : buffer.writeInt32BE(value, offset);
                    break;
                case ref.types.byte:
                    buffer.writeInt8(value, offset);
                    break;
                default:
                    break;
            }
        }
        else {
            value.copy(buffer, offset, 0, value.byteLength);
            if (buffer.byteLength > offset + value.byteLength) {
                let index = offset + value.byteLength;
                for (; index < buffer.byteLength; index++) {
                    buffer[index] = 0;
                }
            }
        }
    }
}

在上面的typescript里面自定义了一个类型,该类型继承了ref.type,该类型是为了对指针变量赋值。在上面的c++ 代码中,我们看到指针变量‘p’所指向的对象需要在typescript中产生,那么实际上就是对指针变量p写入一个地址。

import * as ref from 'ref-napi'
import StructType from 'ref-struct-napi';
import ArrayType from 'ref-array-napi'
//pointerStruct跟c++里面的对象要对应
export let pointerStruct = StructType(
    {
        instance: ref.types.int,
        name: ArrayType(ref.types.char, 50)  //长度50的char数组,跟c++对应
    });
public CreateObject(obj: any) {
        console.log(pointerStruct.size);
        let objTmp = new pointerStruct();
        objTmp.instance = 110;
        var buf = Buffer.from('Spring Dou');
        buf.forEach((v, index) => {
            objTmp.name[index] = v;
        })
        objTmp.name[buf.byteLength] = 0;
        this.objects = ref.alloc(pointerStruct, objTmp);
        obj.type.set(obj, 0, ref.address(this.objects));
    }

    private CreateObjectCallBack: any;
    public SetCallBackToCreateObject() {
        this.CreateObjectCallBack = ffi.Callback(ref.types.void,
            [ref.refType(new pointerType(ref.types.int))], this.CreateObject.bind(this));
        Testify.libObj.SetCallBackToCreateObject(this.CreateObjectCallBack);
    }

上面的typescript可以看到我们定义回调函数的时候用了new pointerType(ref.types.int),也就是表明CreateObject(obj: any)中的参数obj是一个4字节的指针对象。然后在CreateObject里面,调用obj.type.set(...)去将产生的变量的地址写入obj中。

5.为C++指针变量的赋值,跟4中的情况不同,该指针变量实在c++端产生的,需要在typescript这边对这个变量赋值。

void GetValue()
{
    char a[50]{0};
    pfunc(a);
    ::MessageBoxA(nullptr, a, "", MB_OK);
}

看上面c++函数的目的就是在pfunc回调函数中给a数组赋值。

那么pfunc中怎么给它赋值呢我?

//定义回调函数
pfunc(val: any): void {
    var buffer = Buffer.from('Spring Dou');
    val.type.set(val, 0, buffer);
}

不过这个在loadlibrary的时候我们要做该回调函数声明的时候要把val的类型声明为ref.reftype(new pointerType(ref.types.char, 50)), 其中pointerType就是上面我们自定义的类型,50就是指针所指向的内存的字节长度。

public SetCallBackSetValue() {
        this.SetValueCallBack = ffi.Callback(ref.types.void,
            [ref.refType(new pointerType(ref.types.char, 50))], this.pfunc.bind(this));
        Testify.libObj.SetCallBackSetValue(this.SetValueCallBack);
    }

 

 

开源鸿蒙南向嵌入学习笔记——NAPI框架学习(一) NAPI其实是最早应该是来自node.js中的一个拓展库(也可以说是一整套API接口),叫Node-API,叫做N-API。是用来构建本地插件的API,将所有的nodejs底层数据结构黑盒化,封装成二进制接口,这样就可以实现不同版本的Node.js使用同样的接口,其目的是为了简化开发维护。这是一条使用框架编写一些C/C++代码作为JS的接口的时需要注意的事情。黄同学在做一个板子的sample的时候发现,某个smaple的样例源码无法跑通,除了一些简单的语法错误,最主要的是函数定义时的参数类型。 阅读详情

相关推荐

高性能Electron桌面应用开发 - 使用ffi-napi调用C++库

Electron让我们用Web技术开发桌面应用,但当需高性能时会受限。此时,可使用C++开发底层库,然后在Electron中通过ffi-napi调用。ffi-napi可以在Node.js中加载调用C/C++的动态库。本文介绍如何在Electron中使用ffi-napi 调用C++库:开发库,安装ffi-napi,加载库并调用函数,检查结果。通过调用C++函数,可实现高性能功能,大大提高应用性能扩展性。

码农飞上天 6603

node-ffi-napi:适用于N.API风格的Node.js的外部函数接口(FFI

节点ffi-napi N-API的Node.js外部功能接口 node-ffi-napi是一个Node.js插件,用于使用纯JavaScript加载调用动态库。 它可以用于创建与本机库的绑定,而无需编写任何C ++代码。 由于它可以处理跨JavaScriptC的类型转换,因此还简化了C代码对node.js的扩充,这可以将样板代码添加到原本简单的C语言中。有关此example/factorial请参见example/factorial用例。 警告: node-ffi-napi假定您知道自己在做什么。 您可以很容易地创建将解释器隔离的情况,除非您具有C调试器技能,否则您可能不知道发生了什么。 警告: node-ffi的原始API在N-API包装器中几乎没有被改动。 但是,在垃圾回收多线程执行的上下文中,API没有非常明确定义的属性。 如果可能,建议避免使用此库的任何多线程用法。

nodejsnapi框架的使用JS与C的互相调用

NodeJSnapi框架简介,以及在napi框架下JS与C代码的参数互传

新兴IT民工的专栏 831

使用ffi-napi引入C++的dll

引言 这一篇将介绍如何在node.js+electron环境中, 使用node-ffi/ffi-napi调用C/C++编写的动态链接库(即dll), 实现调用C/C++代码。 本教程适用于electron 4.x-6.x版本。 如electron 4.2.10版本, electron 5.0.6版本, electron 6.0.10版本。 ffi 实现这个功能, 主要使用的插件是ffi(Foreign Function Interface,外部函数接口)。 node-ffi是一个用于使用纯Ja

顺其自然~专栏 1万+

ffi-napi安装使用(windwos下) 问题记录 node-gyp electron dll node

ffi-napi安装使用 问题记录 node-gyp electron dll node;windows下安装问题记录。

jjocwc的博客 6828

探索Node.js的边界:Node-FFI-NAPI - 一种在JavaScript中调用C/C++库的新方式

是一个用于Node.js的、基于N-API的 Foreign Function Interface (FFI) 库。它允许你在JavaScript代码中直接调用CC++编写的原生动态链接库(DLL或.so文件),从而打破了JavaScript仅限于Web环境的限制,扩展了Node.js的应用场景。 ## 技术分析 ### N-API Node-FFI-NAPI的核心是利用了Node.js

gitblog_00030的博客 1672

【亲测免费】 node-ffi-napi 技术文档

`node-ffi-napi` 是一个针对 Node.js 的 N-API 外部函数接口库,允许开发者通过纯JavaScript调用动态链接库中的函数。无需编写C++代码,即可实现与本地库的深度集成。它处理JavaScriptC类型之间的转换,简化了将C代码整合到Node.js应用中的过程。但请注意,此库要求用户具备一定的底层编程知识,以避免可能导致的程序异常。 ## 安装指南 ### 自动

gitblog_01256的博客 1197

Node.js使用ffi-napi,ref-array-napi,ref-struct-napi调用动态库

0x01 概述 使用electron开进行桌面程序的开发,似乎成了WEB前端开发人员转桌面程序开发的首选。近期有一些使用在electron中使用加密锁的需求,学习了一下在Node.js中通过ffi-napi模块调用动态链接库,把几款加密锁产品的动态库使用javascript封装了一下,实现了electron中使用加密锁功能。 开发过程中遇到了一些问题,踩了一些坑,这里总结记录一下。这里使用接口函数参数类型比较复杂的ROCKEY-ARM的动态链接库来进行开发。 NOTE: javascript封装的ROCK

詹天佐 7852

Windows下Nodejs如何使用ffi-napi调用dll

Wl,–add-stdcall-alias是用于确保32位程序可以正确链接到64位DLL的GCC特定选项。如果你在64位机器上编译32位程序,或者反过来,你可能需要这个选项。并测试myprogram.exe,输出。

qq_43478653的博客 1702

nodejs使用node-ffi-napi 访问dll文件

用electron做一个访问can卡的上位机 使用node-ffi-napi 访问dll文件,需要安装 ffi-napi ref-napi ref-array-napi ref-struct-napi 安装ffi需要用到node-gyp,安装教程可以百度下 以下是使用过程 需要调用的dll的.h头文件如下,此dll是用来访问can卡的 #ifndef CONTROLCAN_H #define CONTROLCAN_H //#include <cvidef.h> //使用CVI平台开发,请使用

prophet23的博客 3241

【亲测免费】 Node.js 外部函数接口(FFI)之node-ffi-napi下载与安装教程

在这个快速教程中,我们将一起学习如何下载并安装`node-ffi-napi`这个强大的Node.js模块,它允许我们使用纯JavaScript调用动态库中的原生函数,极大地简化了与本地代码交互的过程。 ## 1. 项目介绍 **node-ffi-napi**是专为Node.js设计的一个Foreign Function Interface(FFI),利用N-API风格实现。无需深入C++编程即

gitblog_01282的博客 2488

Node.js使用ffi-napi,ref-napi,ref-array-di,ref-struct-di调用动态库-实例虹软人脸识别(一、基本概念)

系列文章目录 第一章 基础(本章) 第二章 详解-ffi-napi 第三章 详解-ref-napi 文章目录系列文章目录@[TOC](文章目录)前言一、基本概念?二、使用步骤1.引入库2.读入数据总结 前言 提示:这里可以添加本文要记录的大概内容: 本系列主要讲解在开发 electron 桌面应用过程中,需要配合使用厂家硬件,调用提供的 DLL 动态链接库的使用,主要案例是使用 虹软人脸识别 进行人脸识别 一、基本概念? 二、使用步骤 1.引入库

Luckly_smile的博客 4292

ffi-napi 调用本地动态库

ffi-napi 调用本地动态库 在开发之前,确保你已经下载好了相关依赖 "dependencies": { "ffi-napi": "^4.0.3", "ref-array-di": "^1.2.2", "ref-array-napi": "^1.2.2", "ref-napi": "^3.0.3" } 在ffi中,基本类型的映射关系如下,你也可以用ref.types.***来标识对应的类型 export interface UnderlyingTypeDef

dysengor的博客 3147

electron ffi-napi 调用dll 记录

用管理员身份打开CMD 先安装 安装windows-build-tools(ffi-napi依赖ref-napi,这两者都需要依赖python2.7c++编译工具,windows-build-tools为它们自动配置所需环境) // 以管理员身份运行终端执行命令 npm install --global --production windows-build-tools 成功截图 在cmd中输入 python where pyton 继续安装 npm install -g node-gyp

欲掩不从心 7746

nodejs c++ 插件编译踩坑记

这里尤其是在electron里引用了c++插件时,需要注意这个问题,因为electron内集成的node官方的node版本其实是不完全一样的,除了上述NAPI_VERSION以外,还要注意abi的版本,因为这个限定了API调用的约定,如果不一致,那么即便你编译插件通过了,实际运行electron程序时,依然会报错。插件的作者(或者你),在开发时的本机环境,可能与使用者是不一样的,所以有时候下载的插件不能通过编译而报错,那么其中之一的检查项,就是检查插件的node api版本。当然可以,没什么问题。

novaliang的专栏 1169

Electron9.x +vue+ffi-napi 调用Dll动态链接库

本文主要介绍在 Electron9.x 中,使用ffi-napi,ref-array-napi,ref-napi 加载 Windows 动态链接库,并在Vue 渲染进程中使用使用过程中会遇到一系列的坑,本文将会一一解决,并解释原因。如有同行兄弟遇到此问题可以借鉴。 这里列出所使用的环境: 作者:kaiwill 链接:https://www.jianshu.com/p/dd9463dead8c 来源:简书 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 Visual Studio 2

weixin_40450855的博客 3516

npm install ffi各种失败,换命令npm i ffi-napi成功

网上各种帖子安装ffi,基本上到了windows build tools这里会卡住。使用命令npm install --global --production windows-build-tools安装报错信息如下:npm ERR!npm ERR!npm ERR!npm ERR!npm ERR!npm ERR!npm ERR!npm ERR!npm ERR!npm ERR!npm ERR!npm ERR!npm ERR!

chduguxue的专栏 1万+

Node.js外国函数接口库:Node-FFI-NAPI深度解析

在开源社区的璀璨星河中,有一颗特别的明珠——Node-FFI-NAPI,这是一个专为Node.js设计的外国函数接口(Foreign Function Interface),允许开发者通过纯JavaScript调用动态链接库中的函数,无需深入C++的世界。本项目基于JavaScript,巧妙融合了C/C++世界的强大,是跨语言编程的一个典范。 ## 核心功能剖析 Node-FFI-NAPI的主

gitblog_01286的博客 625

nodejs ffi-napi调用go生成的dll(参数传递buffer类型,返回值也为buffer)

node-ffi使用纯JavaScript加载调用动态库的node addon,它可以用来在不写任何C++代码的情况下调用动态链接库的API 接口。ffi究竟干了什么?其实它本质上还是一个编译后的Node addon,node_modules/ffi/build/Release/ffi_bindings.nodeffi_bindings.node就是一个addon ffi充当了nodejsdll之间的桥梁。

a1309525802的博客 1538
上一篇: 使用Self-Signed Certificate构建HTTPS服务
下一篇: 使用pkg编译打包nodejs程序成执行文件
泉水老叮当
博客等级 码龄10年 9粉丝 39原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值