npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@lakutata/ffi

v3.2.0

Published

Native libraries for the lakutata framework: C functions of .so, .dylib and .dll files called from Node.js, Electron and Bun, declared with the C declarations of their headers, a library being a component of the application.

Readme

@lakutata/ffi

English | 中文

lakutata 框架的动态库调用:在 Node.js、Electron 的主进程和 Bun 里调用 .so、.dylib、.dll 文件中的 C 函数,不需要编译任何东西。库用它头文件里的 C 声明来描述(结构体、联合、枚举、typedef、函数指针、原型、#define),每个库都是应用的一个组件。它基于 koffi。lakutata 包不重新导出这个包:请从 @lakutata/ffi 导入。

安装

npm install lakutata @lakutata/ffi
  • koffi 会连同各平台的预编译文件一起安装(每个平台一个包);npm 可能会提示它的安装脚本没有运行:不需要运行它。
  • 需要 Node.js >=18.10.0、Electron 的主进程或 Bun。它的 peer 依赖 @lakutata/core 会随 lakutata 一起安装。

用法

一个库

一个库就是 NativeLibrary 的一个子类:@Library 给出它的文件名(可以按平台分别写,也可以写一个名字,按各平台给库命名的方式补全)和它的声明,@Native 把一个函数绑到一个属性上,@NativeVariable 绑定一个变量。

import {Application} from 'lakutata'
import {buildNativeLibraryOptions, Library, Native, NativeLibrary, NativeVariable, type Out, type Pointer} from '@lakutata/ffi'

@Library({
    names: {darwin: 'libsqlite3.dylib', linux: 'libsqlite3.so.0', win32: 'winsqlite3.dll'},
    declarations: `
        typedef struct sqlite3 sqlite3;
        #define SQLITE_OK 0
        int sqlite3_close(sqlite3 *db);
    `
})
export class SQLite extends NativeLibrary {
    @Native('int sqlite3_open(const char *filename, _Out_ sqlite3 **db)')
    public readonly open: (filename: string, db: Out<Pointer>) => number

    //在线程池里执行;它给出的错误信息由 sqlite3_free 释放
    @Native('int sqlite3_exec(sqlite3 *db, const char *sql, void *callback, void *context, _Out_ char **error)', {async: true, free: 'sqlite3_free'})
    public readonly exec: (db: Pointer, sql: string, callback: null, context: null, error: Out<string>) => Promise<number>

    //声明里和属性同名的函数
    @Native()
    public readonly sqlite3_close: (db: Pointer) => number

    @NativeVariable('extern const char sqlite3_version[]')
    public readonly version: string
}

Application
    .run(() => ({
        id: 'store.app',
        name: 'Store',
        components: {
            sqlite: buildNativeLibraryOptions(SQLite)
        }
    }))
    .onLaunched(async (app: Application): Promise<void> => {
        const sqlite: SQLite = await app.getObject('sqlite')
        const db: Out<Pointer> = [null]
        if (sqlite.open(':memory:', db) !== Number(sqlite.constants.SQLITE_OK)) throw new Error('open failed')
        const error: Out<string> = [null]
        if (await sqlite.exec(db[0]!, 'CREATE TABLE t (id INTEGER)', null, null, error)) console.error(error[0])
        sqlite.sqlite3_close(db[0]!)
        console.log(sqlite.version)
    })
  • 到哪里找库:配置里的 path(给了就只试它),配置和 @Library 里 search 的目录,打包后应用的资源目录下的 native 目录,最后是系统找库的位置。c 表示各系统上的 C 标准库(包括 musl),m 表示它的数学函数。
  • 值的对应关系:数字对应 number;char * 对应字符串;指针和所有 64 位整数(size_t、long、int64_t 等)对应 bigint,声明时加上 {int64: 'number'} 就改为 number;结构体和联合对应对象,位域按名字读写;_Out_ 的整数或指针用只有一个元素的数组传入(Out<T>)。
  • 回调:给函数指针参数传入的 JS 函数只在这次调用期间有效;callback(type, fn) 生成一个库可以保存起来以后再调用的回调,它抛出的异常会成为组件的 error 事件。
  • 线程:异步函数默认在运行时的线程池里执行;在 @Library(或配置)里设置 threads 后,改在库自己的线程里执行。threads: 1 时,异步调用一个接一个执行,适合不是线程安全的库;有多个线程时,每个调用交给最空闲的线程。在这些线程里,可变参数的函数也可以异步调用。
  • 销毁:随应用一起销毁时,属性开始抛出异常,会等进行中的异步调用结束,回调全部释放,库被卸载。原生调用没法中途打断:过了 drainTimeout 还有调用没结束时,库连同它的回调和内存都保留下来。

不用容器

import {loadNativeLibrary, type NativeLibraryHandle} from '@lakutata/ffi'

const libc: NativeLibraryHandle = loadNativeLibrary('c', 'typedef struct { int quot; int rem; } div_t;')
const div = libc.func<(numerator: number, denominator: number) => {quot: number, rem: number}>('div_t div(int numerator, int denominator)')
const snprintf = libc.func<(buffer: Buffer, size: number, format: string, ...args: unknown[]) => number>('int snprintf(char *buffer, size_t size, const char *format, ...)')
console.log(div(17, 5))
//可变参数函数的额外参数,前面用 C 类型名写出类型
const buffer: Buffer = Buffer.alloc(32)
snprintf(buffer, buffer.length, '%s=%d', 'const char *', 'count', 'int', 42)
await libc.close()

根据头文件生成类

函数很多的库,可以用 lakutata 的命令行工具(@lakutata/cli)或 @lakutata/ffi/generator 的 generateNativeLibrary() 根据头文件生成类:

lakutata ffi generate /usr/include/sqlite3.h --class-name SQLite3 --out src/native/SQLite3.ts

头文件先经过 C 编译器的预处理器,保留库自己的声明,库给不了的函数会被去掉并列出来。之后请在生成的文件里把输出参数标成 _Out_,把会阻塞的函数声明为 {async: true},做法见指南:头文件看不出是哪些。

打包

lakutata package 打出的 bundle、可执行文件和 Electron 安装包都会带上 koffi 和本平台的预编译文件。要和应用一起发布的库放进资源里的 native 目录(在 package.json 里写 "lakutata": {"resources": [{"from": "vendor/lib", "to": "native"}]})。在 Alpine 上请用 Bun 打可执行文件(--runtime bun)。

API

| 导出 | 是什么 | |---|---| | NativeLibrary | 库的组件:函数和变量在它的属性上;func、variable、callback、read、write、sizeOf、offsetOf、alloc、free、errno、windowsError、constants、file、pending、threadCount;回调异常的 error 事件 | | buildNativeLibraryOptions、NativeLibraryConfig | 在 components 里注册它,以及它的选项:path、search、keepLoaded、drainTimeout、threads | | Library、LibraryOptions | 类装饰器:NativeLibrary 子类的库:names、search、declarations、crt、threads | | Native | 属性装饰器:一个函数(原型、声明里的名字,或者属性名)及其选项:async、free、int64 | | NativeVariable | 属性装饰器:库导出的一个变量 | | loadNativeLibrary、NativeLibraryHandle、NativeLibraryOptions | 不用容器时的库:func、function、variable、callback、declare、read、write、sizeOf、offsetOf、alloc、free、errno、windowsError、threads、ready、drain、close | | NativeFunctionOptions、NativeVariableAccessor、NativeCallback、LibraryNames、Pointer、Out | 函数的选项、变量、持久回调(pointer、release())、库的文件名、指针(bigint)、输出参数 | | readCString、readWString、readBuffer、viewBuffer、readValue、readArray、writeValue、allocMemory、freeMemory、sizeOf | 用内置类型读写原生内存(viewBuffer 不复制;Electron 里不能用) | | lastErrno、lastWindowsError | 上一次调用后的 errno(Windows 上是指定的 C 运行库的 errno),以及 Windows API 的错误 | | generateNativeLibrary、defaultCompiler(@lakutata/ffi/generator) | 根据头文件生成库的类(它的代码、声明,以及去掉了哪些),以及默认运行的 C 编译器 |

类型声明里有每个导出的说明和示例。

错误

| 错误 | 什么时候 | |---|---| | NativeDeclarationException | 声明或原型读不了(带行号和列号:类型没有声明、#if、带参数的宏、long double 等)、库里没有这个函数、传了 va_list、类上没有 @Library | | NativeLibraryNotFoundException | 库加载不了:消息里列出试过的每个文件和原因 | | NativeLibraryClosedException | 库关闭后又使用它的函数、变量或回调;库关闭时,它的线程里还没结束的调用 | | NativeMemoryException | 给内存函数传了空指针或负的长度、在 Electron 里用 viewBuffer | | TypeError(来自 koffi) | 参数类型不对 | | RangeError | 声明了 {int64: 'number'} 的函数遇到超出安全整数范围的 64 位整数 |

C 函数的原型写错,或者传了错误的指针,整个进程都会退出,任何异常处理都拦不住。

另见

  • 网站上的指南:FFI。
  • lakutata 包里的 doc/zh/FFI.md(英文版在 doc/en):动态库调用指南。
  • lakutata 包里的 doc/zh/NativeAddons.md:用 C++ 或 Rust 编写的原生插件,用于 C 函数库做不到的情况。
  • koffi:这个包所基于的 FFI。
  • @lakutata/core(lakutata):组件、Application 和依赖注入。