Python ctypes, char** и DLL

У меня есть функция dll в С++:

void get_DLLVersion(CAxClass* obj, char ** pVal);

В pVal get_DLLVersion напишите строку c, например «1.0.0.1».

В С++ это так:

char *strdll = (char*)malloc(50);
    
    get_DLLVersion(tst, &strdll);

    cout << "strdll = "<<strdll<<endl;

Мне нужно использовать эту функцию в python.

Основная проблема заключается в том, как создать char** и поставить в качестве второго аргумента функции dll.

Я использую следующий код:

import ctypes

libc = ctypes.CDLL("AxECR.so")
ecr = libc.create_object() #return CAxClass* obj
print (libc.get_DLLVersion) 
libc.get_DLLVersion.argtypes = [c_void_p, ctypes.POINTER(ctypes.POINTER(c_char))]
dll = ctypes.POINTER(ctypes.POINTER(c_char))
libc.get_DLLVersion(ecr,dll) #don`t work Segmentation fault (core dumped)

(1) Объясните «не работает». (2) Есть ли документация о get_DLLVersion, которая описывает, какими должны быть параметры?

Michael Butscher 14.02.2023 11:50

@AlanBirtles Точно не зная проблемы OP, вы не можете знать, решает ли ее упомянутый вопрос.

Michael Butscher 14.02.2023 11:54

@MichaelButscher вполне разумно предположить, что функция с именем get_DLLVersion должна возвращать строку, поэтому pVal должна быть указателем на массив char

Alan Birtles 14.02.2023 12:00

@AlanBirtles OP необходимо создать экземпляр указателя, предполагая, что это выходной параметр, и передать byref, а также установить .restype из create_object. Предложенный дубликат недостаточен

Mark Tolonen 14.02.2023 12:03

@AlanBirtles В дополнение к комментарию Марка есть библиотеки, которые могут, например. не предоставлять строку с завершающим нулем через pVal, а вместо этого строку фиксированной длины.

Michael Butscher 14.02.2023 12:10

@Марк Толонен, каким .restype из create_objec должно быть? Я добавляю пример с кодом C++, возможно, это поможет понять, как работает get_DLLVersion.

BRAiNPet 14.02.2023 12:41

@BRAiNPet странно, что параметр является char**, поскольку, если пользователь выделил память, достаточно char*. Вы уверены, что strdll сохраняет свое значение после звонка?

Mark Tolonen 14.02.2023 19:11

@BRAiNPet Я бы объявил class CAxClass(ctypes.c_void_p): pass непрозрачным указателем на класс и использовал .restype = ctypes.POINTER(CAxClass) в качестве возвращаемого типа.

Mark Tolonen 14.02.2023 20:21
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
1
8
68
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Листинг [Python.Docs]: ctypes — внешняя библиотека функций для Python.

Примечания:

  • Чтобы исправить это, можно создать буфер (массив) через create_string_buffer, а затем передать его адрес (через byref) в функцию

    • Требуется явное приведение (от массива char к указателю char)
  • Для аргумента 1st я создаю одноэлементный объект CAxClass, который возвращается при каждом вызове createObject. У меня также может быть функция создания нового экземпляра, но тогда потребуется еще одна, чтобы уничтожить его, чтобы предотвратить утечку памяти (1)

  • Глядя на то, как функция вызывается из C++, она просто заполняет память по адресу, указанному в качестве аргумента (если не NULL, надеюсь). В этом случае использование двойного указателя не имеет особого смысла, так как той же цели можно достичь с помощью простого указателя (я добавил еще одну функцию в приведенном ниже примере, чтобы доказать это).

Пример:

  • dll00.cpp:

    #include <cstring>
    #include <iostream>
    
    #if defined(_WIN32)
    #  define DLL00_EXPORT_API __declspec(dllexport)
    #else
    #  define DLL00_EXPORT_API
    #endif
    
    #define BUF_LEN 50
    
    
    class CAxClass {};
    
    static CAxClass *gObj = new CAxClass();  // nullptr;
    
    
    #if defined(__cplusplus)
    extern "C" {
    #endif
    
    DLL00_EXPORT_API void* createObject();
    DLL00_EXPORT_API void dllVersion(CAxClass *pObj, char **ppVer);
    DLL00_EXPORT_API void dllVersionSinglePtr(CAxClass *pObj, char *pVer);
    
    #if defined(__cplusplus)
    }
    #endif
    
    
    void* createObject() {
        return gObj;
    }
    
    
    void dllVersion(CAxClass *pObj, char **ppVer)
    {
        if ((ppVer) && (*ppVer)) {
            strncpy(*ppVer, "1.22.333.4444", BUF_LEN);
        } else {
            std::cout << "C - NULL pointer\n";
        }
    }
    
    
    void dllVersionSinglePtr(CAxClass *pObj, char *pVer)
    {
        if (pVer) {
            strncpy(pVer, "55555.666666.7777777.88888888", BUF_LEN);
        } else {
            std::cout << "C - NULL pointer\n";
        }
    }
    
  • code00.py:

    #!/usr/bin/env python
    
    import ctypes as cts
    import sys
    
    
    CharPtr = cts.c_char_p  # More generic: cts.POINTER(cts.c_char) ?
    CharPtrPtr = cts.POINTER(CharPtr)
    
    BUF_LEN = 50
    
    DLL_NAME = "./dll00.{:s}".format("dll" if sys.platform[:3].lower() == "win" else "so")
    
    
    def main(*argv):
        dll = cts.CDLL(DLL_NAME)
    
        createObject = dll.createObject
        createObject.argtypes = ()
        createObject.restype = cts.c_void_p
    
        dllVersion = dll.dllVersion
        dllVersion.argtypes = (cts.c_void_p, CharPtrPtr)
        dllVersion.restype = None
    
        # @TODO - cfati: Testing purposes
        dllVersionSinglePtr = dll.dllVersionSinglePtr
        dllVersionSinglePtr.argtypes = (cts.c_void_p, CharPtr)
        dllVersionSinglePtr.restype = None
    
        obj = createObject()
        print("Object: {:}".format(obj))
    
        buf = cts.create_string_buffer(BUF_LEN)
        dllVersion(obj, cts.byref(cts.cast(buf, CharPtr)))
        print("Version: {:}".format(buf.value))
    
        dllVersionSinglePtr(obj, cts.cast(buf, CharPtr))
        print("Version: {:}".format(buf.value))
    
    
    if __name__ == "__main__":
        print("Python {:s} {:03d}bit on {:s}\n".format(" ".join(elem.strip() for elem in sys.version.split("\n")),
                                                       64 if sys.maxsize > 0x100000000 else 32, sys.platform))
        rc = main(*sys.argv[1:])
        print("\nDone.\n")
        sys.exit(rc)
    

выход:

(qaic-env) [cfati@cfati-5510-0:/mnt/e/Work/Dev/StackOverflow/q075446745]> ~/sopr.sh
### Set shorter prompt to better fit when pasted in StackOverflow (or other) pages ###

[064bit prompt]> ls
code00.py  dll00.cpp
[064bit prompt]>
[064bit prompt]> g++ -fPIC -shared -o dll00.so dll00.cpp
[064bit prompt]>
[064bit prompt]> ls
code00.py  dll00.cpp  dll00.so
[064bit prompt]>
[064bit prompt]> python ./code00.py
Python 3.8.10 (default, Nov 14 2022, 12:59:47) [GCC 9.4.0] 064bit on linux

Object: 34716928
Version: b'1.22.333.4444'
Version: b'55555.666666.7777777.88888888'

Done.

Также можно проверить:

Другие вопросы по теме