Экземпляр класса C из указателя Void с использованием Ctypes

У меня есть C DLL, которая предоставляет несколько методов, которые возвращают void pointers в класс следующим образом:

void *GetLicense() {
    static AppLicenseImpl ipds_;
    return (void *) &ipds_;
}

В С++ после загрузки DLL я бы сделал это, чтобы работать с ней:

typedef void *(* FPGetLicense)();
GetLicense_ = (FPGetLicense)GetAddress("GetLicense");
license_ = (AppLicense *) GetLicense_();
license_->GetApplicationStatus(); // Load data so that other calls don't fail

Я не могу понять, как сделать это параллельно в Python. Это дает мне указатель:

d = ctypes.cdll.LoadLibrary('license.dll')
d.GetLicense.restype = ctypes.c_void_p
p = d.GetLicense() # returns ptr loc, something like 8791433660848L

Но я, очевидно, не могу вызвать p.GetApplicationStatus() в Python. Есть ли у кого-нибудь предложение о том, как я могу создать экземпляр этого класса в Python, чтобы я мог вызывать GetApplicationStatus()?


person g.d.d.c    schedule 15.10.2013    source источник
comment
Что вы подразумеваете под созданием экземпляра этого класса до конца? Экземпляр уже был полностью создан, когда GetLicense возвращается — по крайней мере, так должно быть, иначе следующая строка C++ приведет к segfault.   -  person abarnert    schedule 15.10.2013
comment
Прошу прощения, моя терминология, вероятно, плоха. Требуется ли для этого, чтобы в моем модуле Python было аналогичное определение класса, отражающее класс AppLicense в коде c/c++?   -  person g.d.d.c    schedule 15.10.2013
comment
Ну, ваш код C не может иметь никакой ссылки на класс AppLicense. Вдобавок ко всему, нет реального переносимого способа ссылаться на типы C++ из .DLL/.so в C++; вот почему у вас обычно есть extern "C" оболочки, которые принимают struct или void* в качестве первого аргумента в первую очередь. Но если вы хотите получить доступ к членам структуры или класса из ctypes, тогда да, вам нужно определить ctypes.Struct. Если вы не хотите этого делать, рассмотрите возможность использования cffi или создания пользовательских привязок (возможно, с помощью Cython) вместо использования ctypes.   -  person abarnert    schedule 16.10.2013
comment
Вы можете сделать AppLicense подклассом c_void_p. Тогда экземпляр self является указателем на объект C++. Добавьте методы, которые заключают в себе заглушки extern "C", вызывающие методы C++. Добавьте метод _check_retval_ для централизованной проверки ошибок класса, когда он используется как restype.   -  person Eryk Sun    schedule 16.10.2013
comment
@eryksun - Есть ли шанс, что вы можете показать мне пример кода на этот счет? Кажется, я не могу понять, как использовать указатель, чтобы добраться до нужного мне метода. Я не понимаю, как мне помогает наличие self в моем классе, ссылающееся на указатель.   -  person g.d.d.c    schedule 16.10.2013
comment
Экспортируйте функцию extern "C", такую ​​как AppLicense_GetApplicationStatus, которая принимает AppLicense * и вызывает метод GetApplicationStatus. Класс Python AppLicense (подкласс c_void_p) имеет метод GetApplicationStatus, который вызывает d.AppLicense_GetApplicationStatus(self). Вы бы установили d.GetLicense.restype = AppLicense и т. д. Как уже упоминал Абарнерт, вы можете изучить альтернативы для обертывания библиотек C++, таких как Boost.Python или SWIG.   -  person Eryk Sun    schedule 16.10.2013
comment
@eryksun - Есть ли шанс, что вы можете опубликовать это как ответ, чтобы я мог его принять? Экспорт другой функции для передачи указателя обратно работал так, как мне было нужно.   -  person g.d.d.c    schedule 16.10.2013


Ответы (2)


Цитата из документов:

Иногда у вас есть экземпляры несовместимых типов. В C вы можете привести один тип к другому типу. ctypes предоставляет функцию cast(), которую можно использовать таким же образом.

Итак, Python-эквивалент кода C++:

license = cast(d.GetLicense(), ctypes.POINTER(AppLicense))
license.GetApplicationStatus()

Однако часто в этом нет необходимости; вы можете просто сделать это:

d.GetLicense.restype = ctypes.POINTER(AppLicense)

Это похоже на "обман", но на самом деле это не так. Вы просто говорите ему вызвать конструктор POINTER(AppLicense) с результатом. А поскольку POINTER(AppLicense) является типом данных ctypes, ему не нужно будет сначала предполагать, что результатом является C int.

person abarnert    schedule 15.10.2013
comment
Спасибо, я думаю. Вероятно, мне не хватает некоторого кода поддержки в моем модуле Python для класса. Я прочитаю еще немного и, возможно, обновлю вопрос, когда буду ближе / получу лучшие трассировки или смогу более кратко задать вопрос. - person g.d.d.c; 15.10.2013

Я провел с этим больше времени - из С++, когда я хочу работать с экземпляром класса, на который ссылается указатель void, я делаю что-то вроде этого:

class AppLicense {
public:
    AppLicense() {}
    virtual LicenseStatus GetApplicationStatus() = 0;
}

Но я не мог понять, как это сделать в Python. Это не работает:

class AppLicense(object):
  def GetApplicationStatus(self):
    pass

Поэтому вместо этого я экспортировал другую функцию в dll следующим образом:

extern "C" {
    int P_GetApplicationStatus(void *ptr) {
        try {
            AppLicenseImpl * ref = reinterpret_cast<AppLicenseImpl *>(ptr);
            return ref->GetApplicationStatus();
        } catch (...) {
            return 0; // License Error default.
        }
    }
}

Как только я это сделал, использование его из Python выполняется следующим образом:

d.GetLicense.restype = ctypes.c_void_p
p = d.GetLicense()

d.C_GetApplicationStatus.argtypes = [ctypes.c_void_p]
status = d.P_GetApplicationStatus(p)
person g.d.d.c    schedule 17.10.2013