Hi there, which options do i have for allowing an inheritable object imported from cpp that has virtual methods to override those methods in nim and still having them dispatched correctly (by c++) ?
I've investigated adding some cdecl function pointers to the c++ objects, that are then invoked by the c++ virtual methods and i then can pass (cdecl) closures from nim, but it gets a bit tedious and error prone to write, not to mention that i need to have the class defined (and inherited) plus allocated in c++ (with cnew, mainly because the generated nim c++ code will call newObj which seems more like a malloc that will never construct a proper c++ object calling its constructors).
I was trying to evaluate which kind of options do i have to make this nice to write (could templates or macros help here?), maybe someone can provide some insights.
Thank you !
{.emit: """
class MyClass {
public:
int a;
int MyMethod();
}
""".}
type MyClass {.importcpp, nodecl.} = object
a: cint
To implement one of its methods, we can use the undocumented pragma {.exportcpp.}. There's still some problems to overcome because Nim generates a declaration for every proc before its implement, which will cause an error. You have to tell the compiler to ignore the first declaration. And the pointer this need to be imported.
proc MyMethod(): cint {.
exportcpp: "MyClass::MyMethod",
codegenDecl: """
#ifdef MyMethod_SHOULD_BE_DEFINED
int MyClass::MyMethod()
#else
#define MyMethod_SHOULD_BE_DEFINED
#endif
""".} =
var this {.nodecl, importc, global.}: ptr MyClass
#Use {.nodecl, importc, global.} because {.nodecl.} doesn't work on local variables
return (this[]).a
After that, you may import its correct form so that it can be used normally.
proc MyMethod(self: MyClass): cint {.importcpp: "#.$1(@)", nodecl.}
var foo: MyClass
echo foo.MyMethod()
This is more or less what i'm trying to achieve:
foo.h:
struct Foo {
virtual ~Foo() = default;
virtual void bar() {}
};
inline void processFoos(Foo* foo)
{
foo->bar();
}
test.nim:
type
Foo {.importcpp: "Foo", header: "<foo.h>", inheritable, pure.} = object
Xyz = object of Foo
bar: proc()
proc processFoos(foo: ptr Foo) {.importcpp: "processFoos(@)".}
proc main() =
var xyz = Xyz()
xyz.bar = proc() = echo "123"
processFoos(addr(xyz))
when isMainModule:
main()
But as of today achieve something like this is not possible with current c++ codegen, at least i cannot think of a way to use the exportcpp pragma you mentioned.
I have a hard time understanding how i can make it work (mainly because of my limited knowledge here), imagine that i make a macro that will emit automatically an inherited class and implement those virtual functions in c++ so i can then wrap them in a nim object and "set" them via function pointers.
Now i have another nim file that wants to use the type (i suppose for every nim file there will be a cpp file hence a translation unit), but how can the other nim file see both the nim object and the imported c++ emitted class in the other translation unit without re-emitting ? All i could think of is to have a macro output a c++ header file that is then included in multiple translation units... does it make sense ?
include anotherFile
This is defining a macro that will emit both c++ and nim and the same time, using cdecl function pointers that will call into nim from c++.
Isn't including actually creating another copy of the emitted code ?
Yes, copy to the translation unit.