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.} |
练习
- 使用 c2nim 包装一个 C 库
- 编写一个调用 POSIX API 的系统工具
- 分析 Nim 生成的 C 代码
扩展阅读
← 上一章:性能优化 | 下一章:JavaScript 后端 →