ImportCpp编译指示
注意: c2nim 可以解析C++子集并且知道 importcpp 编译指示模式语言。 没有必要知道这里描述的所有细节。
和 importc pragma for C 类似, importcpp 编译指示一般可以用于引入 C++ 方法或C++符号。 生成的代码会使用C++方法调用的语法: obj->method(arg) 。
结合 header 和 emit 编译指示,这允许与C++库的 草率 接口:
- # 和C++引擎对接的反例... ;-)
- {.link: "/usr/lib/libIrrlicht.so".}
- {.emit: """
- using namespace irr;
- using namespace core;
- using namespace scene;
- using namespace video;
- using namespace io;
- using namespace gui;
- """.}
- const
- irr = "<irrlicht/irrlicht.h>"
- type
- IrrlichtDeviceObj {.header: irr,
- importcpp: "IrrlichtDevice".} = object
- IrrlichtDevice = ptr IrrlichtDeviceObj
- proc createDevice(): IrrlichtDevice {.
- header: irr, importcpp: "createDevice(@)".}
- proc run(device: IrrlichtDevice): bool {.
- header: irr, importcpp: "#.run(@)".}
需要告诉编译器生成C++(命令 cpp )才能使其工作。 当编译器发射C++代码时,会定义条件符号 cpp 。
命名空间
草率接口 示例使用 .emit 来生成 using namespace 声明。 通过 命名空间::标识符 符号来引用导入的名称通常要好得多:
- type
- IrrlichtDeviceObj {.header: irr,
- importcpp: "irr::IrrlichtDevice".} = object
枚举Importcpp
当 importcpp 应用于枚举类型时,数字枚举值用C++枚举类型注释, 像这个示例: ((TheCppEnum)(3)) 。 (事实证明这是实现它的最简单方法。)
过程Importcpp
请注意,procs的 importcpp 变体使用了一种有点神秘的模式语言,以获得最大的灵活性:
哈希 # 符号被第一个或下一个参数替换。哈希 #. 后面的一个点表示该调用应使用C++的点或箭头表示法。
符号 @ 被剩下的参数替换,用逗号分隔。示例:
- proc cppMethod(this: CppObj, a, b, c: cint) {.importcpp: "#.CppMethod(@)".}
- var x: ptr CppObj
- cppMethod(x[], 1, 2, 3)
生成:
- x->CppMethod(1, 2, 3)
作为一个特殊的规则来保持与旧版本的 importcpp 编译指示的向后兼容性,如果没有特殊的模式字符(任何一个 #'@ ),那么认为是C++的点或箭头符号,所以上面的例子也可以写成:
- proc cppMethod(this: CppObj, a, b, c: cint) {.importcpp: "CppMethod".}
请注意,模式语言自然也涵盖了C++的运算符重载功能:
- proc vectorAddition(a, b: Vec3): Vec3 {.importcpp: "# + #".}
- proc dictLookup(a: Dict, k: Key): Value {.importcpp: "#[#]".}
- 上标点 ' 后跟0..9范围的整数 i 被第i个形参类型替换。 第0位是结果类型。这可以用于将类型传递给C++函数模板。 在 ' 和数字之间可以使用星号来获得该类型的基类型。 (所以它从类型中“拿走了一颗星”; T * 变为 T 。) 可以使用两颗星来获取元素类型的元素类型等。示例:
- type Input {.importcpp: "System::Input".} = object
- proc getSubsystem*[T](): ptr T {.importcpp: "SystemManager::getSubsystem<'*0>()", nodecl.}
- let x: ptr Input = getSubsystem[Input]()
生成:
- x = SystemManager::getSubsystem<System::Input>()
- #@ 是一个支持 cnew 操作的特例。 这是必需的,以便直接内联调用表达式,而无需通过临时位置。 这只是为了规避当前代码生成器的限制。
例如,C++的 new 运算符可以像这样“导入”:
- proc cnew*[T](x: T): ptr T {.importcpp: "(new '*0#@)", nodecl.}
- # 'Foo'构造函数:
- proc constructFoo(a, b: cint): Foo {.importcpp: "Foo(@)".}
- let x = cnew constructFoo(3, 4)
生成:
- x = new Foo(3, 4)
但是,根据用例,new Foo
也可以这样包装:
- proc newFoo(a, b: cint): ptr Foo {.importcpp: "new Foo(@)".}
- let x = newFoo(3, 4)
封装构造函数
有时候C++类有一个私有的复制构造函数,因此不能生成像 Class c = Class(1,2); 这样的代码,而是 Class c(1,2); 。 为此,包含C ++构造函数的Nim proc需要使用 构造函数 编译器。 这个编译指示也有助于生成更快的C++代码,因为构造然后不会调用复制构造函数:
- # 更好的'Foo'构建函数:
- proc constructFoo(a, b: cint): Foo {.importcpp: "Foo(@)", constructor.}
封装析构函数
封装destruct由于Nim直接生成C++,所以任何析构函数都由C++编译器在作用域出口处隐式调用。 这意味着通常人们可以完全没有封装析构函数! 但是当需要显式调用它时,需要将其封装起来。 模式语言提供了所需的一切:
- proc destroyFoo(this: var Foo) {.importcpp: "#.~Foo()".}
对象的Importcpp
泛型 importcpp 的对象映射成C++模板。这意味着您可以轻松导入C++的模板,而无需对象类型的模式语言:
- type
- StdMap {.importcpp: "std::map", header: "<map>".} [K, V] = object
- proc `[]=`[K, V](this: var StdMap[K, V]; key: K; val: V) {.
- importcpp: "#[#] = #", header: "<map>".}
- var x: StdMap[cint, cdouble]
- x[6] = 91.4
生成:
- std::map<int, double> x;
- x[6] = 91.4;
- 如果需要更精确的控制, 上标点 ' 可以用在提供的模式里标志泛型类型的具体类型参数。 更多细节,见过程模式中的上标点操作符。
- type
- VectorIterator {.importcpp: "std::vector<'0>::iterator".} [T] = object
- var x: VectorIterator[cint]
Produces:
- std::vector<int>::iterator x;