Nim如何与C/C++/Objc互动

痞子三分冷 提交于 2020-03-14 02:30:00

header pragma(头文件指示):

type
  PFile {.importc: "FILE*", header: "<stdio.h>".} = distinct pointer
  # 导入c语言的 FILE* 指针类型到Nim里用PFile新类型来代替使用.

Compile pragma(编译指示):
直接让nim文件使用c/c++代码文件, 编译的时候会先编译.c文件成.o然后链接让nim也能使用其内容.

#test.nim
{.compile: "testc.c".}
proc csum(n: cint): cint {.importc.}
echo csum(100)

//testc.c
int csum(int n) {
    return n + 10;
}

Link pragma(连接指示):
直接链接obj文件.

{.link: "test.o".}

PassC pragma(编译参数指示):
类似makefile compile flag: -g -Wall

{.passC: "-Wall -Werror".}
#也可使用外部程序指令
{.passC: gorge("pkg-config --cflags sdl").}

PassL pragma(连接参数指示):
类似makefile link flag: -L/xxx/sdl -lsdl

{.passL: "-lSDLmain -lSDL".}
#同样可使用外部程序指令
{.passL: gorge("pkg-config --libs sdl").}

Emit pragma(发表指示, 这翻起来还真别扭): 这个指示可以直接在nim代码里运行c/c++/objc等代码, 强大如斯.
(注释: '"""'3个引号是nim的语法, 在里面的所有内容都是字符串.)

{.emit: """
static int cvariable = 420;
""".}

{.push stackTrace:off.}
proc embedsC() =
  var nimVar = 89
  # emit里用"`"号(就是~号的按键不要加shift)之间使用nim代码的内容.
  {.emit: """fprintf(stdout, "%d\n", cvariable + (int)`nimVar`);""".}
{.pop.}

embedsC()

写类和结构开头要写上/*TYPESECTION*/ 或 /*VARSECTION*/.

{.emit: """
/*TYPESECTION*/
struct Vector3 {
public:
  Vector3(): x(5) {}
  Vector3(float x_): x(x_) {}
  float x;
};
""".}

type Vector3 {.importcpp: "Vector3", nodecl} = object
  x: cfloat

proc constructVector3(a: cfloat): Vector3 {.importcpp: "Vector3(@)", nodecl}

ImportCpp pragma(C++导入指示): 可用c2nim工具把C/C++库或代码文件转成nim代码.
和使用importc方法类似, 这些符号会在后面详解, 这里有小教如何在nim里连上鬼火引擎呢.

# Horrible example of how to interface with a C++ engine ... ;-)

{.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 {.final, header: irr,
                      importcpp: "IrrlichtDevice".} = object
  IrrlichtDevice = ptr IrrlichtDeviceObj

proc createDevice(): IrrlichtDevice {.
  header: irr, importcpp: "createDevice(@)".}
proc run(device: IrrlichtDevice): bool {.
  header: irr, importcpp: "#.run(@)".}

当然也可以不emit命名空间, 可以写成这样.
(嘛~这些都属于C++的内容了, 想必大家都知道, 但还是多句嘴吧.)

type
  IrrlichtDeviceObj {.final, header: irr,
                      importcpp: "irr::IrrlichtDevice".} = object

Importcpp for procs(c++导入之函数使用):
单独一个"#"号表示代替第一个或下一个参数.
"#"号接"."表示使用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)

C++的"."或"->"同上面的例子也可以写成:

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

同样也能应用于重载函数

//这里应该是代替c++的operator重载操作符+和[]
proc vectorAddition(a, b: Vec3): Vec3 {.importcpp: "# + #".}
proc dictLookup(a: Dict, k: Key): Value {.importcpp: "#[#]".}

"'"单引号后面接0~9的数字来替换第i个参数, 第0位则作为返回类型, 可以用来传递c++函数模板. "'"单引号和数字之间加入"*"星号表示获得该类型的基类型(所以去掉星号T*就是T类型?), "**"双星表示获得该元素类型的元素类型等. (这句不是很懂, 所以放上原文, E文不错的朋友请告知, 感谢)

An apostrophe ' followed by an integer i in the range 0..9 is replaced by the i'th parameter type. The 0th position is the result type. This can be used to pass types to C++ function templates. Between the ' and the digit an asterisk can be used to get to the base type of the type. (So it "takes away a star" from the type; T* becomes T.) Two stars can be used to get to the element type of the element type etc.

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>()

"#@"目前只知道应用于new操作符, 因为new操作符比较特殊吧? 其它功能暂不知. 代替c++的new操作符的示例:

#'*0#@拆开来就是:'*0为该类型, #@指该操作行为是new吧.
proc cnew*[T](x: T): ptr T {.importcpp: "(new '*0#@)", nodecl.}

# constructor of 'Foo':
proc constructFoo(a, b: cint): Foo {.importcpp: "Foo(@)".}

let x = cnew constructFoo(3, 4)

等价于

x = new Foo(3, 4)

然而, 依赖new的表示式也能用以下方法代替.

proc newFoo(a, b: cint): ptr Foo {.importcpp: "new Foo(@)".}

let x = newFoo(3, 4)

Wrapping constructors(封装构造函数): 构造/析构函数是c++基础内容, 这里不在阐述, 不懂的话可以翻阅相关资料了解一下.
加入constructor的pragma即可.

# a better constructor of 'Foo':
proc constructFoo(a, b: cint): Foo {.importcpp: "Foo(@)", constructor.}

Wrapping destructors(封装析构函数):

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

Importcpp for objects(c++导入之对象):
这里射映了一个C++的map模板类型并声明使用对象

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

等价于c++的

std::map<int, double> x;
x[6] = 91.4;

如果需要更详细的操作, 可以使用"'"加数字来射映(嗯, 那啥上面有解释过).

type
  VectorIterator {.importcpp: "std::vector<'0>::iterator".} [T] = object

var x: VectorIterator[cint]

等价于

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

ImportObjC pragma(objc导入指示):
使用objc需要导入libobjc.a的静态库, passL就是这个作用.

# horrible example of how to interface with GNUStep ...

{.passL: "-lobjc".}
{.emit: """
#include <objc/Object.h>
@interface Greeter:Object
{
}

- (void)greet:(long)x y:(long)dummy;
@end

#include <stdio.h>
@implementation Greeter

- (void)greet:(long)x y:(long)dummy
{
  printf("Hello, World!\n");
}
@end

#include <stdlib.h>
""".}

type
  Id {.importc: "id", header: "<objc/Object.h>", final.} = distinct int

proc newGreeter: Id {.importobjc: "Greeter new", nodecl.}
proc greet(self: Id, x, y: int) {.importobjc: "greet", nodecl.}
proc free(self: Id) {.importobjc: "free", nodecl.}

var g = newGreeter()
g.greet(12, 34)
g.free()

这里教你如何使用c2nim把c头文件转成nim:Nim Wrapping C

参考资料:importcpp-pragma

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!