代码注入语义

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

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

end

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

target

beginning

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

end

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

modify-function

native

beginning

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

end

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

target

beginning

这段代码被注入到Python方法包装器 (PyCLASS_METHOD(...))中,正好在决策器找到要调用的签名之后,并且在转换要使用的参数之后,但在实际调用之前。

结束

此代码在Python方法包装器 (PyCLASS_METHOD(...))中注入,紧接在C++方法调用之后, 但仍然在每个签名的重载创建的范围内。

shell

declaration

仅用于虚函数。此代码被注入到顶部。

override

仅用于虚函数。代码在调用Python覆盖的代码之前注入。

开始

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

end

与上述相同,但代码在c++调用后插入

typesystem

native

beginning

将代码写入模块.cpp文件的开头,紧接在#include语句之后。这个位置与包装类.cpp文件中的native/beginning位置有类似的用途,即编写函数原型,但不限于此用途。

结束

将代码写入模块.cpp文件的末尾。通常在文件开头使用native/beginning代码注入插入的函数原型的实现。

目标

开始

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

end

在模块初始化函数的末尾插入代码 (initMODULENAME()),但在检查之前,如果导入模块时出现问题,则会发出致命错误。

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++方法。每个重载方法签名都有自己的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将代码注入模块头部。这对于类型定义非常有用。