Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I call a C++ constructor function in dart ffi?

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

like image 373
XavierB Avatar asked Sep 05 '25 04:09

XavierB


2 Answers

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);
}
like image 60
Richard Heap Avatar answered Sep 07 '25 19:09

Richard Heap


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.

like image 30
Botje Avatar answered Sep 07 '25 17:09

Botje