使用 template/macro 在 nimlang 中批量创建 C 接口?

batch created C interface in nimlang with template/macro?

背景

其实我在移植FLTK C 1.3.3 for FreeBASIC to nimlang. Please note FLTK C 1.3.3 for FreeBASIC is a C interface upon the FLTK in CPP

DLL中的很多函数都满足相同的类似名称格式,例如

#inclib "fltk-c-1.3.3-64" ' Windows 64-bit

function Fl_ButtonExNew (byval x as long, byval y as long, byval w as long, byval h as long, byval title as const zstring ptr=0) as Fl_ButtonEx Ptr
sub Fl_ButtonExDelete(byval x as Fl_ButtonEx ptr)

function Fl_BoxExNew (byval x as long, byval y as long, byval w as long, byval h as long, byval title as const zstring ptr=0) as Fl_BoxEx Ptr
sub Fl_BoxExDelete(byval x as Fl_BoxEx ptr)

and so on

似乎delcared function/sub会自动加载fltk-c-1.3.3-64.dll中的同名函数(如有错误请指正)

FreeBASIC 中的解决方案

所以在 FreeBASIC 头文件 fltk-main.bi 中,有一个辅助宏

#macro DeclareEx(_name_)
declare function _name_##ExNew(byval x as long, byval y as long, byval w as long, byval h as long, byval title as const zstring ptr=0) as _name_##Ex ptr
declare sub      _name_##ExDelete         (byref ex as _name_##Ex ptr)
#endmacro

借助于它,上面的代码(以及许多其他代码)可以通过简单的一行代码生成:

DeclareEx(Fl_Button)
DeclareEx(Fl_Box)

我在 nimlang 中的问题

在nimlang中(请暂时忽略数字类型转换),以上代码可以手译为

const fltk = "fltk-c-1.3.3-64.dll"
type long = int64
proc Fl_ButtonExNew (x: long, y: long, w: long, h: long, title: cstring=nil): Ptr Fl_ButtonEx {.cdecl, importc: "Fl_ButtonExNew", dynlib: fltk, discardable.}
proc Fl_ButtonExDelete(x: ptr Fl_ButtonEx) {.cdecl, importc: "Fl_ButtonExDelete", dynlib: fltk, discardable.}

proc Fl_BoxExNew (x: long, y: long, w: long, h: long, title: cstring=nil): Ptr Fl_BoxEx {.cdecl, importc: "Fl_BoxExNew", dynlib: fltk, discardable.}
proc Fl_BoxExDelete(x: ptr Fl_BoxEx) {.cdecl, importc: "Fl_BoxExDelete", dynlib: fltk, discardable.}

所以我试着模仿 FreeBASIC 宏的作用

const fltk = "fltk-c-1.3.3-64.dll"
type long = int64

template DeclareEx*(name: untyped) {.dirty.}=
    type `name Ex` = object
    type `name ExNew`* = proc(x: long, y: long, w: long, h: long, title: cstring=nil): ptr `name Ex` {.cdecl, importc: "name New", dynlib: fltk, discardable.}
    type `name ExDelete`*  = proc(ex: ptr `name Ex`) {.cdecl, importc: "name ExDelete", dynlib: fltk, discardable.}

DeclareEx(Fl_Button)

但是当我编译它时,我得到

d.nim(9, 10) template/generic instantiation of DeclareEx from here

d.nim(6, 118) Error: invalid pragma: importc: "name New"

那么,有什么解决办法吗?谢谢

importc pragma 用于从 C 导入过程或变量。但是,您正在编写 importc: "name New",而 name New 在 C 中不是有效标识符,因为它有一个space 在里面。您需要尝试将传递到模板中的变量的符号名称与该附加字符串后缀连接起来。

不过,我不知道如何在模板中执行此操作。也许您可以传递一个字符串作为参数,然后使用 & 运算符将其与 importc.

的后缀连接起来

模板中提供的反引号插值只能用于需要标识符的位置。 importc pragma 需要一个常量字符串表达式。您可以使用 astToStr 魔法将任何 AST 输入转换为其相应的字符串表示,这是此处解决方案的关键:

const fltk = "fltk-c-1.3.3-64.dll"
type long = int64

template DeclareEx*(name: untyped) =
  const
    newProc = astToStr(name) & "New"
    deleteProc = astToStr(name) & "ExDelete"

  type `name Ex`* {.inject.} = object
  proc `name ExNew`*(x: long, y: long, w: long, h: long, title: cstring=nil): ptr `name Ex` {.cdecl, inject, importc: newProc, dynlib: fltk, discardable.}
  proc `name ExDelete`* (ex: ptr `name Ex`) {.cdecl, inject, importc: deleteProc, dynlib: fltk, discardable.}

DeclareEx(Fl_Button)
DeclareEx(Fl_Window)

var btn: ptr Fl_ButtonEx
Fl_ButtonExDelete(btn)