Добавление swig pythoncode для установки этого флага на объекте Python - PullRequest
0 голосов
/ 09 мая 2018

У меня есть контейнер класса C ++, MyContainer , содержащий объекты типа MyObject , также класса C ++.

Ниже приведен код заголовка C ++ ( freemenot.h )

#ifndef freemenotH
#define freemenotH
#include <vector>
#include <string>

using std::string;
class MyObject
{
    public:
                        MyObject(const string& lbl);
                        ~MyObject();
        string          getLabel();

    private:
        string          label;
};

class MyContainer
{
    public:
                    MyContainer();
                    ~MyContainer();
        void        addObject(MyObject* o);
        MyObject*   getObject(unsigned int t);
        int         getNrOfObjects();

    private:
        std::vector<MyObject*>   mObjects;
};

#endif

и это источник ( freemenot.cpp )

#include "freemenot.h"
#include <iostream>
using namespace std;

/* MyObject source */
MyObject::MyObject(const string& lbl)
:
label(lbl)
{ cout<<"In object ctor"<<endl; }

MyObject::~MyObject() { cout<<"In object dtor"<<endl; }
string MyObject::getLabel() { return label; }


/* MyContainer source */
MyContainer::MyContainer() { cout<<"In container ctor"<<endl; }

MyContainer::~MyContainer()
{
    cout<<"In container dtor"<<endl;
    for(unsigned int i = 0; i < mObjects.size(); i++)
    {
        delete mObjects[i];
    }
}

int MyContainer::getNrOfObjects() { return mObjects.size(); }
void MyContainer::addObject(MyObject* o) { mObjects.push_back(o); }
MyObject* MyContainer::getObject(unsigned int i) { return mObjects[i]; }

Обратите внимание, что объекты хранятсякак RAW-указатели в векторе.Класс разработан таким образом, и, таким образом, контейнер отвечает за освобождение объектов в своем деструкторе, как это делается в деструкторах для цикла.

В коде C ++, как показано ниже, объект o1 добавляется в контейнерc, который возвращается клиентскому коду

MyContainer* getAContainerWithSomeObjects()
{
  MyContainer* c = new MyContainer();
  MyObject* o1 = new MyObject();
  c.add(o1);
  return c;
}

Возвращенный контейнер владеет своими объектами и отвечает за удаление этих объектов после завершения.В C ++ доступ к объектам-контейнерам в порядке после выхода из указанной выше функции.

Для представления перечисленных выше классов в python с использованием Swig потребуется файл интерфейса.Этот интерфейсный файл выглядит следующим образом:

%module freemenot
%{    #include "freemenot.h"    %}
%include "std_string.i"
//Expose to Python
%include "freemenot.h"

. Для генерации модуля Python с использованием CMake использовался следующий скрипт CMake.

cmake_minimum_required(VERSION 2.8)
project(freemenot)

find_package(SWIG REQUIRED)
include(UseSWIG)
find_package(PythonInterp)
find_package(PythonLibs)
get_filename_component(PYTHON_LIB_FOLDER ${PYTHON_LIBRARIES} DIRECTORY CACHE)
message("Python lib folder: " ${PYTHON_LIB_FOLDER})
message("Python include folder: " ${PYTHON_INCLUDE_DIRS})
message("Python libraries: " ${PYTHON_LIBRARIES})

set(PyModule "freemenot")
include_directories(
    ${PYTHON_INCLUDE_PATH}
    ${CMAKE_CURRENT_SOURCE_DIR}
)

link_directories( ${PYTHON_LIB_FOLDER})

set(CMAKE_MODULE_LINKER_FLAGS ${CMAKE_CURRENT_SOURCE_DIR}/${PyModule}.def)

set_source_files_properties(${PyModule}.i PROPERTIES CPLUSPLUS ON)
set_source_files_properties(${PyModule}.i PROPERTIES SWIG_FLAGS "-threads")

SWIG_ADD_LIBRARY(${PyModule}
    MODULE LANGUAGE python
    SOURCES ${PyModule}.i freemenot.cpp)

SWIG_LINK_LIBRARIES (${PyModule} ${PYTHON_LIB_FOLDER}/Python37_CG.lib    )

# INSTALL PYTHON BINDINGS
# Get the python site packages directory by invoking python
execute_process(COMMAND python -c "import site; print(site.getsitepackages()[0])" OUTPUT_VARIABLE PYTHON_SITE_PACKAGES OUTPUT_STRIP_TRAILING_WHITESPACE)
message("PYTHON_SITE_PACKAGES = ${PYTHON_SITE_PACKAGES}")

install(
    TARGETS _${PyModule}
    DESTINATION ${PYTHON_SITE_PACKAGES})

install(
    FILES         ${CMAKE_CURRENT_BINARY_DIR}/${PyModule}.py
    DESTINATION   ${PYTHON_SITE_PACKAGES}
)

Генерация файлов make с использованием CMake и компиляцияс помощью компилятора borlands bcc32 генерируется модуль Python ( freemenot ), который устанавливается в папку действительных папок сайта python3.

Затем в Python для освещения проблемы можно использовать следующий сценарий

import freemenot as fmn

def getContainer():
   c = fmn.MyContainer()
   o1 = fmn.MyObject("This is a label")
   o1.thisown = 0
   c.addObject(o1)
   return c

c = getContainer()
print (c.getNrOfObjects())

#if the thisown flag for objects in the getContainer function
#is equal to 1, the following call return an undefined object
#If the flag is equal to 0, the following call will return a valid object
a = c.getObject(0)
print (a.getLabel())

Этот код Python может выглядеть хорошо, но не работает , как ожидалось.Проблема в том, что когда функция getContainer () возвращает, память для объекта o1 освобождается , если флаг thisown не установлен в ноль.Доступ к объекту после этой строки с использованием возвращенного контейнера приведет к катастрофе.Заметьте, в этом нет ничего плохого, так как именно так работает сборка мусора питонами.

Для приведенного выше варианта использования возможность установки объектов python thisown flag внутри функции addObject сделает объекты C ++ пригодными для использования в Python.Наличие пользователя для установки этого флага не является хорошим решением.Можно также расширить класс python с помощью функции «addObject» и изменить флаг thisown внутри этой функции, тем самым скрыв этот трюк памяти от пользователя.

Вопрос в том, как заставить Swig сделать это,без расширения класса?Я ищу использование typemap или, возможно, % pythoncode , но мне не удается найти хороший рабочий пример.

Приведенный выше код должен бытьиспользуется и передается программе C ++, которая вызывает интерпретатор Python.Программа на C ++ отвечает за управление памятью, выделенной в функции python, даже после PyFinalize ().

Приведенный выше код можно загрузить с github https://github.com/TotteKarlsson/miniprojects

Ответы [ 2 ]

0 голосов
/ 23 мая 2018

Существует несколько разных способов решения этой проблемы, поэтому я постараюсь объяснить их по очереди, опираясь на несколько вещей по пути. Надеемся, что это полезно для просмотра параметров и особенностей SWIG, даже если вам действительно нужен только первый пример.

Добавить код Python для непосредственного изменения thisown

Решение, наиболее похожее на предложенное вами, основано на использовании директивы %pythonprepend SWIG для добавления дополнительного кода Python. Вы можете настроить таргетинг на основе объявления C ++ о перегрузке, которая вас волнует, например ::10000

%module freemenot
%{    #include "freemenot.h"    %}
%include "std_string.i"

%pythonprepend MyContainer::addObject(MyObject*) %{
# mess with thisown
print('thisown was: %d' % args[0].thisown)
args[0].thisown = 0
%}

//Expose to Python
%include "freemenot.h"

Там, где единственная примечательная особенность возникает из-за того, что аргументы передаются с использованием *args вместо именованных аргументов, поэтому мы должны получить к нему доступ по номеру позиции.

Есть несколько других мест / методов для вставки дополнительного кода Python (при условии, что вы не используете -builtin) в документации SWIG Python , и всегда можно использовать параметр «monkey patching».

Используйте Python C API для настройки thisown

Следующим возможным вариантом здесь является использование таблицы типов, вызывающей API-интерфейс Python C, для выполнения эквивалентных функций. В этом случае я соответствовал типу аргумента и имени аргумента, но это означает, что карта типов будет применена ко всем функциям, которые получают MyObject * с именем o. (Самое простое решение в этом случае - сделать так, чтобы имена описывали предполагаемую семантику в заголовках, если в настоящее время они будут соответствовать друг другу, что имеет побочное преимущество, заключающееся в упрощении IDE и документации).

%module freemenot
%{    #include "freemenot.h"    %}
%include "std_string.i"

%typemap(in) MyObject *o {
    PyObject_SetAttrString($input, "thisown", PyInt_FromLong(0)); // As above, but C API
    $typemap(in,MyObject*); // use the default typemap
}

//Expose to Python
%include "freemenot.h"

Самым примечательным моментом в этом примере, отличном от сопоставления карты типов, является использование $typemap здесь для «вставки» другой карты типов, в частности, значения по умолчанию для MyObject*, в нашу собственную карту типов. Стоит взглянуть в сгенерированный файл оболочки на пример до / после того, как это выглядит в итоге.

Используйте среду выполнения SWIG для прямого доступа к SwigPyObject элементу own struct *

Поскольку мы уже пишем C ++ вместо того, чтобы проходить через setattr в коде Python, мы можем адаптировать эту карту типов для использования большего количества внутренних компонентов SWIG и пропустить переход от C к Python и обратно к C снова.

Внутри SWIG есть структура, которая содержит детали каждого экземпляра, включая владельца, тип и т. Д.

Мы могли бы просто привести непосредственно от PyObject* к SwigPyObject*, но для этого потребовалось бы написать обработку ошибок / проверку типа (это PyObject, даже SWIG?) И мы сами стали бы зависеть от деталей различных различных способов. SWIG может создавать интерфейсы Python. Вместо этого есть одна функция, которую мы можем вызвать, которая обрабатывает все это для нас, поэтому мы можем написать нашу карту типов следующим образом:

%module freemenot
%{    #include "freemenot.h"    %}
%include "std_string.i"

%typemap(in) MyObject *o {
    // TODO: handle NULL pointer still
    SWIG_Python_GetSwigThis($input)->own = 0; // Safely cast $input from PyObject* to SwigPyObject*
    $typemap(in,MyObject*); // use the default typemap
}

//Expose to Python
%include "freemenot.h"

Это всего лишь эволюция предыдущего ответа, но реализованная исключительно во время выполнения SWIG C.

Скопируйте конструкцию нового экземпляра перед добавлением

Существуют и другие способы решения этой проблемы собственности. Во-первых, в этом конкретном случае ваш MyContainer предполагает, что он всегда может вызывать delete для каждого экземпляра, который он хранит (и, следовательно, владеет этой семантикой).

Мотивирующим примером для этого было бы, если бы мы также обернули такую ​​функцию:

MyObject *getInstanceOfThing() {
    static MyObject a;
    return &a;
}

Что приводит к проблеме с нашими предыдущими решениями - мы устанавливаем thisown в 0, но здесь это уже было бы 0, и поэтому мы все еще не можем юридически вызвать delete для указателя, когда контейнер освобожден.

Существует простой способ справиться с этим, который не требует знания внутренних компонентов прокси-сервера SWIG - при условии, что MyObject является копируемым, тогда вы можете просто создать новый экземпляр и быть уверенным, что независимо от того, откуда он взялся, он собирается быть законным для контейнера, чтобы удалить его. Мы можем сделать это, немного адаптировав нашу карту типов:

%module freemenot
%{    #include "freemenot.h"    %}
%include "std_string.i"

%typemap(in) MyObject *o {
    $typemap(in,MyObject*); // use the default typemap as before
    $1 = new $*1_type(*$1); // but afterwards call copy-ctor
}

//Expose to Python
%include "freemenot.h"

Обратите внимание на то, что здесь используется еще несколько функций SWIG, которые позволяют нам знать тип входных данных карты типов - $*1_type - это тип аргумента карты типов, на который однажды ссылались. Мы могли бы просто написать MyObject здесь, так как это то, к чему он относится, но это позволяет вам обрабатывать такие вещи, как шаблоны, если ваш контейнер действительно является шаблоном, или повторно использовать карту типов в других подобных контейнерах с %apply.

Здесь нужно следить за утечками, если у вас была функция C ++, которую вы намеренно позволяли возвращать экземпляру без установки thisown в предположении, что контейнер станет владельцем, который теперь не будет храниться.

Дайте контейнеру возможность управлять владельцем

Наконец, одна из других техник, которые мне нравятся, не так часто здесь возможна, как в настоящее время, но стоит упомянуть для потомков. Если у вас есть возможность хранить некоторые дополнительные данные рядом с каждым экземпляром в контейнере, вы можете вызвать Py_INCREF и сохранить ссылку на базовый PyObject* независимо от того, откуда он взялся. При условии, что вы получите обратный вызов во время уничтожения, вы также можете вызвать Py_DECREF и заставить среду выполнения Python поддерживать объект живым, пока он находится в контейнере.

Вы также можете сделать это даже тогда, когда невозможно сохранить соединение 1-1 MyObject* / PyObject*, сохранив теневой контейнер где-нибудь еще. Это может быть трудно сделать, если вы не хотите добавить другой объект в контейнер, создать его подкласс или не можете быть уверены, что начальный экземпляр Python контейнера всегда будет жить достаточно долго.

0 голосов
/ 11 мая 2018

Вы ищете % newobject .Вот небольшой пример:

%module test

%newobject create;
%delobject destroy;

%inline %{

#include <iostream>
struct Test
{
    Test() { std::cout << "create" << std::endl; }
    ~Test() { std::cout << "destroy" << std::endl; }
};

Test* create() { return new Test; }
void destroy(Test* t) { delete t; }

%}

Использование:

>>> import test
>>> t1 = test.create()    # create a test object
create
>>> t2 = test.Test()      # don't really need a create function :)
create
>>> t3 = test.create()    # and another.
create
>>> test.destroy(t2)      # explicitly destroy one
destroy
>>>
>>>
>>>
>>> ^Z                   # exit Python and the other two get destroyed.

destroy
destroy
...