强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

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 类型
ccharchar
cscharsigned char
cucharunsigned char
cshortshort
cintint
clonglong
clonglonglong long
cfloatfloat
cdoubledouble
cstringchar*
pointervoid*
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
nimpyPython 互操作
c2nim自动生成绑定

练习

  1. 包装一个简单的 C 库(如 zlib)
  2. 使用 nimpy 调用 Python 的 requests 库
  3. 为 OpenSSL 创建基本的 Nim 绑定

扩展阅读


上一章:并发编程 | 下一章:Web 开发