I am trying to wrap a C++ class. I don't know C++ very well just the very basic. I just would like to have a very basic example running.
I have the following C++ code just to create a point using OpenCascade:
++
#include <iostream>
#include <Geom2d_CartesianPoint.hxx>
int main ()
{
std::cout << "Hello, world !" ;
Geom2d_CartesianPoint *g2d_cp = new
Geom2d_CartesianPoint (0.0, 0.0) ;
}
I can compile it by doing:
g++ -o ex02.o -c -I/usr/include/opencascade ex02.cpp
g++ -rdynamic -o ex02 ex02.o -Wl,-rpath,/usr/lib /usr/lib/libTKG2d.so /usr/lib/libTKernel.so -lpthread -ldl -lm -ltbbmalloc -ltbb
The header is available in Geom2d_CartesianPoint.hxx.
Now I am trying to wrap the class in order to use just the constructor. I am quite lost. I have created the following:
{.link: "/usr/lib/libTKG2d.so /usr/lib/libTKernel.so".}
const
geom2d_cartesianpoint = "/usr/include/opencascade/Geom2d_CartesianPoint.hxx"
type
Geom2d_CartesianPointObj* {.header: geom2d_cartesianpoint,
importcpp: "Geom2d_CartesianPoint".} = object
#Geom2d_CartesianPoint = ptr Geom2d_CartesianPointObj
proc newPoint(x,y:float): Geom2d_CartesianPointObj {.
header: geom2d_cartesianpoint, importcpp: "Geom2d_CartesianPoint(@)".}
var pnt = newPoint(0.0,0.0)
echo repr pnt
and the error that I am getting:
$ nim cpp --passC="-I/usr/include/opencascade" ex
Hint: used config file '/home/jose/.choosenim/toolchains/nim-1.2.6/config/nim.cfg' [Conf]
Hint: system [Processing]
Hint: widestrs [Processing]
Hint: io [Processing]
Hint: ex [Processing]
CC: ex.nim
/home/jose/.cache/nim/ex_d/@mex.nim.cpp:84:37: error: no matching function for call to ‘Geom2d_CartesianPoint::Geom2d_CartesianPoint()’
84 | N_LIB_PRIVATE Geom2d_CartesianPoint pnt__gQ9cjxtZURY9c2ElQxWazspQ;
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
En el fichero incluido desde /home/jose/.cache/nim/ex_d/@mex.nim.cpp:10:
/usr/include/opencascade/Geom2d_CartesianPoint.hxx:46:19: nota: candidate: ‘Geom2d_CartesianPoint::Geom2d_CartesianPoint(Standard_Real, Standard_Real)’
46 | Standard_EXPORT Geom2d_CartesianPoint(const Standard_Real X, const Standard_Real Y);
| ^~~~~~~~~~~~~~~~~~~~~
/usr/include/opencascade/Geom2d_CartesianPoint.hxx:46:19: nota: el candidato espera 2 argumentos, se proporcionaron 0
/usr/include/opencascade/Geom2d_CartesianPoint.hxx:44:19: nota: candidate: ‘Geom2d_CartesianPoint::Geom2d_CartesianPoint(const gp_Pnt2d&)’
44 | Standard_EXPORT Geom2d_CartesianPoint(const gp_Pnt2d& P);
| ^~~~~~~~~~~~~~~~~~~~~
/usr/include/opencascade/Geom2d_CartesianPoint.hxx:44:19: nota: el candidato espera 1 argumento, se proporcionaron 0
/usr/include/opencascade/Geom2d_CartesianPoint.hxx:37:7: nota: candidate: ‘Geom2d_CartesianPoint::Geom2d_CartesianPoint(const Geom2d_CartesianPoint&)’
37 | class Geom2d_CartesianPoint : public Geom2d_Point
| ^~~~~~~~~~~~~~~~~~~~~
/usr/include/opencascade/Geom2d_CartesianPoint.hxx:37:7: nota: el candidato espera 1 argumento, se proporcionaron 0
/usr/include/opencascade/Geom2d_CartesianPoint.hxx:37:7: nota: candidate: ‘Geom2d_CartesianPoint::Geom2d_CartesianPoint(Geom2d_CartesianPoint&&)’
/usr/include/opencascade/Geom2d_CartesianPoint.hxx:37:7: nota: el candidato espera 1 argumento, se proporcionaron 0
Error: execution of an external compiler program 'g++ -c -w -w -fpermissive -I/usr/include/opencascade -std=gnu++14 -funsigned-char -I/home/jose/.choosenim/toolchains/nim-1.2.6/lib -I/home/jose/src/3d/occt -o /home/jose/.cache/nim/ex_d/@mex.nim.cpp.o /home/jose/.cache/nim/ex_d/@mex.nim.cpp' failed with exit code: 1
How can I fix this?
I don't have all the code so I can't verify this. But I think the problem is that new Geom2d_CartesianPoint (0.0, 0.0) ; returns a pointer, but in nim land you don't treat it as a pointer? You probably need proc newPoint(x,y:float): ptr Geom2d_CartesianPointObj.
I also think that importcpp does not mean import C++ but import with object calling convention. So its doing something like something.Geom2d_CartesianPoint. I would see if just`importc` works?
I would also create a simple C++ file with a simple object that you write and try to ffi it first. Then once that is understood you can apply to a more complex code base. Make it work on a toy case first.
I have wrapped some C++ before and some times you have to write some glue in C++ to connect it to some thing that is more like C before you can ffi to it from nim: https://github.com/treeform/steamworks/blob/master/src/steamworks.nim#L229
@mantielero missing {.constructor.} ? see https://github.com/kaushalmodi/std_vector/blob/master/src/std_vector.nim
please improve https://nim-lang.github.io/Nim/manual.html#importcpp-pragma-importcpp-for-objects as needed (see https://github.com/nim-lang/Nim/blob/devel/doc/manual.rst#L1)
That makes sense. I tried constructor yesterday, but I was expenting to find a new something in the generated code, which I didn't when I tried it.
Right now I am trying:
# nim cpp ex.nim
{.link: "/usr/lib/libTKG2d.so /usr/lib/libTKernel.so",
passC:"-I/usr/include/opencascade" }
{.push header: "/usr/include/opencascade/Geom2d_CartesianPoint.hxx"}
type
Geom2d_CartesianPointObj* {.importcpp: "Geom2d_CartesianPoint".} = object
#, bycopy
#proc Geom2d_CartesianPoint*(this: var Geom2d_CartesianPoint; P: gp_Pnt2d): Standard_EXPORT {.
# importcpp: "Geom2d_CartesianPoint".}
# NOTE: check StandardReal and Standard_EXPORT
proc newPoint*(x,y:cfloat): Geom2d_CartesianPointObj {.
constructor, importcpp: "Geom2d_CartesianPoint(@)".}
{.pop.} # {.push header: "/usr/include/opencascade/Geom2d_CartesianPoint.hxx"}
#var pnt = Geom2d_CartesianPoint((0.0).cfloat,(0.0).cfloat)
var pnt = newPoint((0.0).cfloat, (0.0).cfloat)
echo repr pnt
but it complains about the function signature. The signature uses Standard_Real, which makes things more complex (I would need to wrap more headers). I think is equivalent to cdouble.
As mentioned by treeform, I should probably try with a simpler library first.
I have manage to compile and run a simple example using OpenSceneGraph library:
{.passL: "-losg -losgViewer",
passC:"-I/usr/include/osg" }
type
Vec3Obj {.importcpp: "osg::Vec3",
header: "Vec3", bycopy.} = object
proc Vec3*(x,y,z:cdouble): Vec3Obj {.
importcpp: "osg::Vec3(@)", header: "Vec3", constructor.}
proc x*(this:Vec3Obj):cdouble {.
importcpp: "#.x()", header: "Vec3", constructor.}
var p = Vec3((1.0).cdouble, (2.0).cdouble, (3.0).cdouble)
echo repr p.x()
and then:
$ nim cpp -r prueba
1.0
I tried to do something similar with the OpenCascade example and it doesn't work:
# nim cpp ex.nim
{.passL: "-lTKG2d -lTKernel",
passC:"-I/usr/include/opencascade" }
{.push header: "Geom2d_CartesianPoint.hxx"}
type
Standard_Real* = cdouble #{.importcpp: "Geom2d_CartesianPoint" .}
Geom2d_CartesianPointObj* {.importcpp: "Geom2d_CartesianPoint", bycopy.} = object
Geom2d_CartesianPointObjPtr* = ptr Geom2d_CartesianPointObj
proc newPoint*(x,y:Standard_Real): Geom2d_CartesianPointObj {.
importcpp: "Geom2d_CartesianPoint(@)", constructor.}
{.pop.} # {.push header: "Geom2d_CartesianPoint.hxx"}
var pnt = newPoint((0.0).Standard_Real,(0.0).Standard_Real)
echo repr pnt
When I compile it I get the error:
/home/jose/.cache/nim/ex_d/@mex.nim.cpp:84:37: error: no matching function for call to ‘Geom2d_CartesianPoint::Geom2d_CartesianPoint()’
84 | N_LIB_PRIVATE Geom2d_CartesianPoint pnt__gQ9cjxtZURY9c2ElQxWazspQ;
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
which I don't understand, given that I am calling the constructor with parameters.
In fact, it spots as candidate the signature that I am trying to use:
En el fichero incluido desde /home/jose/.cache/nim/ex_d/@mex.nim.cpp:10:
/usr/include/opencascade/Geom2d_CartesianPoint.hxx:46:19: nota: candidate: ‘Geom2d_CartesianPoint::Geom2d_CartesianPoint(Standard_Real, Standard_Real)’
46 | Standard_EXPORT Geom2d_CartesianPoint(const Standard_Real X, const Standard_Real Y);
The main difference that I can spot is that OpenCascade is not using namespaces.
I have tried with ::Geom2d_CartesianPoint(@), but I get the same error.
Just for reference, the header is:
++
// Created on: 1993-03-24
// Created by: Philippe DAUTRY
// Copyright (c) 1993-1999 Matra Datavision
// Copyright (c) 1999-2014 OPEN CASCADE SAS
//
// This file is part of Open CASCADE Technology software library.
//
// This library is free software; you can redistribute it and/or modify it under
// the terms of the GNU Lesser General Public License version 2.1 as published
// by the Free Software Foundation, with special exception defined in the file
// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
// distribution for complete text of the license and disclaimer of any warranty.
//
// Alternatively, this file may be used under the terms of Open CASCADE
// commercial license or contractual agreement.
#ifndef _Geom2d_CartesianPoint_HeaderFile
#define _Geom2d_CartesianPoint_HeaderFile
#include <Standard.hxx>
#include <Standard_Type.hxx>
#include <gp_Pnt2d.hxx>
#include <Geom2d_Point.hxx>
#include <Standard_Real.hxx>
class gp_Pnt2d;
class gp_Trsf2d;
class Geom2d_Geometry;
class Geom2d_CartesianPoint;
DEFINE_STANDARD_HANDLE(Geom2d_CartesianPoint, Geom2d_Point)
//! Describes a point in 2D space. A
//! Geom2d_CartesianPoint is defined by a gp_Pnt2d
//! point, with its two Cartesian coordinates X and Y.
class Geom2d_CartesianPoint : public Geom2d_Point
{
public:
//! Returns a persistent copy of P.
Standard_EXPORT Geom2d_CartesianPoint(const gp_Pnt2d& P);
Standard_EXPORT Geom2d_CartesianPoint(const Standard_Real X, const Standard_Real Y);
//! Set <me> to X, Y coordinates.
Standard_EXPORT void SetCoord (const Standard_Real X, const Standard_Real Y);
//! Set <me> to P.X(), P.Y() coordinates.
Standard_EXPORT void SetPnt2d (const gp_Pnt2d& P);
//! Changes the X coordinate of me.
Standard_EXPORT void SetX (const Standard_Real X);
//! Changes the Y coordinate of me.
Standard_EXPORT void SetY (const Standard_Real Y);
//! Returns the coordinates of <me>.
Standard_EXPORT void Coord (Standard_Real& X, Standard_Real& Y) const Standard_OVERRIDE;
//! Returns a non persistent cartesian point with
//! the same coordinates as <me>.
//! -C++: return const&
Standard_EXPORT gp_Pnt2d Pnt2d() const Standard_OVERRIDE;
//! Returns the X coordinate of <me>.
Standard_EXPORT Standard_Real X() const Standard_OVERRIDE;
//! Returns the Y coordinate of <me>.
Standard_EXPORT Standard_Real Y() const Standard_OVERRIDE;
Standard_EXPORT void Transform (const gp_Trsf2d& T) Standard_OVERRIDE;
Standard_EXPORT Handle(Geom2d_Geometry) Copy() const Standard_OVERRIDE;
//! Dumps the content of me into the stream
Standard_EXPORT virtual void DumpJson (Standard_OStream& theOStream, Standard_Integer theDepth = -1) const Standard_OVERRIDE;
DEFINE_STANDARD_RTTIEXT(Geom2d_CartesianPoint,Geom2d_Point)
protected:
private:
gp_Pnt2d gpPnt2d;
};
#endif // _Geom2d_CartesianPoint_HeaderFile
Do you spot anything wrong?
_EXPORT
mean in your code?Tried to write a binding to blend2d (https://blend2d.com/) but stumbled upon this error:
{.link: "/usr/lib/libblend2d.so" passC: "-I/usr/include" }
{.push header: "/usr/local/include/blend2d.h"}
{.push header: "/usr/local/include/blend2d-impl.h"}
type
BLImage* {.importcpp: "BLImage" .} = object
BLContext* {.importcpp: "BLContext" .} = object
nim c blend2d.nim
Hint: used config file '/home/nix/.choosenim/toolchains/nim-1.4.0/config/nim.cfg' [Conf]
Hint: used config file '/home/nix/.choosenim/toolchains/nim-1.4.0/config/config.nims' [Conf]
....CC: stdlib_system.nim
CC: blend2d.nim
Hint: [Link]
/usr/bin/ld: /home/nix/.cache/nim/blend2d_d/@mblend2d.nim.cpp.o: in function `PreMain()':
@mblend2d.nim.cpp:(.text+0x45): undefined reference to `systemDatInit000()'
/usr/bin/ld: @mblend2d.nim.cpp:(.text+0x56): undefined reference to `systemInit000()'
/usr/bin/ld: /Build/blend2d: hidden symbol `_Z13systemInit000v' isn't defined
/usr/bin/ld: final link failed: bad value
collect2: error: ld returned 1 exit status
Error: execution of an external program failed: 'g++ -o /Build/blend2d /usr/lib/libblend2d.so /home/nix/.cache/nim/blend2d_d/stdlib_system.nim.c.o /home/nix/.cache/nim/blend2d_d/@mblend2d.nim.cpp.o -ldl'
Is this specific to blend2d or how to solve it? I'll try now with a hello world C++ lib and see if this works.
So I manage to compile it. The code is:
# nim cpp ex.nim
{.passL: "-lTKG2d -lTKernel",
passC:"-I/usr/include/opencascade" }
discard "forward decl of gp_Pnt2d"
discard "forward decl of gp_Trsf2d"
discard "forward decl of Geom2d_Geometry"
discard "forward decl of Geom2d_CartesianPoint"
{.push header: "Geom2d_CartesianPoint.hxx"}
type
Standard_Real* {.importcpp: "Geom2d_CartesianPoint".} = cdouble #{.importcpp: "Geom2d_CartesianPoint" .}
Geom2d_CartesianPoint* {.importcpp: "Geom2d_CartesianPoint",
bycopy.} = object
proc constructGeom2d_CartesianPoint*(X, Y: Standard_Real): Geom2d_CartesianPoint {.
importcpp: "Geom2d_CartesianPoint(@)"
.}
{.pop.} # {.push header: "Geom2d_CartesianPoint.hxx"}
proc cnew*[T](x: T): ptr T {.importcpp: "(new '*0#@)", nodecl.}
let pnt = cnew constructGeom2d_CartesianPoint( (0.0).Standard_Real,
(0.0).Standard_Real )
echo repr pnt
Which is a mix of what c2nim gave me and the documentation.
I have a question which is probably a C++ one rather than a Nim question.
I used the constructor pragma which worked on OSG library but not with OpenCascade.
Then I tried with something like this (which failed as well):
proc newFoo(a, b: cint): ptr Foo {.importcpp: "new Foo(@)".}
let x = newFoo(3, 4)
and finally, I imported the new operator (which worked with OpenCascade):
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)
The documentation says: "However, depending on the use case new Foo can also be wrapped like this instead: ...."
I don't really understand the difference between these three cases or three ways to wrap a constructor. Probably because I don't know C++. It looks like it is related to this, so I just keep this for others like me (hobby programmers who might find this thread).
After I found my error, I checked that the following works:
Geom2d_CartesianPoint *prueba = new Geom2d_CartesianPoint (0.0, 0.0) ;
but the following doesn't:
Geom2d_CartesianPoint *prueba = Geom2d_CartesianPoint (0.0, 0.0) ;
Congratulations on wrapping a non-trivial C++ library! It can be quite tricky.
After I found my error, I checked that the following works:
Geom2d_CartesianPoint *prueba = new Geom2d_CartesianPoint (0.0, 0.0) ;
but the following doesn't:
Geom2d_CartesianPoint *prueba = Geom2d_CartesianPoint (0.0, 0.0) ;
In the first line C++ operator new first allocates memory on a heap, then calls object constructor Geom2d_CartesianPoint(0.0, 0.0) to initialize the allocated memory and finally returns the pointer which is then assigned to the prueba variable of the type Geom2d_CartesianPoint*.
In the second line, without new, an attempt is made to assign an object to a pointer, a compile-time error.
Sorry for unburying this thread, but I feel like revisiting it, since I hit the same issue recently. I fix it as explained above but it stills doesn't feel right.
I am wrapping the following:
class BRepFilletAPI_MakeFillet : public BRepFilletAPI_LocalOperation
{
public:
BRepFilletAPI_MakeFillet(const TopoDS_Shape& S, const ChFi3d_FilletShape FShape = ChFi3d_Rational);
. . .
The binding goes as follows:
type
BRepFilletAPI_MakeFillet* {.importcpp: "BRepFilletAPI_MakeFillet",
header: "BRepFilletAPI_MakeFillet.hxx", bycopy
.} = object of BRepFilletAPI_LocalOperation
proc newFillet*(s: TopoDS_Shape;
fShape: ChFi3dFilletShape = chFi3dRational): BRepFilletAPI_MakeFillet {.
cdecl, constructor, importcpp: "BRepFilletAPI_MakeFillet(@)",
header: "BRepFilletAPI_MakeFillet.hxx".}
When I use this, I creates something like:
N_LIB_PRIVATE BRepFilletAPI_MakeFillet tmp__fillet4849_62;
...
tmp__fillet4849_62 = BRepFilletAPI_MakeFillet(fuseOp__fillet4849_44.Shape(), ((ChFi3d_FilletShape) 0));
The way in which this library should be used in C++ would be:
BRepFilletAPI_MakeFillet filletOp(fuseOp.Shape());
^^^^^^^^------ variable's name
^^^^^^^^^^^^^^^^^^^^^^^^--------------- constructor's name
According to the manual, by using constructor I should be getting the format: Class c(1,2).
So by means of the constructor pragma, shouldn't I be getting something like?
BRepFilletAPI_MakeFillet tmp__fillet4849_62(fuseOp__fillet4849_44.Shape(), ((ChFi3d_FilletShape) 0));
Because this line:
N_LIB_PRIVATE BRepFilletAPI_MakeFillet tmp__fillet4849_62;
raises an error due to the fact that BRepFilletAPI_MakeFillet is not a constructor without arguments.After playing A LOT. And checking this example: https://forum.nim-lang.org/t/8660 I found what is the source of the issue.
The problem is main. When the code is not in a main block, it creates:
N_LIB_PRIVATE BRepFilletAPI_MakeFillet tmp__fillet4849_62;
...
tmp__fillet4849_62 = BRepFilletAPI_MakeFillet(fuseOp__fillet4849_44.Shape(), ((ChFi3d_FilletShape) 0));
When it is in a main block, it creates:
BRepFilletAPI_MakeFillet tmp(fuseOp.Shape(), ((ChFi3d_FilletShape) 0));
I am not sure if this is something that could be fixed or if this is the expected behaviour. As a bare minimum, it should be documented in my opinion.