强曰为道

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

22 C 后端深入

第 22 章:C 后端深入

22.1 生成的 C 代码

Nim 编译器将 Nim 代码翻译为 C 代码,然后由系统 C 编译器(GCC/Clang)编译为机器码。

22.1.1 查看生成的 C 代码

# 查看生成的 C 代码位置
nim c --nimcache:nimcache main.nim
ls nimcache/

# 查看主文件
cat nimcache/@mmain.nim.c

# 使用 genScript 生成独立的 C 文件
nim c --genScript main.nim

22.1.2 代码结构分析

# example.nim
type
  Person = object
    name: string
    age: int

proc greet(p: Person): string =
  "Hello, " & p.name & "! You are " & $p.age & " years old."

let p = Person(name: "Alice", age: 30)
echo greet(p)

生成的 C 代码会包含:

// 类型定义
struct Person {
  NimStringV2 name;
  NI age;
};

// 过程实现
N_LIB_PRIVATE N_NIMCALL(NimStringV2, greet__example_10)(Person* p) {
  NimStringV2 result;
  // ... 字符串拼接实现
  return result;
}

// 入口点
int main(int argc, char** argv) {
  // ... 初始化
  Person p;
  p.name = TM__xxx_2;  // "Alice"
  p.age = 30;
  NimStringV2 T1_ = greet__example_10((&p));
  echoBinSafe((&T1_), ...);
  // ... 清理
  return 0;
}

22.2 与 C 库集成

22.2.1 包装 C 头文件

# 包装 OpenSSL
{.passL: "-lssl -lcrypto".}
{.pragma: ssl, header: "<openssl/ssl.h>".}
{.pragma: ssl, header: "<openssl/err.h>".}

type
  SSL_CTX {.ssl.} = object
  SSL {.ssl.} = object
  PSSL_CTX = ptr SSL_CTX
  PSSL = ptr SSL

proc SSL_CTX_new(method: pointer): PSSL_CTX {.ssl.}
proc SSL_new(ctx: PSSL_CTX): PSSL {.ssl.}
proc SSL_connect(ssl: PSSL): cint {.ssl.}
proc SSL_read(ssl: PSSL, buf: pointer, num: cint): cint {.ssl.}
proc SSL_write(ssl: PSSL, buf: pointer, num: cint): cint {.ssl.}
proc SSL_free(ssl: PSSL) {.ssl.}
proc SSL_CTX_free(ctx: PSSL_CTX) {.ssl.}

22.2.2 使用 c2nim 自动生成

# 安装 c2nim
nimble install c2nim

# 转换 C 头文件
c2nim sqlite3.h --out sqlite3.nim

# 转换并添加前缀
c2nim --prefix:sqlite3_ sqlite3.h --out sqlite3.nim

22.2.3 手动包装示例:SQLite

{.passL: "-lsqlite3".}
{.pragma: sqlite, header: "<sqlite3.h>".}

type
  Sqlite3 {.sqlite.} = object
  PSqlite3 = ptr Sqlite3
  
  ExecCallback = proc(arg: pointer, ncols: cint, 
                       values, names: cstringArray): cint {.cdecl.}

const
  SQLITE_OK* = 0
  SQLITE_ROW* = 100
  SQLITE_DONE* = 101

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: ExecCallback, arg: pointer,
                   errmsg: ptr cstring): cint {.sqlite.}
proc sqlite3_prepare_v2(db: PSqlite3, sql: cstring, nByte: cint,
                         ppStmt: ptr pointer, pzTail: ptr cstring): cint {.sqlite.}
proc sqlite3_step(stmt: pointer): cint {.sqlite.}
proc sqlite3_column_text(stmt: pointer, iCol: cint): cstring {.sqlite.}
proc sqlite3_column_int(stmt: pointer, iCol: cint): cint {.sqlite.}
proc sqlite3_finalize(stmt: pointer): cint {.sqlite.}
proc sqlite3_free(p: pointer) {.sqlite.}

# 使用
var db: PSqlite3
let rc = sqlite3_open(cstring("test.db"), addr db)
if rc == SQLITE_OK:
  echo "Database opened!"
  var errMsg: cstring
  discard sqlite3_exec(db, "CREATE TABLE IF NOT EXISTS users(id INTEGER, name TEXT)", nil, nil, addr errMsg)
  discard sqlite3_close(db)

22.3 编译指示(Pragmas)

22.3.1 常用编译指示

# importc - 导入 C 函数
proc printf(fmt: cstring) {.importc, varargs, header: "<stdio.h>".}

# exportc - 导出给 C 调用
proc myNimFunc(): cint {.exportc, dynlib.} = 42

# header - 指定头文件
{.header: "<math.h>".}
proc sqrt(x: cdouble): cdouble {.importc.}

# dynlib - 动态链接
proc zlibVersion(): cstring {.importc, dynlib: "libz.so".}

# passC / passL - 传递编译/链接标志
{.passC: "-I/usr/include/mysql".}
{.passL: "-lmysqlclient".}

# 编译期条件
when defined(linux):
  {.passL: "-lrt".}
when defined(macosx):
  {.passL: "-framework CoreFoundation".}

22.3.2 代码生成控制

# align - 内存对齐
type
  AlignedStruct = object
    x: int32
    y: int32
  {.align: 16.}

# packed - 紧凑布局(无填充)
type
  PackedStruct {.packed.} = object
    a: uint8
    b: uint32
    c: uint8

# union - C 联合体
type
  Value {.union.} = object
    intVal: int32
    floatVal: float32
    bytes: array[4, uint8]

22.4 内存布局与指针

# 指针操作
var x = 42
var p = addr x
echo p[]     # 42(解引用)
p[] = 100
echo x       # 100

# cast 转换
let rawPtr = cast[pointer](p)
let intPtr = cast[ptr int](rawPtr)
echo intPtr[]  # 100

# 分配原始内存
var buf = alloc(1024)  # 分配 1024 字节
cast[ptr int](buf)[] = 42
echo cast[ptr int](buf)[]  # 42
dealloc(buf)

# unsafeAddr - 获取不可靠地址
proc dangerous(p: ptr int) =
  echo p[]

var val = 10
dangerous(unsafeAddr val)  # 可能不安全

22.5 与 C++ 集成

# 使用 C++ 后端
# nim cpp main.nim

{.push header: "<vector>".}
type
  CppVector[T] {.importcpp: "std::vector".} = object

proc newCppVector[T](): CppVector[T] {.importcpp: "std::vector<'*0>()", constructor.}
proc size[T](v: CppVector[T]): csize_t {.importcpp: "size".}
proc pushBack[T](v: var CppVector[T], val: T) {.importcpp: "push_back".}
proc `[]`[T](v: CppVector[T], i: csize_t): T {.importcpp: "#[#]".}
{.pop.}

var v = newCppVector[int]()
v.pushBack(1)
v.pushBack(2)
v.pushBack(3)
echo v.size()   # 3
echo v[0]       # 1

22.6 实战示例

🏢 系统工具:直接调用 Linux API

import std/[posix, strformat]

# 直接使用 POSIX API
proc getSystemInfo() =
  var uts: Utsname
  if uname(addr uts) == 0:
    echo &"System:  {cast[cstring](addr uts.sysname)}"
    echo &"Node:    {cast[cstring](addr uts.nodename)}"
    echo &"Release: {cast[cstring](addr uts.release)}"
    echo &"Version: {cast[cstring](addr uts.version)}"
    echo &"Machine: {cast[cstring](addr uts.machine)}"

proc getCPUInfo() =
  let cpuinfo = readFile("/proc/cpuinfo")
  var count = 0
  for line in cpuinfo.splitLines():
    if line.startsWith("model name"):
      inc count
      if count == 1:
        echo &"CPU: {line.split(':')[1].strip()}"
  echo &"Cores: {count}"

proc getMemoryInfo() =
  let meminfo = readFile("/proc/meminfo")
  for line in meminfo.splitLines():
    if line.startsWith("MemTotal"):
      echo &"Memory: {line.split(':')[1].strip()}"
      break

getSystemInfo()
getCPUInfo()
getMemoryInfo()

本章小结

特性说明
生成位置~/.cache/nim/
查看代码nim c --genScript
导入 C{.importc.}
导出 C{.exportc.}
指针ptr T, addr, cast
动态库{.dynlib.}

练习

  1. 使用 c2nim 包装一个 C 库
  2. 编写一个调用 POSIX API 的系统工具
  3. 分析 Nim 生成的 C 代码

扩展阅读


上一章:性能优化 | 下一章:JavaScript 后端