-
Notifications
You must be signed in to change notification settings - Fork 48
Wrapping a class
Let's take for example cv::Tonemap
class from photo
module:
class CV_EXPORTS_W Tonemap : public Algorithm
{
public:
CV_WRAP virtual void process(InputArray src, OutputArray dst) = 0;
CV_WRAP virtual float getGamma() const = 0;
CV_WRAP virtual void setGamma(float gamma) = 0;
};
CV_EXPORTS_W Ptr<Tonemap> createTonemap(float gamma = 1.0f);
This class inherits from cv::Algorithm
, so let's create that first. We use torch.class
:
./cv/Classes.lua
require 'cv'
local ffi = require 'ffi'
ffi.cdef[[
struct PtrWrapper {
void *ptr;
};
]]
local C = ffi.load(libPath('Classes'))
-- ***** Algorithm *****
ffi.cdef[[
]]
do
local Algorithm = torch.class('cv.Algorithm')
end
Our class will have the only field of type struct PtrWrapper
, which wraps a pointer to our Algorithm, created in C++. The member functions will call corresponding C functions. I stick to the convention to name them <class-name>_<method-name>()
; for example, Algorithm_getDefaultName()
. If a function is a constructor or a destructor, <method-name>
is ctor
or dtor
.
These are some required functions for Algorithm -- (look for them in opencv2/core.hpp : class Algorithm
):
ffi.cdef[[
struct PtrWrapper Algorithm_ctor();
void Algorithm_dtor(struct PtrWrapper ptr);
const char *Algorithm_getDefaultName(struct PtrWrapper ptr);
]]
./include/Classes.hpp
#include <opencv2/core.hpp>
#include <Common.hpp>
// Algorithm
struct AlgorithmPtr {
void *ptr;
inline cv::Algorithm * operator->() { return static_cast<cv::Algorithm *>(ptr); }
inline AlgorithmPtr(cv::Algorithm *ptr) { this->ptr = ptr; }
};
extern "C"
struct AlgorithmPtr Algorithm_ctor();
extern "C"
void Algorithm_dtor(AlgorithmPtr ptr);
extern "C"
const char *Algorithm_getDefaultName(AlgorithmPtr ptr);
Note that every such function (except constructor) takes PtrWrapper
first.
AlgorithmPtr
is simply PtrWrapper
that is "redefined" for a specific class for convenience (note the operators for implicit conversion). This is a "hack": while in FFI cdefs we declare functions taking PtrWrapper, in C we operate <class-name>Ptr
. There should be no errors as these structs are identical.
Here are the implementations of a ctor, a dtor and one member function (the remaining are similar to implement):
./src/Classes.cpp
#include <Classes.hpp>
// Algorithm
extern "C"
struct AlgorithmPtr Algorithm_ctor() {
// The pointer returned by new is implicitly converted
return new cv::Algorithm();
}
extern "C"
void Algorithm_dtor(AlgorithmPtr ptr) {
delete static_cast<cv::Algorithm *>(ptr.ptr);
}
extern "C"
const char *Algorithm_getDefaultName(AlgorithmPtr ptr) {
// Thanks to operator-> redefiniton
return ptr->getDefaultName().c_str();
}
Now let's insert the Lua templates. Again -- the code should be self-explanatory.
./cv/Classes.lua
do
local Algorithm = torch.class('cv.Algorithm')
function Algorithm:__init()
self.ptr = ffi.gc(C.Algorithm_ctor(), C.Algorithm_dtor)
end
function Algorithm:getDefaultName()
return ffi.string(C.Algorithm_getDefaultName(self.ptr))
end
end
Let's return to cv::Tonemap
. Now it should be obvious how to bind this class, basing on Algorithm
. Don't forget to pass self.ptr
!
struct TonemapPtr {
void *ptr;
inline cv::Tonemap * operator->() { return static_cast<cv::Tonemap *>(ptr); }
inline TonemapPtr(cv::Tonemap *ptr) { this->ptr = ptr; }
};
extern "C" struct TonemapPtr Tonemap_ctor();
extern "C" struct TensorWrapper Tonemap_process(struct TonemapPtr ptr, struct TensorArray src, struct TensorWrapper dst);
extern "C" float Tonemap_getGamma(struct TonemapPtr ptr);
extern "C" void Tonemap_setGamma(struct TonemapPtr ptr, float gamma);
But you may notice that the only way to create Tonemap is calling createTonemap()
, which returns a shared pointer (cv::Ptr<cv::Tonemap>
). That's why this will result in early destruction of our just created object:
extern "C"
struct TonemapPtr Tonemap_ctor()
{
// FAILS!
// Tonemap will be destroyed right after return
return cv::createTonemap();
}
Even worse, just as boost::shared_ptr
, cv::Ptr
doesn't allow to manually detach itself. That's why we exploit yet another hack to access cv::Ptr
's private fields:
./include/Classes.hpp
// This is simply cv::Ptr with all fields made public
template <typename T>
struct PublicPtr
{
public:
cv::detail::PtrOwner* owner;
T* stored;
};
// increfs a cv::Ptr and returns the pointer
template <typename T>
T *rescueObjectFromPtr(cv::Ptr<T> ptr) {
PublicPtr<T> *publicPtr = reinterpret_cast<PublicPtr<T> *>(&ptr);
publicPtr->owner->incRef();
return ptr.get();
}
Having this, problem is solved by
./src/photo.cpp
extern "C" struct TonemapPtr Tonemap_ctor()
{
return rescueObjectFromPtr(cv::createTonemap());
}
Other (minor) differences are:
- declare inheritance explicitly in
torch.class()
- use
Algorithm_dtor()
for destruction (as it's virtual) -
Algorithm_dtor()
comes from another lib (namespace), so refer to it byClasses.Algorithm_dtor
./cv/photo.lua
...
--- ***************** Classes *****************
require 'cv.Classes'
local Classes = ffi.load(cv.libPath('Classes'))
ffi.cdef[[
struct PtrWrapper Tonemap_ctor();
struct TensorWrapper Tonemap_process(struct PtrWrapper ptr, struct TensorArray src, struct TensorWrapper dst);
float Tonemap_getGamma(struct PtrWrapper ptr);
void Tonemap_setGamma(struct PtrWrapper ptr, float gamma);
]]
-- Tonemap
do
local Tonemap = torch.class('cv.Tonemap', 'cv.Algorithm')
function Tonemap:__init()
self.ptr = ffi.gc(C.Tonemap_ctor(), Classes.Algorithm_dtor)
end
function Tonemap:process(t)
local argRules = {
{"src", required = true},
{"dst", default = nil}
}
local src, dst = cv.argcheck(t, argRules)
C.Tonemap_process(self.ptr, cv.wrap_tensor(src), cv.wrap_tensor(dst));
end
function Tonemap:getGamma()
return C.Tonemap_getGamma(self.ptr);
end
function Tonemap:setGamma(t)
local argRules = {
{"gamma", required = true}
}
local gamma = cv.argcheck(t, argRules)
C.Tonemap_setGamma(self.ptr, gamma)
end
end
Done! Check out ./demo/LineSegmentDetector.lua
for class usage example.