代码注入语义#

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

将代码写入类包装器 .cpp 文件的开始处,紧接在 #include 指令之后。常见用途是写入自定义函数的原型,这些函数的定义将放在 native/end 代码注入中。

end

将代码写入类包装器 .cpp 文件的末尾。可用于编写在 native/beginning 中声明的自定义/辅助函数的定义。

target

beginning

在包装器初始化函数的开始处(init_CLASS(PyObject* module))放置自定义代码。这可以用来在将 PyCLASS_Type 结构注册到 Python 之前对其进行操作。

end

在类包装器初始化函数的末尾(init_CLASS(PyObject* module))写入指定的自定义代码。这里的代码将在所有包装的类组件初始化之后执行。

modify-function

native

beginning

此代码段放置在C++包装类的虚拟方法重写中(负责将C++调用传递给Python重写,如果有的话),在将C++参数转换之后但在Python调用之前。

end

此代码注入位于C++包装类的虚拟方法重写中,在Python调用之后但在解引用Python方法和参数元组之前。

target

beginning

此代码在Python方法包装器中注入(PyCLASS_METHOD(...)),在决策者找到要调用的signature之后,以及将用于调用的参数转换之后,但在实际调用之前。

end

此代码在Python方法包装器中注入(PyCLASS_METHOD(...)),在C++方法调用之后注入,但仍在为每个signature创建的作用域内。

shell

声明

仅用于虚拟函数。此代码在顶部注入。

beginning

仅用于虚拟函数。当函数没有Python实现时注入代码,然后代码在C++调用之前插入

end

与上述类似,但代码是在C++调用之后插入

typesystem

native

beginning

将代码写入模块.cpp文件的开头,紧随#include语句。此位置与包装类.cpp文件上的native/beginning位置具有类似的目的,即写入函数原型,但不限于这种用途。

end

将代码写入模块.cpp文件的结尾。通常这是为文件开头插入并带有native/beginning代码注入的功能原型提供的实现。

target

beginning

在模块初始化函数(initMODULENAME())的开始插入代码,在调用Py_InitModule之前。

end

在模块初始化函数(initMODULENAME())的结尾插入代码,但在遇到导入模块问题时发出致命错误检查之前。

声明

将代码插入模块头文件中。

代码注入解剖#

为了使事情更加清晰,我们可以使用一个简化的示例生成的包装代码以及各种代码注入的位置。

下面是生成包装代码的示例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++方法。每个重载方法的签名都有自己的 beginningend 代码注入。

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``

此外,可以通过指定 targetdeclaration 来向模块头部注入代码。这对于类型定义很有用。