ImportCpp pragma
Note: c2nim can parse a large subset of C++ and knows about the importcpp pragma pattern language. It is not necessary to know all the details described here.
Similar to the importc pragma for C, the importcpp pragma can be used to import C++ methods or C++ symbols in general. The generated code then uses the C++ method calling syntax: obj->method(arg). In combination with the header and emit pragmas this allows sloppy interfacing with libraries written in C++:
# 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 {.header: irr,
importcpp: "IrrlichtDevice".} = object
IrrlichtDevice = ptr IrrlichtDeviceObj
proc createDevice(): IrrlichtDevice {.
header: irr, importcpp: "createDevice(@)".}
proc run(device: IrrlichtDevice): bool {.
header: irr, importcpp: "#.run(@)".}
The compiler needs to be told to generate C++ (command cpp) for this to work. The conditional symbol cpp is defined when the compiler emits C++ code.
Namespaces
The sloppy interfacing example uses .emit to produce using namespace declarations. It is usually much better to instead refer to the imported name via the namespace::identifier notation:
type
IrrlichtDeviceObj {.header: irr,
importcpp: "irr::IrrlichtDevice".} = object
Importcpp for enums
When importcpp is applied to an enum type the numerical enum values are annotated with the C++ enum type, like in this example: ((TheCppEnum)(3)). (This turned out to be the simplest way to implement it.)
Importcpp for procs
Note that the importcpp variant for procs uses a somewhat cryptic pattern language for maximum flexibility:
- A hash # symbol is replaced by the first or next argument.
- A dot following the hash #. indicates that the call should use C++’s dot or arrow notation.
- An at symbol @ is replaced by the remaining arguments, separated by commas.
For example:
proc cppMethod(this: CppObj, a, b, c: cint) {.importcpp: "#.CppMethod(@)".}
var x: ptr CppObj
cppMethod(x[], 1, 2, 3)
Produces:
x->CppMethod(1, 2, 3)
As a special rule to keep backward compatibility with older versions of the importcpp pragma, if there is no special pattern character (any of # ‘ @) at all, C++’s dot or arrow notation is assumed, so the above example can also be written as:
proc cppMethod(this: CppObj, a, b, c: cint) {.importcpp: "CppMethod".}
Note that the pattern language naturally also covers C++’s operator overloading capabilities:
proc vectorAddition(a, b: Vec3): Vec3 {.importcpp: "# + #".}
proc dictLookup(a: Dict, k: Key): Value {.importcpp: "#[#]".}
- 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.
For example:
type Input {.importcpp: "System::Input".} = object
proc getSubsystem*[T](): ptr T {.importcpp: "SystemManager::getSubsystem<'*0>()", nodecl.}
let x: ptr Input = getSubsystem[Input]()
Produces:
x = SystemManager::getSubsystem<System::Input>()
@ is a special case to support a cnew operation. It is required so that the call expression is inlined directly, without going through a temporary location. This is only required to circumvent a limitation of the current code generator.
For example C++’s new operator can be “imported” like this:
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)
Produces:
x = new Foo(3, 4)
However, depending on the use case new Foo can also be wrapped like this instead:
proc newFoo(a, b: cint): ptr Foo {.importcpp: "new Foo(@)".}
let x = newFoo(3, 4)
Wrapping constructors
Sometimes a C++ class has a private copy constructor and so code like Class c = Class(1,2); must not be generated but instead Class c(1,2);. For this purpose the Nim proc that wraps a C++ constructor needs to be annotated with the constructor pragma. This pragma also helps to generate faster C++ code since construction then doesn’t invoke the copy constructor:
# a better constructor of 'Foo':
proc constructFoo(a, b: cint): Foo {.importcpp: "Foo(@)", constructor.}
Wrapping destructors
Since Nim generates C++ directly, any destructor is called implicitly by the C++ compiler at the scope exits. This means that often one can get away with not wrapping the destructor at all! However, when it needs to be invoked explicitly, it needs to be wrapped. The pattern language provides everything that is required:
proc destroyFoo(this: var Foo) {.importcpp: "#.~Foo()".}
Importcpp for objects
Generic importcpp’ed objects are mapped to C++ templates. This means that one can import C++’s templates rather easily without the need for a pattern language for object types:
type
StdMap[K, V] {.importcpp: "std::map", header: "<map>".} = 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
Produces:
std::map<int, double> x;
x[6] = 91.4;
If more precise control is needed, the apostrophe ‘ can be used in the supplied pattern to denote the concrete type parameters of the generic type. See the usage of the apostrophe operator in proc patterns for more details.
type
VectorIterator[T] {.importcpp: "std::vector<'0>::iterator".} = object
var x: VectorIterator[cint]
Produces:
std::vector<int>::iterator x;