新闻详情

新闻详情

首页 / 资讯中心 / 详情

Erlang/OTP NIF 实战指南:从 C 到 Erlang 的原生函数调用

发布时间:2026/9/25 5:47:23来源:尧图网络
Erlang/OTP NIF 实战指南:从 C 到 Erlang 的原生函数调用
编程语言语言运行时标准库编译器并发编程【免费下载链接】otpErlang/OTP项目地址https://gitcode.com/gh_mirrors/ot/otp点击查看免费下载本文是 Erlang/OTP 互操作教程中NIFNative Implemented Functions部分的完整实战指南。NIF 让你能够把 C 代码以共享库UNIX 下的.so、Windows 下的.dll的形式动态链接进 Erlang 运行时使 Erlang 模块中的函数直接由 C 实现。读完本文你将掌握 NIF 的适用场景、Erlang 侧模块与 C 侧 NIF 库的完整写法、ERL_NIF_INIT宏与类型转换 API 的用法以及编译、加载、运行和排查错误的全流程。NIF 是什么定位与适用场景NIF 是 Erlang/OTP 运行时内置的三种互操作机制之一另外两种是分布式 Erlang 和端口 Port也是端口驱动linked-in driver的一种更简单、更高效的替代方案。从 互操作教程概览 可以看到NIF 让你在需要与操作系统或外部库交互时为普通 Erlang 函数提供 C 实现。NIF 最适用于同步函数——例如教程中的foo和bar——即那些计算量相对较小、没有副作用、直接返回结果的函数。与端口驱动相比的取舍NIF 库被动态链接进 emulator 进程本身因此它是与端口驱动并列的从 Erlang 调用 C 代码最快的方式优点调用 NIF 不需要上下文切换context switch没有进程间通信开销代价它也是最不安全的方式——NIF 中的一次崩溃如段错误会直接拖垮整个 emulator而不是像端口那样只影响外部 OS 进程。教程概览 对此给出了明确的实践建议由于有缺陷的 NIF 可能导致内存泄漏、挂起、崩溃甚至敏感信息泄露如果端口方案可行优先使用外部端口只有当端口通信的开销不可接受时才用 NIF 与原生代码C、C 或 Rust交互。从问题出发要解决的示例整个教程围绕一个具体问题展开详见 Problem Example假设你有两个简单的 C 函数希望从 Erlang 直接调用/* complex.c */ int foo(int x) { return x1; } int bar(int y) { return y*2; }从 Erlang 的角度最好能像调用普通 Erlang 函数一样调用它们把 C 通信细节隐藏在模块实现内部% Erlang code ... Res complex:foo(X), ...完整源码见仓库中的 complex.c。下文展示如何用 NIF 实现这个complex模块。Erlang 侧程序模块、stub 与加载即使一个模块的所有函数都是 NIF也仍然必须有一个 Erlang 模块原因有二NIF 库必须由同一模块中的 Erlang 代码显式加载模块中的所有 NIF 都必须有对应的 Erlang 实现——通常是最小的 stub 实现负责抛出异常它们也可以作为某些架构上没有原生实现时的回退实现fallback。NIF 库通过erlang:load_nif/2加载第一个参数是共享库的名字第二个参数可以是任意 term会被原样传给库并用于初始化。教程中的complex6模块完整源码见 complex6.erl演示了标准写法-module(complex6). -export([foo/1, bar/1]). -nifs([foo/1, bar/1]). -on_load(init/0). init() - ok erlang:load_nif(./complex6_nif, 0). foo(_X) - erlang:nif_error(nif_library_not_loaded). bar(_Y) - erlang:nif_error(nif_library_not_loaded).关键点逐条拆解-on_load(init/0)指令模块被加载时自动调用init/0。如果init返回的不是ok例如本例中 NIF 库加载失败模块会被卸载对其内部函数的调用都会失败erlang:load_nif/2第二个参数0是传给库的初始化信息示例中未使用实际项目中可传入配置项供 C 侧load回调通过load_info参数读取stub 实现加载 NIF 库成功后stub 实现被覆盖foo和bar的调用转而分派到 C 侧的 NIF 实现若库尚未加载或加载失败stub 会通过erlang:nif_error(nif_library_not_loaded)抛出异常明确提示 NIF 库未就绪-nifs([foo/1, bar/1])属性从示例模块可以看出用该属性声明模块内的 NIF 函数列表让编译器在编译期就知道这些函数的 NIF 属性。仓库中另一个官方示例 matrix_nif.erl 还展示了一种更紧凑的 stub 写法用宏-define(nif, nif_error(?LINE))让每个 stub 抛出的错误自动带上源码行号便于调试。NIF 库代码C 侧实现模块的 NIF 被编译并链接进一个共享库。每个 NIF 都是一个普通的 C 函数通过ERL_NIF_INIT宏配合一个结构体数组声明模块中所有 NIF 的名字、元数arity和函数指针。C 侧必须包含头文件erl_nif.h并且由于共享库是模块而非程序不能包含main函数。教程中的 NIF 库完整源码见 complex6_nif.c#include erl_nif.h extern int foo(int x); extern int bar(int y); static ERL_NIF_TERM foo_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { int x, ret; if (!enif_get_int(env, argv[0], x)) { return enif_make_badarg(env); } ret foo(x); return enif_make_int(env, ret); } static ERL_NIF_TERM bar_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { int y, ret; if (!enif_get_int(env, argv[0], y)) { return enif_make_badarg(env); } ret bar(y); return enif_make_int(env, ret); } static ErlNifFunc nif_funcs[] { {foo, 1, foo_nif}, {bar, 1, bar_nif} }; ERL_NIF_INIT(complex6, nif_funcs, NULL, NULL, NULL, NULL)理解 NIF 函数的签名与参数每个 NIF 函数的固定形态是static ERL_NIF_TERM foo_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])env环境参数一个不透明句柄必须传给绝大多数 NIF API 函数。它封装了调用方 Erlang 进程的相关信息是enif_*系列 API 正确工作的前提argc/argv函数实参以数组argv传入argc是数组长度也就是函数的元数。函数的第 N 个实参通过argv[N-1]访问。本例中两个函数都是单参数因此只用到argv[0]返回值函数实参与返回值都用ERL_NIF_TERM类型表示。类型转换 APIenif_get_int和enif_make_int用于在 Erlang term 与 C 的int类型之间互相转换enif_get_int(env, term, x)尝试把 term 解析为整数并写入x。如果argv[0]不是整数返回false0enif_make_int(env, ret)把 C 的int包装成ERL_NIF_TERM返回给 Erlangenif_make_badarg(env)构造一个badarg异常。当参数类型不符时调用它NIF 会以抛出bad argument异常的方式返回——这正是教程运行时示例中complex6:foo(not an integer)触发异常的原因。enif_*API 家族覆盖了整数、浮点、二进制、列表、元组、原子、资源对象等几乎所有 Erlang 类型的双向转换具体函数清单见 erl_nif 参考文档。ERL_NIF_INIT宏的参数ERL_NIF_INIT是每个 NIF 库必须且只能出现一次的宏其六个参数如下第一个参数Erlang 模块的名字必须以 C 标识符形式给出。宏会把它字符串化为字符串字面量stringified用于在运行时匹配加载它的 Erlang 模块第二个参数ErlNifFunc结构体数组每个元素包含 NIF 的名字、元数和函数指针其余四个参数指向回调函数的指针按序为LOAD、RELOAD、UPGRADE、UNLOAD用于初始化库如打开资源类型、热升级等场景。本例未使用全部传NULL。从宏的实际定义见 erl_nif.h可以进一步看到ERL_NIF_INIT会生成一个返回ErlNifEntry的_nif_init导出函数其中自动填写ERL_NIF_MAJOR_VERSION/ERL_NIF_MINOR_VERSIONNIF API 版本号运行时据此做版本兼容检查——这也是 NIF API 有版本演进机制的直接证据erts 的测试目录 nif_SUITE_data 中保留了不同 API 版本的头文件副本可供对照#NAME模块名的字符串化形式sizeof(FUNCS) / sizeof(*FUNCS)自动计算的 NIF 函数个数指向FUNCS数组以及LOAD, RELOAD, UPGRADE, UNLOAD四个回调的指针。因此 C 侧程序员只需提供函数实现与ErlNifFunc数组库的入口与版本信息均由宏自动生成。编译与运行完整示例第一步编译 C 代码为共享库UNIXLinux/macOS下使用gcc-fpic生成位置无关代码共享库必需-shared链接为动态库unix gcc -o complex6_nif.so -fpic -shared complex.c complex6_nif.cWindows 下使用 MSVC 的cl-LD表示生成 DLL-MD链接动态 C 运行时-Fe指定输出文件名windows cl -LD -MD -Fe complex6_nif.dll complex.c complex6_nif.c第二步启动 Erlang 并编译 Erlang 模块 erl Erlang R13B04 (erts-5.7.5) [64-bit] [smp:4:4] [rq:4] [async-threads:0] [kernel-poll:false] Eshell V5.7.5 (abort with ^G) 1 c(complex6). {ok,complex6}编译complex6.erl的同时触发-on_loaderlang:load_nif(./complex6_nif, 0)被自动执行NIF 库被链接进运行时并覆盖 stub。需要注意示例中加载路径是相对路径./complex6_nif实际项目中建议使用绝对路径或code:priv_dir/1得到的应用私有目录避免工作目录不同导致加载失败。第三步运行并验证3 complex6:foo(3). 4 4 complex6:bar(5). 10 5 complex6:foo(not an integer). ** exception error: bad argument in function complex6:foo/1 called as comlpex6:foo(not an integer)foo(3)由 C 侧计算31返回4bar(5)计算5*2返回10与 complex.c 中的纯 C 逻辑完全一致而当传入非整数时enif_get_int返回falseNIF 通过enif_make_badarg抛出bad argument异常——错误处理路径与 C 侧代码一一对应。进阶用资源对象承载有状态的 NIF基础示例中 NIF 是无状态的纯函数。当 NIF 需要持有跨调用存活的结构化数据如句柄、缓冲、对象时教程之外的标准做法是使用NIF 资源对象resource objects。仓库中 matrix_nif.c 与 matrix_nif.erl 是官方配套的完整矩阵计算示例展示了该模式的全部要素资源类型注册在load回调即ERL_NIF_INIT的第三个参数中调用enif_open_resource_type注册资源类型并传入析构函数matrix_dtor其返回值存放于priv_data供后续 NIF 使用资源分配与包装enif_alloc_resource分配 C 结构体内存enif_make_resource把它包装成ERL_NIF_TERM返回给 Erlang之后由enif_get_resource从参数中还原 C 指针示例用联合体mx_t做指针类型转换以避免严格别名警告析构与生命周期enif_release_resource与enif_alloc/enif_free配对使用matrix_dtor负责释放Matrix-data——资源对象在最后一个 Erlang 引用消失时自动回收把内存管理交给了 NIF API列表/元组构建enif_get_list_cell、enif_make_list_cell、enif_make_tuple2等用于在 Erlang 列表、元组与 C 结构之间转换create/3、pos/3、add/2、size_of/1、to_term/1五个 NIF 完整演示了入参校验非法参数统一走badarg标签、矩阵遍历与结果构造。该示例的 Erlang 侧与complex6同构-nifs声明、-on_loaderlang:load_nif(./matrix_nif, 0)stub 则用宏展开为带行号的erlang:error/1调用。注意事项与安全边界在决定使用 NIF 前请务必牢记以下边界均出自本仓库官方文档稳定性风险NIF 运行在 emulator 进程内任何内存错误都会导致整个 Erlang 节点崩溃或数据损坏且可能泄露敏感信息见 教程概览 的警告调度器阻塞教程中的 NIF 都是短计算。若 NIF 执行耗时较长如大文件 I/O、复杂加密、等待外部资源会阻塞调度器上的普通任务。官方文档明确提到可借助dirty schedulers脏调度器处理此类长时间工作见 erl_nif 参考文档 中 dirty NIF 相关章节但这需要将 NIF 声明为脏 NIF而非默认的普通 NIF适用取舍能承受端口开销时优先用端口Port隔离故障域只有开销不可接受时才引入 NIF。NIF 适合的是foo、bar这类同步、短小、无副作用的计算函数加载失败处理erlang:load_nif/2失败会让模块卸载、所有函数调用失败因此 stub 中的erlang:nif_error/1异常信息如nif_library_not_loaded是排查加载问题的第一手线索。参考资料与深入学习路径本教程上下文互操作教程导言 与 互操作机制概览问题定义Problem Example本示例完整源码complex.c、complex6.erl、complex6_nif.c资源对象进阶示例matrix_nif.c、matrix_nif.erlNIF API 权威参考erl_nif 参考文档类型转换、资源对象、脏调度器、回调机制等头文件与宏实现erl_nif.hERL_NIF_INIT定义见 L422-L444赞分享编程语言语言运行时标准库编译器并发编程【免费下载链接】otpErlang/OTP项目地址https://gitcode.com/gh_mirrors/ot/otp点击查看免费下载相关推荐Erlang/OTP 互操作性教程用 Port、NIF 与 C Node 打通 Erlang 与 C 的调用链路Erlang/OTP 互操作性教程用 Port、NIF 与 C Node 打通 Erlang 与 C 的调用链路 导读 Erlang/OTP 虽然自带强大的并编程语言语言运行时标准库编译器并发编程Erlang/OTP 互操作性指南Distributed Erlang、Port、NIF 与 C/Java 库全解析Erlang/OTP 互操作性指南Distributed Erlang、Port、NIF 与 C/Java 库全解析 Erlang/OTP 提供了多种与其他编编程语言语言运行时标准库编译器并发编程Erlang/OTP 中的 SHA-256 可移植 C 实现从算法原理到 Yielding NIF 集成Erlang/OTP 中的 SHA 256 可移植 C 实现从算法原理到 Yielding NIF 集成 本篇文章围绕 Erlang/OTP 仓库中 erts编程语言语言运行时标准库编译器并发编程上一篇OpenBLAS未来发展方向AI加速和量子计算的前沿探索下一篇如何快速部署zapret-discord-youtube-linux系统自动化测试环境管理创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

2026年AI Agent工具接入终极指南:treg如何把60+供应商API变成按次计费 2026/9/25 6:26:56

2026年AI Agent工具接入终极指南:treg如何把60+供应商API变成按次计费

2026年AI Agent工具接入终极指南:treg如何把60供应商API变成按次计费 【免费下载链接】treg OpenRouter for agent tools. Join community here: https://discord.gg/6mQYYfFMAn 项目地址: https://gitcode.com/GitHub_Trending/treg/treg treg 是一个开源的…

阅读更多 →
Edge浏览器内置冲浪游戏:入口、玩法与背后的产品逻辑 2026/9/25 6:26:56

Edge浏览器内置冲浪游戏:入口、玩法与背后的产品逻辑

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
转行汽车电子,为什么我建议从电机控制开始?附完整学习路线与书单 2026/9/25 6:26:50

转行汽车电子,为什么我建议从电机控制开始?附完整学习路线与书单

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
华为Pura X View游戏助手智能光影技术解析与调优指南 2026/9/25 6:26:50

华为Pura X View游戏助手智能光影技术解析与调优指南

1. 华为 Pura X View 游戏助手这次更新到底动了谁的蛋糕华为 Pura X View 游戏助手新增《原神》智能光影功能,这条消息在手游圈和鸿蒙生态开发者群里传开的时候,我第一反应不是"又一个画质增强",而是华为终于把手伸进了移动端游戏渲…

阅读更多 →
STM32在AI聊天机器人中的实时控制与物理世界落地 2026/9/25 6:26:50

STM32在AI聊天机器人中的实时控制与物理世界落地

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
海光1000系列处理器发布:C86架构打造轻量级端侧CPU 2026/9/25 6:26:44

海光1000系列处理器发布:C86架构打造轻量级端侧CPU

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉