#### 本文為[李你幹嘛](https://www.cnblogs.com/liniganma)原創,轉載請註明出處:[Pybind11綁定C++抽象類(DLL介面)](https://www.cnblogs.com/liniganma/p/17666063.html) # 摘要 假設我們將DLL ...
本文為李你幹嘛原創,轉載請註明出處:Pybind11綁定C++抽象類(DLL介面)
摘要
假設我們將DLL中的介面封裝成了C++抽象類,並將該類和DLL文件提供給用戶,類似於抽象類導出DLL中描述的辦法,如果這個時候我們想使用pybind11綁定這個C++抽象類,會遇到報錯,如抽象類無法實例化等等,此時Pybind11給出了輔助類的辦法overriding-virtual-functions-in-python,但是如果只想轉換C++抽象類的一部分的話,這個方案是不適用的。Pybind11有個很強大的功能,如果我們將C++類使用py::class綁定後,那麼C++暴露給python的這個類會自動轉換成Python的類。如果我們要大量的在Python中使用到這個C++抽象類且接觸不到其基類時,就沒有辦法完成這個抽象類的綁定。
在這裡我們給出一個解決思路,即用Wrapper類將C++的抽象類封裝,並對這個類使用pybind綁定,這樣我們就有了一個Python端的Wrapper類。再根據官網給出的辦法Custom Type Casters實現從Python端Wrapper類到C++抽象類和從C++抽象類到Python端Wrapper類的自動轉換。這樣當C++暴露給Python這個抽象類時,pybind會自動調用轉換器將抽象類轉換成Wrapper類的Python對象,當Python的Wrapper類傳遞給C++時,會將Wrapper類變成C++抽象類。
問題描述
假設我們將C++抽象類AbstractDLLInterface作為DLL介面,ConcreteDLLInterface1類作為具體實現,但是並不把它暴露給用戶。
#ifdef MYDLL_EXPORTS
#define MYDLL_API __declspec(dllexport)
#else
#define MYDLL_API __declspec(dllimport)
#endif
// 抽象類介面
class MYDLL_API AbstractDLLInterface {
public:
virtual ~AbstractDLLInterface() {}
virtual void dllFunction() = 0;
virtual AbstractDLLInterface* createInstance() const = 0;
};
// 具體的實現類1
class ConcreteDLLInterface1 : public AbstractDLLInterface {
public:
void dllFunction() override;
AbstractDLLInterface* createInstance() const override;
};
// 在實現文件中提供具體實現
void ConcreteDLLInterface1::dllFunction() {
// DLL 介面函數的具體實現
// ...
}
AbstractDLLInterface* ConcreteDLLInterface1::createInstance() const {
return new ConcreteDLLInterface1();
}
現在我們只有AbstractDLLInterface類的聲明和一個DLL文件,我們的C++代碼中需要經常使用AbstractDLLInterface類作為返回值或者函數參數,而我們需要把這一部分用Pybind綁定。下麵給出解決方案。
解決方案
創建Wrapper類
class Wrapper {
public:
Wrapper(AbstractDLLInterface* instance) : instance_(instance) {}
void dllFunction() {
instance_->dllFunction();
}
AbstractDLLInterface* instance_;
};
定義AbstractDLLInterface類的type_caster
namespace PYBIND11_NAMESPACE {
namespace detail {
template <> struct type_caster<AbstractDLLInterface> {
public:
PYBIND11_TYPE_CASTER(AbstractDLLInterface, const_name("AbstractDLLInterface"));
/**
* Conversion part 1 (Python -> C++): convert a PyObject into an AbstractDLLInterface
*/
bool load(handle src, bool) {
Wrapper wrapper = py::cast<Wrapper>(src);
value = *(wrapper.instance_);
return true;
}
/**
* Conversion part 2 (C++ -> Python): convert an AbstractDLLInterface into a PyObject
*/
static handle cast(AbstractDLLInterface src, return_value_policy policy, handle parent) {
std::shared_ptr<Wrapper> wrapper_ptr = std::make_shared<Wrapper>(&src);
return type_caster<std::shared_ptr<Wrapper>>::cast(wrapper_ptr, py::return_value_policy::take_ownership, parent);
}
};
}
} // namespace PYBIND11_NAMESPACE::detail
Pybind中的處理思路無非是在綁定好的類、函數上,在python遇到定義過的類或者類型等就將其從C++的類包裝成python的類,在python端有參數要傳遞給C++就將參數從Python類轉換成C++類。上面的代碼就實現了這個過程,我們將Python中的Wrapper類與C++中的AbstractDLLInterface類視為等效的,那麼如果有Python中的Wrapper類需要傳入到C++中時,會調用load將AbstractDLLInterface類實例從Wrapper類中提取出來,如果C++中有AbstractDLLInterface類實例要傳入到Python中時,會調用cast新建一個Wrapper實例,再將這個Wrapper實例轉換成Python對象傳入到Python空間中。
實際操作用大多數不會用到AbstractDLLInterface而是AbstractDLLInterface的智能指針,此處是對AbstractDLLInterface進行轉換,但是處於安全性考慮最好對std::shared_ptr<AbstractDLLInterface>類型進行轉換。
綁定Pybind
// 綁定代碼
PYBIND11_MODULE(my_module, m) {
py::class_<Wrapper, std::shared_ptr<Wrapper>>(m, "Wrapper")
.def(py::init<AbstractDLLInterface*>())
.def("dllFunction", &Wrapper::dllFunction);
}
註意此時我們不需要綁定AbstractDLLInterface類,綁定AbstractDLLInterface類編譯時會報錯。py::class_傳入參數Wrapper, std::shared_ptr