代码注入语义¶
API Extractor 提供了 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++ Wrapper还是Python Wrapper中(参见代码生成术语)。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方法包装器
( |
|
结束 |
此代码在Python方法包装器
( |
||
shell |
declaration |
仅用于虚函数。此代码被注入到顶部。 |
|
override |
仅用于虚函数。代码在调用Python覆盖的代码之前注入。 |
||
开始 |
仅用于虚函数。当函数没有Python实现时,代码会被注入,然后代码会在c++调用之前插入 |
||
end |
与上述相同,但代码在c++调用后插入 |
||
typesystem |
native |
beginning |
将代码写入模块 |
结束 |
将代码写入模块 |
||
目标 |
开始 |
在模块初始化函数的开始处插入代码
( |
|
end |
在模块初始化函数的末尾插入代码
( |
||
declaration |
将代码插入模块头部。 |
代码注入剖析¶
为了更清楚地说明问题,让我们使用一个简化的生成包装器代码示例,并展示每种代码注入的位置。
下面是将为其生成包装代码的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文件
包含绑定代码。下一节将使用生成的包装代码的简化版本,其中注入点用注释标记。
有许多由百分号%表示的占位符,这些占位符在插入代码时会被扩展。有关列表,请参见Type System Variables。
值得注意的案例¶
类型系统描述系统为绑定开发者提供了很大的灵活性,这种灵活性是力量,但也伴随着责任。对封装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将代码注入模块头部。这对于类型定义非常有用。