Okay, I have a MWE that duplicates my issue: My wrapper correctly gives me the main application. The main application manages some other classes as members, but when I get those members back - they seem to not work. This example has a main class that has two member classes. One class just manages an integer, the other one manages a double. I wrote a C wrapper around the C++ library, and a C application that uses the wrapper works as expected - but there is something wrong with how I did it in Julia.
I squished all of the code together because it just made the question harder to read.
I expected to be able to run the julia wrapper with:
m = MainApp(1, 3.5)
a = m.borrowA()
a.get_id() # should yield 1, yields garbage
b = m.borrowB()
b.get_s() # should yield 3.5, yields garbage
C++ "library"
// stuff.h
class A {
private:
int id;
public:
A(int id);
int get_id() const;
};
class B {
private:
double s;
public:
B(double s);
double get_s() const;
};
class MainApp {
private:
A myA;
B myB;
public:
A* borrowA();
B* borrowB();
MainApp(int one, double two);
};
// stuff.cpp
#include "stuff.h"
A::A(int id) : id(id) {};
int A::get_id() const { return id; }
B::B(double s) : s(s) {};
double B::get_s() const { return s; }
MainApp::MainApp(int one, double two) : myA(one), myB(two) {}
A *MainApp::borrowA() { return &myA; }
B *MainApp::borrowB() { return &myB; }
C wrapper
#include "wrap_stuff.h"
#include <new>
H_mainapp *MainApp_create(int a, double b) {
return reinterpret_cast<H_mainapp *>(new (std::nothrow) MainApp(a, b));
}
void MainApp_destroy(H_mainapp *m) {
MainApp *p = reinterpret_cast<MainApp *>(m);
delete p;
}
H_A *MainApp_borrow_A(H_mainapp *m) {
MainApp *p = reinterpret_cast<MainApp *>(m);
return reinterpret_cast<H_A *>(p->borrowA());
}
H_B *MainApp_borrow_B(H_mainapp *m) {
MainApp *p = reinterpret_cast<MainApp *>(m);
return reinterpret_cast<H_B *>(p->borrowB());
}
int A_get_id(H_A *a) {
A *aa = reinterpret_cast<A *>(a);
return aa->get_id();
}
double B_get_s(H_B *b) {
B *bb = reinterpret_cast<B *>(b);
return bb->get_s();
}
Build Script for Libraries
#!/usr/bin/bash
g++ -shared -fPIC stuff.cpp -o libstuff.so
g++ -shared -fPIC wrap_stuff.cpp -o libstuff_wrapper.so \
-Wl,-rpath=. -L. -lstuff
gcc test_c_interface.c -o test_c_interface \
-Wl,-rpath=. -L. -lstuff_wrapper
Lastly,
Julia code to use the library
mutable struct MainApp
p::Ptr{Cvoid}
function MainApp(a, b)
obj = @ccall "libstuff_wrapper.so".MainApp_create(a::Cint, b::Cdouble)::Ptr{Cvoid}
if obj != C_NULL
g = new(obj)
finalizer(x -> ccall((:MainApp_destroy, "libstuff_wrapper.so"), Cvoid, (Ptr{Cvoid},), x.p), g)
else
error("Can't create MainApp");
end
end
end
function Base.getproperty(this::MainApp, s::Symbol)
if s == :borrowA
function()
ah = @ccall "libstuff_wrapper.so".MainApp_borrow_A()::Ptr{Cvoid}
@show ah
A(ah)
end
elseif s == :borrowB
function()
B(@ccall "libstuff_wrapper.so".MainApp_borrow_B()::Ptr{Cvoid})
end
else
getfield(this, s)
end
end
#------------------------------------------------------------------------------#
mutable struct A
p::Ptr{Cvoid}
function A(x::Ptr{Cvoid})
x == C_NULL && throw(error("argument cannot be C_NULL"))
new(x)
end
end
function Base.getproperty(this::A, s::Symbol)
@show s
if s == :get_id
function()
@ccall "libstuff_wrapper.so".A_get_id()::Cint
end
else
getfield(this, s)
end
end
#------------------------------------------------------------------------------#
mutable struct B
p::Ptr{Cvoid}
function B(x::Ptr{Cvoid})
x == C_NULL && throw(error("argument cannot be C_NULL"))
new(x)
end
end
function Base.getproperty(this::B, s::Symbol)
if s == :get_s
function()
@ccall "libstuff_wrapper.so".B_get_s()::Cdouble
end
else
getfield(this, s)
end
end