3

I'm new to ffi. But I successfully used dart-ffi with functions call.

Now, I'd like to use a C++ object in dart ffi. I don't know if it is possible, but I tried like this.

The prototypes of constructor call are :

function_dart = lib
    .lookup<NativeFunction<function_native>>("constructor_function")
    .asFunction();

But I've got : Failed to lookup symbol <constructor_function>, where I tried constructor function with :

constructor_function
class::constructor_function
class::constructor_function(args)

I did nm -gDC <lib>, and I can see the constructor.

Help !

edit 1 : @Botje, @Richard-Heap

I'm trying to use the VideoCapture instance from OpenCV.

I have followed the instructions from Botje's answer.

So I created a lib, like this :

bind.hpp :

#ifndef BIND_HPP
# define BIND_HPP

#include <opencv2/videoio.hpp>

extern "C" {
  cv::VideoCapture *cvCreateVideoCapture(char *filename, int apiPreference);
}
#endif

bind.cpp :

#include "bind.hpp"

cv::VideoCapture *createVideoCapture(char *filename, int apiPreference) {
  return new cv::VideoCapture(filename, apiPreference);
}

The commands I use to compile :

g++ -c bind.cpp -lopencv -o bind.o
g++ bind.o -shared -o bind.so

I get : dart: symbol lookup error: ./lib/src/bind.so: undefined symbol: _ZN2cv12VideoCaptureC1ERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEi

Next step, is to use a method of VideoCapture instance.

Thank you

XavierB
  • 2,198
  • 1
  • 9
  • 12
  • C++ has no notion of the programmer "calling the constructor" (I realise that some people think it does, due to the way some declaration and cast syntaxes look), and constructors do not have names. Objects are created by more complex machinery, most of which is buried inside the language's semantics and not made available to you directly. What are you trying to accomplish? – Asteroids With Wings May 04 '20 at 15:28
  • I'm trying to use a 3rd party library with C++ objects and I don't want to rewrite it. To be more precise, I have to open a file format only with a C++ object constructor. I'm OK to write an interface but not rewriting the object himself. It seems an overkill job, just for the possibilities, that maybe I don't know. – XavierB May 05 '20 at 14:58
  • The `-lopencv` should go on the link step (`gcc ... -o bind.so`), not the compile step. If there is anything else open a new question instead of polluting this one. – Botje May 18 '20 at 15:03

2 Answers2

7

Dart ffi uses a C interface, so you have to adapt as follows.

Start with C++ class

Rect::Rect(int32_t width, int32_t height) {
  m_width = width;
  m_height = height;
}

void Rect::setWidth(int32_t width) {
  m_width = width;
}

void Rect::setHeight(int32_t height) {
  m_height = height;
}

int32_t Rect::area() {
  return m_width * m_height;
}

create a C adapter header

EXTERNC void* rect_init(int32_t width, int32_t height);
EXTERNC void rect_destroy(void *ptr);
EXTERNC int32_t rect_area(void *ptr);

and implementation

void* rect_init(int32_t width, int32_t height){
    return new Rect(width, height);
}

void rect_destroy(void *ptr){
    auto typed_ptr = static_cast<Rect*>(ptr);
    delete typed_ptr;
}

int32_t rect_area(void *ptr){
    auto typed_ptr = static_cast<Rect*>(ptr);
    return typed_ptr->area();
}

in Dart, create the typedefs

typedef example_init_rect = Pointer<Void> Function(Int32 w, Int32 h);
typedef ExampleInitRect = Pointer<Void> Function(int w, int h);

typedef example_free_rect = Void Function(Pointer<Void> p);
typedef ExampleFreeRect = void Function(Pointer<Void> p);

typedef example_area_rect = Int32 Function(Pointer<Void> p);
typedef ExampleAreaRect = int Function(Pointer<Void> p);

and bind the C adapter functions. Finally, you could create a Dart class that proxies the underlying C++ class.

class NativeRect {
  Pointer<Void> _nativeInstance;

  NativeRect(int width, int height) {
    _nativeInstance = Example()._exInitRect(width, height);
  }

  void free() {
    Example()._exFreeRect(_nativeInstance);
  }

  int get area => Example()._exAreaRect(_nativeInstance);
}
Richard Heap
  • 48,344
  • 9
  • 130
  • 112
2

C++ compilers use "name mangling" to ensure symbol names are unique. The fact that you had to add the -C option (or --demangle) to make it show up is a hint.

For example, here is the mangled symbol for some_class::some_class(int, std::string):

_ZN10some_classC2EiNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE

You will need to pass the mangled name (not the demangled name) in order to call the constructor. You will also need to match the ABI for an object (ie have a pointer to the object's memory in the correct register). Sometimes this is simply a hidden first argument to the constructor, but not in all ABIs.

If at all possible, write a C++ wrapper function that constructs the object for you and tag it with extern "C" so you don't have to jump through these hoops.

For example:

extern "C"
some_class* create_some_class(int x, const char * some_text) {
    return new some_class(x, some_text);
}

You can now simply call create_some_class from Dart with basic types and you will get back a pointer to the constructed C++ object. If you intend to wrap a large API like this, consider migrating to something like SWIG that can auto-generate these wrappers.

Botje
  • 26,269
  • 3
  • 31
  • 41
  • OK, 2 questions : I followed your solution that I find pretty good, with a lib of mine that use the 3rd party library I need. But I've got `undefined from libofmine.so`. The method is well here in libofmine (saw with nm -gDC), but as "U" / undefined. 1. How can I solve this ? 2. If everything is successful, how can I call a method of the instance ? Thank you – XavierB May 05 '20 at 15:13
  • Can you edit your question to show how `libofmine.so` is created and which library contains the constructor? As for your second question: more C wrapper code OR maybe call the appropriate mangled function name with the object as first argument. – Botje May 05 '20 at 15:19
  • OK, I edited my question with more details (openCV, my code to wrap). Thank you for your help. – XavierB May 18 '20 at 15:01