Nim 完全指南 / 17 外部函数接口
第 17 章:外部函数接口(FFI)
17.1 调用 C 函数
17.1.1 importc 基础
# 直接导入 C 标准库函数
proc printf(formatstr: cstring) {.importc, varargs, header: "<stdio.h>".}
proc abs(x: cint): cint {.importc, header: "<stdlib.h>".}
printf("Hello from C! %d\n", 42)
echo abs(-10)
# 从特定库导入
proc cos(x: cdouble): cdouble {.importc, header: "<math.h>".}
proc sin(x: cdouble): cdouble {.importc, header: "<math.h>".}
echo cos(0.0) # 1.0
echo sin(3.14159 / 2) # ≈ 1.0
17.1.2 编译链接
# 链接动态库
proc zlibVersion(): cstring {.importc, dynlib: "libz.so".}
echo zlibVersion()
# 链接静态库
proc myFunc(): cint {.importc, linkc: "my_func".}
# 使用 {.passL.} 编译指示
{.passL: "-lm".}
proc pow(base, exp: cdouble): cdouble {.importc, header: "<math.h>".}
17.1.3 C 类型映射
| Nim 类型 | C 类型 |
|---|
cchar | char |
cschar | signed char |
cuchar | unsigned char |
cshort | short |
cint | int |
clong | long |
clonglong | long long |
cfloat | float |
cdouble | double |
cstring | char* |
pointer | void* |
proc strlen(s: cstring): csize_t {.importc, header: "<string.h>".}
echo strlen("Hello") # 5
17.2 包装 C 头文件
17.2.1 使用 c2nim
# 安装 c2nim
nimble install c2nim
# 转换 C 头文件
c2nim header.h -o header.nim
17.2.2 手动包装
# 包装 SQLite3
{.passL: "-lsqlite3".}
{.pragma: sqlite, header: "<sqlite3.h>".}
type
Sqlite3* {.sqlite.} = object
PSqlite3* = ptr Sqlite3
proc sqlite3_open(filename: cstring, ppDb: ptr PSqlite3): cint {.sqlite.}
proc sqlite3_close(db: PSqlite3): cint {.sqlite.}
proc sqlite3_exec(db: PSqlite3, sql: cstring,
callback: pointer, arg: pointer,
errmsg: ptr cstring): cint {.sqlite.}
proc sqlite3_free(p: pointer) {.sqlite.}
# 使用
var db: PSqlite3
let rc = sqlite3_open("test.db", addr db)
if rc == 0:
echo "Database opened"
var errMsg: cstring
discard sqlite3_exec(db, "CREATE TABLE IF NOT EXISTS t1(a,b)", nil, nil, addr errMsg)
discard sqlite3_close(db)
17.3 C++ 绑定
# 使用 cpp 后端
# nim cpp main.nim
{.push header: "<iostream>".}
type StdString {.importcpp: "std::string".} = object
proc initStdString(s: cstring): StdString {.importcpp: "std::string(@)".}
proc size(s: StdString): csize_t {.importcpp: "size".}
{.pop.}
# 使用 C++ 类
proc main() =
var s = initStdString("Hello C++!")
echo "Size: ", s.size()
main()
17.4 Python 绑定
# 使用 nimpy 模块
# nimble install nimpy
import nimpy
let os = pyImport("os")
echo os.getcwd()
let math = pyImport("math")
echo math.sqrt(144.0)
# 调用 Python 函数
let json = pyImport("json")
let data = json.loads("""{"name": "Nim", "version": 2}""")
echo data["name"]
# 从 Nim 导出给 Python
proc add(a, b: int): int {.exportpy.} =
a + b
17.5 实战示例
🏢 场景:使用 libcurl
{.passL: "-lcurl".}
{.pragma: curl, header: "<curl/curl.h>".}
type
CURL* {.curl.} = object
CURLcode = cint
const
CURLE_OK: CURLcode = 0
CURLOPT_URL = 10002
CURLOPT_WRITEFUNCTION = 20011
proc curl_easy_init(): ptr CURL {.curl.}
proc curl_easy_setopt(curl: ptr CURL, option: cint): CURLcode {.curl, varargs.}
proc curl_easy_perform(curl: ptr CURL): CURLcode {.curl.}
proc curl_easy_cleanup(curl: ptr CURL) {.curl.}
proc curl_global_init(flags: clong): CURLcode {.curl.}
proc writeCallback(buffer: cstring, size: csize_t,
nitems: csize_t, userdata: pointer): csize_t {.cdecl.} =
let data = newString(size * nitems)
copyMem(addr data[0], buffer, size * nitems)
cast[ptr string](userdata)[] &= data
result = size * nitems
proc fetchUrl(url: string): string =
discard curl_global_init(0)
let curl = curl_easy_init()
if curl == nil:
return ""
var response = ""
discard curl_easy_setopt(curl, CURLOPT_URL, url.cstring)
discard curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeCallback)
discard curl_easy_setopt(curl, 10001, addr response)
discard curl_easy_perform(curl)
curl_easy_cleanup(curl)
result = response
echo fetchUrl("https://httpbin.org/get")
🏢 场景:调用系统 API
# Linux 特定系统调用
when defined(linux):
proc getpid(): cint {.importc, header: "<unistd.h>".}
proc getuid(): cint {.importc, header: "<unistd.h>".}
echo "PID: ", getpid()
echo "UID: ", getuid()
# Windows API
when defined(windows):
proc MessageBoxA(hWnd: pointer, text, caption: cstring,
uType: uint32): int32 {.importc,
dynlib: "user32.dll".}
discard MessageBoxA(nil, "Hello from Nim!", "Nim FFI", 0)
本章小结
| 特性 | 用途 |
|---|
{.importc.} | 导入 C 函数 |
{.header.} | 指定头文件 |
{.dynlib.} | 动态链接库 |
{.exportc.} | 导出给 C 调用 |
{.exportpy.} | 导出给 Python |
| nimpy | Python 互操作 |
| c2nim | 自动生成绑定 |
练习
- 包装一个简单的 C 库(如 zlib)
- 使用 nimpy 调用 Python 的 requests 库
- 为 OpenSSL 创建基本的 Nim 绑定
扩展阅读
← 上一章:并发编程 | 下一章:Web 开发 →