ImportCpp编译指示

注意: c2nim 可以解析C++子集并且知道 importcpp 编译指示模式语言。 没有必要知道这里描述的所有细节。

importc pragma for C 类似, importcpp 编译指示一般可以用于引入 C++ 方法或C++符号。 生成的代码会使用C++方法调用的语法: obj->method(arg)

结合 headeremit 编译指示,这允许与C++库的 草率 接口:

  1. # 和C++引擎对接的反例... ;-)
  2.  
  3. {.link: "/usr/lib/libIrrlicht.so".}
  4.  
  5. {.emit: """
  6. using namespace irr;
  7. using namespace core;
  8. using namespace scene;
  9. using namespace video;
  10. using namespace io;
  11. using namespace gui;
  12. """.}
  13.  
  14. const
  15. irr = "<irrlicht/irrlicht.h>"
  16.  
  17. type
  18. IrrlichtDeviceObj {.header: irr,
  19. importcpp: "IrrlichtDevice".} = object
  20. IrrlichtDevice = ptr IrrlichtDeviceObj
  21.  
  22. proc createDevice(): IrrlichtDevice {.
  23. header: irr, importcpp: "createDevice(@)".}
  24. proc run(device: IrrlichtDevice): bool {.
  25. header: irr, importcpp: "#.run(@)".}

需要告诉编译器生成C++(命令 cpp )才能使其工作。 当编译器发射C++代码时,会定义条件符号 cpp

命名空间

草率接口 示例使用 .emit 来生成 using namespace 声明。 通过 命名空间::标识符 符号来引用导入的名称通常要好得多:

  1. type
  2. IrrlichtDeviceObj {.header: irr,
  3. importcpp: "irr::IrrlichtDevice".} = object

枚举Importcpp

importcpp 应用于枚举类型时,数字枚举值用C++枚举类型注释, 像这个示例: ((TheCppEnum)(3)) 。 (事实证明这是实现它的最简单方法。)

过程Importcpp

请注意,procs的 importcpp 变体使用了一种有点神秘的模式语言,以获得最大的灵活性:

  • 哈希 # 符号被第一个或下一个参数替换。哈希 #. 后面的一个点表示该调用应使用C++的点或箭头表示法。

  • 符号 @ 被剩下的参数替换,用逗号分隔。示例:

  1. proc cppMethod(this: CppObj, a, b, c: cint) {.importcpp: "#.CppMethod(@)".}
  2. var x: ptr CppObj
  3. cppMethod(x[], 1, 2, 3)

生成:

  1. x->CppMethod(1, 2, 3)

作为一个特殊的规则来保持与旧版本的 importcpp 编译指示的向后兼容性,如果没有特殊的模式字符(任何一个 #'@ ),那么认为是C++的点或箭头符号,所以上面的例子也可以写成:

  1. proc cppMethod(this: CppObj, a, b, c: cint) {.importcpp: "CppMethod".}

请注意,模式语言自然也涵盖了C++的运算符重载功能:

  1. proc vectorAddition(a, b: Vec3): Vec3 {.importcpp: "# + #".}
  2. proc dictLookup(a: Dict, k: Key): Value {.importcpp: "#[#]".}
  • 上标点 ' 后跟0..9范围的整数 i 被第i个形参类型替换。 第0位是结果类型。这可以用于将类型传递给C++函数模板。 在 ' 和数字之间可以使用星号来获得该类型的基类型。 (所以它从类型中“拿走了一颗星”; T * 变为 T 。) 可以使用两颗星来获取元素类型的元素类型等。示例:
  1. type Input {.importcpp: "System::Input".} = object
  2. proc getSubsystem*[T](): ptr T {.importcpp: "SystemManager::getSubsystem<'*0>()", nodecl.}
  3.  
  4. let x: ptr Input = getSubsystem[Input]()

生成:

  1. x = SystemManager::getSubsystem<System::Input>()
  • #@ 是一个支持 cnew 操作的特例。 这是必需的,以便直接内联调用表达式,而无需通过临时位置。 这只是为了规避当前代码生成器的限制。

例如,C++的 new 运算符可以像这样“导入”:

  1. proc cnew*[T](x: T): ptr T {.importcpp: "(new '*0#@)", nodecl.}
  2.  
  3. # 'Foo'构造函数:
  4. proc constructFoo(a, b: cint): Foo {.importcpp: "Foo(@)".}
  5.  
  6. let x = cnew constructFoo(3, 4)

生成:

  1. x = new Foo(3, 4)

但是,根据用例,new Foo 也可以这样包装:

  1. proc newFoo(a, b: cint): ptr Foo {.importcpp: "new Foo(@)".}
  2.  
  3. let x = newFoo(3, 4)

封装构造函数

有时候C++类有一个私有的复制构造函数,因此不能生成像 Class c = Class(1,2); 这样的代码,而是 Class c(1,2); 。 为此,包含C ++构造函数的Nim proc需要使用 构造函数 编译器。 这个编译指示也有助于生成更快的C++代码,因为构造然后不会调用复制构造函数:

  1. # 更好的'Foo'构建函数:
  2. proc constructFoo(a, b: cint): Foo {.importcpp: "Foo(@)", constructor.}

封装析构函数

封装destruct由于Nim直接生成C++,所以任何析构函数都由C++编译器在作用域出口处隐式调用。 这意味着通常人们可以完全没有封装析构函数! 但是当需要显式调用它时,需要将其封装起来。 模式语言提供了所需的一切:

  1. proc destroyFoo(this: var Foo) {.importcpp: "#.~Foo()".}

对象的Importcpp

泛型 importcpp 的对象映射成C++模板。这意味着您可以轻松导入C++的模板,而无需对象类型的模式语言:

  1. type
  2. StdMap {.importcpp: "std::map", header: "<map>".} [K, V] = object
  3. proc `[]=`[K, V](this: var StdMap[K, V]; key: K; val: V) {.
  4. importcpp: "#[#] = #", header: "<map>".}
  5.  
  6. var x: StdMap[cint, cdouble]
  7. x[6] = 91.4

生成:

  1. std::map<int, double> x;
  2. x[6] = 91.4;
  • 如果需要更精确的控制, 上标点 ' 可以用在提供的模式里标志泛型类型的具体类型参数。 更多细节,见过程模式中的上标点操作符。
  1. type
  2. VectorIterator {.importcpp: "std::vector<'0>::iterator".} [T] = object
  3.  
  4. var x: VectorIterator[cint]

Produces:

  1. std::vector<int>::iterator x;