У меня есть функция 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)
@AlanBirtles Точно не зная проблемы OP, вы не можете знать, решает ли ее упомянутый вопрос.
@MichaelButscher вполне разумно предположить, что функция с именем get_DLLVersion
должна возвращать строку, поэтому pVal
должна быть указателем на массив char
@AlanBirtles OP необходимо создать экземпляр указателя, предполагая, что это выходной параметр, и передать byref
, а также установить .restype
из create_object
. Предложенный дубликат недостаточен
@AlanBirtles В дополнение к комментарию Марка есть библиотеки, которые могут, например. не предоставлять строку с завершающим нулем через pVal
, а вместо этого строку фиксированной длины.
@Марк Толонен, каким .restype
из create_objec
должно быть? Я добавляю пример с кодом C++, возможно, это поможет понять, как работает get_DLLVersion.
@BRAiNPet странно, что параметр является char**
, поскольку, если пользователь выделил память, достаточно char*
. Вы уверены, что strdll
сохраняет свое значение после звонка?
@BRAiNPet Я бы объявил class CAxClass(ctypes.c_void_p): pass
непрозрачным указателем на класс и использовал .restype = ctypes.POINTER(CAxClass)
в качестве возвращаемого типа.
Листинг [Python.Docs]: ctypes — внешняя библиотека функций для Python.
Примечания:
Чтобы исправить это, можно создать буфер (массив) через create_string_buffer, а затем передать его адрес (через byref) в функцию
Для аргумента 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.
Также можно проверить:
[SO]: C++ и Python: передать и вернуть массив 2D-двойных указателей из python в c++ (@ответ CristiFati) для использования двойного указателя при передаче 2D-массива
[ТАК]: функция C, вызванная из Python через ctypes, возвращает неверное значение (@ответ CristiFati) из-за распространенной ошибки при работе с CTypes (вызов функций)
[SO]: Python ctypes cdll.LoadLibrary, создайте экземпляр объекта, выполните его метод, адрес частной переменной усекается (@ответ CristiFati) для примера сноски № 1
(1) Объясните «не работает». (2) Есть ли документация о
get_DLLVersion
, которая описывает, какими должны быть параметры?