代码注入语义#
API 提取器 提供了 inject-code 标签,允许用户将自定义编写的代码插入到生成的代码的特定位置。然而,这仅是实现正确绑定代码的一部分,自定义代码应写入的位置取决于生成的绑定代码所使用的的技术。
这是对 Qt for Python 重要的 inject-code
标签选项。
<inject-code class="native | target" position="beginning | end">
// custom code
</inject-code>
inject-code 标签#
以下表格描述了在 Qt for Python 中使用 inject-code 标签的语义。`class` 属性指定是要在 C++ 包装器 还是 Python 包装器 中注入代码(参见 代码生成术语)。`position` 属性指定自定义代码在函数中的位置。
父标签 |
类 |
位置 |
含义 |
---|---|---|---|
value-type, object-type |
native |
beginning |
将代码写入类包装器 |
end |
将代码写入类包装器 |
||
target |
beginning |
在包装器初始化函数的开始处( |
|
end |
在类包装器初始化函数的末尾( |
||
modify-function |
native |
beginning |
此代码段放置在C++包装类的虚拟方法重写中(负责将C++调用传递给Python重写,如果有的话),在将C++参数转换之后但在Python调用之前。 |
end |
此代码注入位于C++包装类的虚拟方法重写中,在Python调用之后但在解引用Python方法和参数元组之前。 |
||
target |
beginning |
此代码在Python方法包装器中注入( |
|
end |
此代码在Python方法包装器中注入( |
||
shell |
声明 |
仅用于虚拟函数。此代码在顶部注入。 |
|
beginning |
仅用于虚拟函数。当函数没有Python实现时注入代码,然后代码在C++调用之前插入 |
||
end |
与上述类似,但代码是在C++调用之后插入 |
||
typesystem |
native |
beginning |
将代码写入模块 |
end |
将代码写入模块 |
||
target |
beginning |
在模块初始化函数( |
|
end |
在模块初始化函数( |
||
声明 |
将代码插入模块头文件中。 |
代码注入解剖#
为了使事情更加清晰,我们可以使用一个简化的示例生成的包装代码以及各种代码注入的位置。
下面是生成包装代码的示例C++类。
class InjectCode
{
public:
InjectCode();
double overloadedMethod(int arg);
double overloadedMethod(double arg);
virtual int virtualMethod(int arg);
};
从C++类中,Qt for Python将生成一个包含绑定代码的injectcode_wrapper.cpp
文件。下一节将使用带有注释标记注入点的简化的生成包装代码。
有几个用百分号%
指示的占位符,在插入代码时会展开。有关列表,请参阅类型系统变量。
值得注意的案例#
类型系统描述系统为绑定开发者提供了很大的灵活性,这是力量,但随之而来的是责任。对包装API的一些修改如果不在一些代码注入的情况下将不会完全完成。
移除参数并为其设置默认值#
一个简单的例子是在去除一个参数后,就像C++方法 METHOD(ARG)
更改为用于Python的 METHOD()
;当然,绑定开发者必须向生成器提供一些指导,以便知道如何调用它。最常用的解决方案是同时删除参数并设置其默认值,这样原始的C++方法就可以无问题地被调用。
手动移除参数并调用方法#
如果移除了参数但没有提供默认值,生成器将不会写出对方法的任何调用并期待使用 modify-function - target/beginning
代码注入来根据本身的条款调用原始的C++方法。如果就连这种自定义代码都没有提供,生成器会放置一个 #error
子句以阻止错误绑定代码的编译。
总是手动调用方法!#
如果您的注入自定义代码中包含对包装C++方法的调用,这肯定意味着您不希望生成器重新调用相同的方法。正如预期的那样,Qt for Python会在代码注入中检测到用户编写的调用,并不会写入自己的调用,但要使这正常工作,绑定开发者必须使用模板变量 %FUNCTION_NAME
而不是编写包装方法/函数的实际名称。
换句话说,使用
<inject-code class="target" position="beginning | end">
%CPPSELF.originalMethodName();
</inject-code>
而不是
<inject-code class="target" position="beginning | end">
%CPPSELF.%FUNCTION_NAME();
</inject-code>
函数/方法的代码注入#
在本地端#
注意,这仅在存在C++包装器时才使用,即包装的类是多态的。
int InjectCodeWrapper::virtualMethod(int arg)
{
PyObject *method = BindingManager::instance().getOverride(this, "virtualMethod");
if (!py_override)
return this->InjectCode::virtualMethod(arg);
(... here C++ arguments are converted to Python ...)
// INJECT-CODE: <modify-function><inject-code class="native" position="beginning">
// Uses: pre method call custom code, modify the argument before the
// Python call.
(... Python method call goes in here ...)
// INJECT-CODE: <modify-function><inject-code class="native" position="end">
// Uses: post method call custom code, modify the result before delivering
// it to C++ caller.
(... Python method and argument tuple are dereferenced here ...)
return Shiboken::Converter<int>::toCpp(method_result);
}
在目标端#
从C++中收集的方法的所有重载都在单个Python方法上,该方法使用重载决策器根据Python调用的参数调用正确的C++方法。每个重载方法的签名都有自己的 beginning
和 end
代码注入。
static PyObject *PyInjectCode_overloadedMethod(PyObject *self, PyObject *arg)
{
PyObject* py_result{};
if (PyFloat_Check(arg)) {
double cpp_arg0 = Shiboken::Converter<double >::toCpp(arg);
// INJECT-CODE: <modify-function><inject-code class="target" position="beginning">
// Uses: pre method call custom code.
py_result = Shiboken::Converter<double >::toPython(
PyInjectCode_cptr(self)->InjectCode::overloadedMethod(cpp_arg0)
);
// INJECT-CODE: <modify-function><inject-code class="target" position="end">
// Uses: post method call custom code.
} else if (PyNumber_Check(arg)) {
(... other overload calling code ...)
} else goto PyInjectCode_overloadedMethod_TypeError;
if (PyErr_Occurred() || !py_result)
return {};
return py_result;
PyInjectCode_overloadedMethod_TypeError:
PyErr_SetString(PyExc_TypeError, "'overloadedMethod()' called with wrong parameters.");
return {};
}
包装类的代码注入#
在本地端#
这些注入放入包装类的 CLASSNAME_wrapper.cpp
文件的主体中。
// Start of ``CLASSNAME_wrapper.cpp``
#define protected public
// default includes
#include <shiboken.h>
(...)
#include "injectcode_wrapper.h"
using namespace Shiboken;
// INJECT-CODE: <value/object-type><inject-code class="native" position="beginning">
// Uses: prototype declarations
(... C++ wrapper virtual methods, if any ...)
(... Python wrapper code ...)
PyAPI_FUNC(void)
init_injectcode(PyObject *module)
{
(...)
}
(...)
// INJECT-CODE: <value/object-type><inject-code class="native" position="end">
// Uses: definition of functions prototyped at ``native/beginning``.
// End of ``CLASSNAME_wrapper.cpp``
在目标端#
向类的Python初始化函数进行代码注入。
// Start of ``CLASSNAME_wrapper.cpp``
(...)
PyAPI_FUNC(void)
init_injectcode(PyObject *module)
{
// INJECT-CODE: <value/object-type><inject-code class="target" position="beginning">
// Uses: Alter something in the PyInjectCode_Type (tp_flags value for example)
// before registering it.
if (PyType_Ready(&PyInjectCode_Type) < 0)
return;
Py_INCREF(&PyInjectCode_Type);
PyModule_AddObject(module, "InjectCode",
((PyObject*)&PyInjectCode_Type));
// INJECT-CODE: <value/object-type><inject-code class="target" position="end">
// Uses: do something right after the class is registered, like set some static
// variable injected on this same file elsewhere.
}
(...)
// End of ``CLASSNAME_wrapper.cpp``
模块的代码注入#
C++库被包装为Python模块,一组类、函数、枚举和命名空间。Qt for Python为它们创建包装文件,并为整个模块创建一个额外的 MODULENAME_module_wrapper.cpp
文件以注册整个模块。具有 typesystem
标签的父标签的代码注入XML标签将放在该文件上。
在本地端#
这与类包装器的代码注入 在本地端 的工作方式完全相同。
在目标端#
这与类包装代码注入相似 在目标端。注意,在 target/end
注入的代码在错误检查之前被插入,以防止不良的自定义代码未被发现地通过。
// Start of ``MODULENAME_module_wrapper.cpp``
(...)
initMODULENAME()
{
// INJECT-CODE: <typesystem><inject-code class="target" position="beginning">
// Uses: do something before the module is created.
PyObject *module = Py_InitModule("MODULENAME", MODULENAME_methods);
(... initialization of wrapped classes, namespaces, functions and enums ...)
// INJECT-CODE: <typesystem><inject-code class="target" position="end">
// Uses: do something after the module is registered and initialized.
if (PyErr_Occurred())
Py_FatalError("can't initialize module sample");
}
(...)
// Start of ``MODULENAME_module_wrapper.cpp``
此外,可以通过指定 target
和 declaration
来向模块头部注入代码。这对于类型定义很有用。