Skip to content

sinkingsugar/nimline

Repository files navigation

Nimline

previously known as fragments/ffi/cpp

please star and support fragments as well!

Build Status

Write C/C++ straight from Nim, without the need to generate wrappers. Inspired by std/jsffi and using the magic of importcpp, macros and many other awesome Nim features.

Note: Since imported C++ methods and types are not known to the Nim-compiler, errors will be reported by the C++ compiler instead. They will also not be caught by the linter.

Code-generation example

Nim:

var obj = cppinit(MyClass, 1)
obj.someField = 42
let res = obj.someMethod(1, 2.0).to(cint)

# or for an internal pointer, which is more flexible due to lack of constructors in nim
var
  myx = MyClass.new()
  myxref = MyClass.newref()
myx.test3().to(void)
myx.number = 99
myxref.test3().to(void)
myxref.number = 100

Generated C++:

MyClass obj(((NI) 1));
obj.someField = (((NI) 42));
int res = (int)(obj.someMethod((((NI) 1)), (2.0000000000000000e+00)));

Usage

Importing C++ types

If a C++ type needs to be used as a variable, it first has to be declared using defineCppType. This will import the type and enable interop on it.

defineCppType(MyNimType, "MyCppClass", "MyHeader.h")
var obj: MyNimType
obj.someField = 42

Accessing members

Nimline allows you to access C++ members and functions of Nim objects using the dot-operators ., .= and .(), and other operations that mimic C++.

obj.someField = 42 # Translates to `obj.someField = 42`
discard obj.someField.to(cint) # Translates to `obj.someField`
obj.someMethod(1).to(void) # Translates to `obj.someMethod(1)`

Return types have to be explicitly specified using to, if they need to be stored in variables, or passed to Nim-procs, even for void retruns. This is because the Nim-compiler is not aware of these function signatures. They can however be used in further C++ calls. Function arguments are automatically reinterpreted as C++ types.

If a member name collides with a Nim-keyword, the more explicit notation invoke can be used:

obj.invoke("someMethod", 1).to(void)

Even types that were not declared with defineCppType can be used in this way, by first reinterpreting them using toCpp.

type MyClass {.importcpp.} = object
var obj: MyClass
obj.toCpp.someField = 42

Note: toCpp(), invoke() and member-function calls return a CppProxy. This is a non-concrete type only enables member access, and does not appear in the generated C++.

Globals

Global variables and free function can be used from the special global object, just like data members and member functions.

global.globalNumber = 42
echo global.globalNumber.to(cint)
global.printf("Hello World\n".cstring).to(void)

Free functions can alternatively be called with invokeFunction.

invokeFunction("printf", "Hello World\n".cstring).to(void)

Constructors and destructors

To initialize an object on the stack, use cppinit():

# Translates to `MyType obj(1)`
let obj = cppinit(MyType, 1)

Objects can also be constructed on the heap, either through a ptr or ref using cppctor(). This translates to a placement-new in C++ and needs to be followed by cppdtor for cleanup.

# Allocate storage
let
  tracked: ref MyClass = new MyClass
  untracked: ptr MyClass = cast[ptr MyClass](alloc0(sizeof(MyClass)))

# Call placement-new, e.g. `new (tracked) MyClass(1)`
cppctor(tracked, 1)
cppctor(untracked, 2)

# Call destructor, e.g. `tracked->~MyClass()`
cppdtor(tracked)
cppdtor(untracked)

For convenience, cppnewref() creates a new reference, constructs the object and calls it's destructor on finalization.

cppnewref(tracked, 1)

Adding C++ compiler options

The folloing helpers are available to set C++ compiler/linker options in a platform independent way.

cppdefines("MYDEFINE", "MYDEFINE2=10") # e.g. -DMYDEFINE -DMYDEFINE2=10
cppincludes(".") # e.g. -I.
cppfiles("MyClass.cpp")
cpplibpaths(".") # e.g. -L.
cpplibs("libMyModule.so") # e.g. -llibMyModule.so

Standard library helpers

For convenience, the following standard library types are supported out of the box.

  • StdString (std::string)

    let str: StdString = "HelloWorld"
  • StdArray (std::array)

    var cppArray: StdArray[cint, 4]
    cppArray[0] = 42
    echo cppArray[0]
  • StdTuple (std::tuple)

    var cppTyple = makeCppTuple(1, 1.0, obj)
    cppTupleSet(0, cppTuple.toCpp, 42.toCpp)
    echo cppTupleGet[int](0, cppTuple.toCpp)
    let nimTuple = cppTuple.toNimTyple()
  • StdException (std::exception)

    try: someNativeFunction()
    except StdException as e: raiseAssert($e.what())

Known issues

  • cppinit does not work in the global scope. This is a Nim issue (nim-lang/Nim#6198).
  • Nim always memsets to 0 value types, this might have serious bad effects on most cpp classes. Workaround with refs and pointers.

About

Wrapper-less C/C++ interop for Nim

Resources

License

Stars

Watchers

Forks

Packages

No packages published