这个问题实际上是以下两个问题的延伸:
假设我有以下c ++类(简化),我使用SWIG向Python公开:
struct Component
{
virtual void update(double dt);
}
struct DerivedComponent : public Component
{
void update(double dt) { std::cout << "DerivedComponent::update()" << std::endl; }
void f() { std::cout << "DerivedComponent::f()" << std::endl; }
}
class Entity
{
public:
Component* component(const std::string& class_name)
{
return m_components[class_name];
}
Component* create_component(const std::string& class_name)
{
// Creates a new Component, possibly using the method described in (1),
// adds it to m_components and returns the component.
// This method can also instantiate subclasses of Component that were
// defined in c++.
}
private:
std::unordered_map<std::string, Component*> m_components;
}
现在,在Python中,我定义了一个继承自Component
:
class PythonDerivedComponent(Component):
def __init__(self):
Component.__init__(self)
def update(self, dt):
print("DerivedComponent::update(" + str(dt) + ")")
def g()
print("DerivedComponent::g()")
我已经到了可以向实体添加组件的阶段。另外,使用Flexo在(2)中描述的方法,我可以从create_component()
检索派生的组件类型,以便在c ++中定义组件时使用:
e = Entity()
c = e.create_component("DerivedComponent") # Has type <class 'module.DerivedComponent'>
c.f() # Prints "DerivedComponent::f()" as expected.
现在,我的问题是,当在Python中定义组件时,是否可以从create_component()
获取派生组件类型?当然,我只需要能够在Python中做到这一点。
e = Entity("Player")
e.create_component("PythonDerivedComponent")
# Somewhere else...
e = get_entity("Player")
c = e.component("PythonDerivedComponent") # Has type <class 'module.Component'>
c.g() # Can't call this function.
答案 0 :(得分:3)
为了完成一个完整的工作演示,我不得不稍微扩展你的头文件,我的结果看起来像:
#ifndef TEST_HH
#define TEST_HH
#include <map>
#include <functional>
#include <iostream>
#include <string>
struct Component
{
virtual void update(double dt) = 0;
virtual ~Component() {}
};
struct DerivedComponent : public Component
{
void update(double) { std::cout << "DerivedComponent::update()" << std::endl; }
void f() { std::cout << "DerivedComponent::f()" << std::endl; }
static DerivedComponent *create() {
return new DerivedComponent;
}
};
class Entity
{
public:
Component* component(const std::string& class_name)
{
return m_components[class_name];
}
Component* create_component(const std::string& class_name)
{
// Creates a new Component, possibly using the method described in (1),
// adds it to m_components and returns the component.
// This method can also instantiate subclasses of Component that were
// defined in c++.
Component *result = nullptr;
if (m_components[class_name]) {
result = m_components[class_name];
}
else if (m_registry[class_name]) {
result = m_registry[class_name]();
m_components[class_name] = result;
}
return result; // Or raise an exception if null?
}
void register_component(const std::string& class_name, std::function<Component*()> creator) {
m_registry[class_name] = creator;
}
private:
std::map<std::string, Component*> m_components;
std::map<std::string, std::function<Component*()> > m_registry;
};
inline void register_builtins(Entity& e) {
e.register_component("DerivedComponent", DerivedComponent::create);
}
#endif
主要是修复了一些语法错误并添加了类型注册表,其中std::function
个对象知道如何创建实例。
我们正在根据您引用的两个问题构建字体图,所以我不会在这个答案中谈论这个部分,除了说为了让您的示例Python类工作我们有使用董事(否则它是永久抽象的)和&#39; out&#39;上一个问题中的typemap已经过修改,可以在更多地方应用,也可以添加额外的功能。 (请注意,这假设带有class_name
的字符串始终为arg2
。您可以向返回此名称的基类添加一个函数,这将消除对此假设的需要)
我们需要两个主要技巧来完成这项工作:
Component::register_component
。在这种情况下,我使用C ++ 11 lambda函数实现了它,它保留了对它将要生成的产品类型的引用。 (请注意,我的示例会泄漏这些类型,因为它永远不会减少引用计数器。如果这对您的使用有问题,您应该使用智能指针。)PyObject
。有几种方法可以做到这一点,例如使用std::map<Component*,PyObject*>
的全局地图,或者在test.hh中的Entity
类中实际添加它。我不喜欢全局变量,并且假设你不希望最终混淆Python接口与C +++实现的关注点。因此,我选择添加另一个继承自Component的中间抽象类,仅用于记住实现它的PyObject
。 添加中间PythonComponent
类可能会带来额外的好处,这就是您不会为纯C ++派生类型支付SWIG导向器的成本。 (如果您希望自己可以使用%pythoncode
和%rename
来玩游戏,假装他们真的只使用Component
而不是PythonComponent
,但是我可以使用%module(directors=1) test
%{
#include "test.hh"
#include <cassert>
struct PythonComponent : Component {
PyObject *derrived;
};
%}
%feature("director") PythonComponent;
%include <std_string.i>
// Note: this now gets applied to anything returning Component *
%typemap(out) Component * {
const PythonComponent * const pycomp = dynamic_cast<PythonComponent*>($1);
if (pycomp) {
$result = pycomp->derrived;
Py_INCREF($result);
}
else {
const std::string lookup_typename = *arg2 + " *";
swig_type_info * const outtype = SWIG_TypeQuery(lookup_typename.c_str());
$result = SWIG_NewPointerObj(SWIG_as_voidptr($1), outtype, $owner);
}
}
%include "test.hh"
struct PythonComponent : Component {
};
%extend Entity {
void register_component(const std::string& class_name, PyObject *python_type) {
assert(PyCallable_Check(python_type));
Py_INCREF(python_type);
$self->register_component(class_name, [python_type](){
PyObject *pyinstance = PyObject_CallObject(python_type, NULL);
void *result;
const auto res = SWIG_ConvertPtr(pyinstance, &result,SWIGTYPE_p_PythonComponent, 0);
if (!SWIG_IsOK(res)) {
assert(false); // TODO: raise exception
}
const auto out = reinterpret_cast<PythonComponent *>(result);
out->derrived = pyinstance;
return out;
});
}
}
。我没有这样做)
我的SWIG界面文件最终看起来像:
%extend
我们使用register_component
来实现PythonComponent
的重载。这种重载还允许我们在Python中控制本机C ++类型的注册,只需要很少的额外工作I wrote an answer about that previously。
就Python包装器而言,Component
类型实际上并没有真正改变dynamic_cast
。保留引用的详细信息将作为实现细节保留。
有了这些机制,我们需要做的就是让这项工作是实施新的&#39; out&#39;添加PythonComponent
的typemap,用于确定表中的C ++类型是否真的是PyObject
,是否使用保留的swig -py3 -c++ -python -Wall test.i
g++ -std=c++11 -Wall -Wextra test_wrap.cxx -I/usr/include/python3.4/ -lpython3.4m -shared -o _test.so
而不是查找SWIG类型。< / p>
我们编译:
register_component
我调整了您的测试用例以纠正一些问题并致电from test import *
e = Entity()
register_builtins(e)
c = e.create_component("DerivedComponent") # Has type <class 'module.DerivedComponent'>
c.f() # Prints "DerivedComponent::f()" as expected.
class PythonDerivedComponent(PythonComponent):
def update(self, dt):
print("PythonDerivedComponent::update(" + str(dt) + ")")
def g(self):
print("PythonDerivedComponent::g()")
e.register_component("PythonDerivedComponent", PythonDerivedComponent)
e.create_component("PythonDerivedComponent")
c = e.component("PythonDerivedComponent")
print(type(c))
c.g() # Now works.
:
DerivedComponent::f()
<class '__main__.PythonDerivedComponent'>
PythonDerivedComponent::g(
当我们运行它时,我们看到:
{{1}}