Я пытаюсь взаимодействовать между Python и C++.
Это мой код C++ для тестового метода DLL:
extern "C" __declspec(dllexport) PEParserNamespace::PEParserBase& _cdecl test(PEParserNamespace::PEParserBase* base) {
printf("the C++ function was called\n");
base->bytes = 12345;
return *base;
}
Я пытаюсь использовать его из Python следующим образом:
import ctypes
#DataStructures.py
class PEParserBase(ctypes.Structure):
_fields_ = [("hFile", ctypes.c_void_p),
("dwFileSize", ctypes.c_ulong),
("bytes", ctypes.c_ulong),
("fileBuffer",ctypes.c_void_p)]
class PEHEADER(ctypes.Structure):
xc = 0
#FunctionWrapper.py
def testWrapper(peParserBase, _instanceDLL):
_instanceDLL.test.argtypes = [ctypes.POINTER(PEParserBase)]
_instanceDLL.test.restype = PEParserBase
return _instanceDLL.test(ctypes.byref(pEParserBase))
pEParserBase = PEParserBase()
print("hallo welt")
_test = ctypes.CDLL('PeParserPythonWrapper.dll')
print(id(testWrapper(pEParserBase, _test)))
print(id(pEParserBase))
Я ожидал, что testWrapper
вернет исходный экземпляр PEParserBase
, но это не так — сообщаемые значения id
отличаются. Код C++ не создает никаких новых экземпляров PEParserBase
или чего-то еще, поэтому я уверен, что проблема должна быть в коде Python.
Почему это происходит и как это исправить?
Добро пожаловать в Stack Overflow. Ничего страшного, если ваш английский не является родным, но, пожалуйста, все же старайтесь писать о проблеме, а не о себе — и старайтесь задавать четкий вопрос, прямо и без разговора. Я отредактировал пост, чтобы исправить текст и соответствовать стандартам сайта. Для получения дополнительной информации, пожалуйста, прочитайте Как задать вопрос и Следует ли удалить слова «Привет», «Спасибо», слоганы и приветствия из сообщений?.
@MichaelButscher, кажется, стоит написать в качестве ответа (включая объяснение, почему он не работает как есть; я предполагаю, что существует какая-то неявная копия, связанная с пересечением границы DLL или что-то в этом роде?), если есть нет применимого дубликата (определенно не моя область знаний, поэтому я не могу легко найти его).
Листинг [Python.Docs]: ctypes — внешняя библиотека функций для Python.
У вас есть Undefined Behavior (на самом деле их куча):
CTypes (как следует из названия) работает с C. Ссылка — это специфичная для C++ концепция (C ничего о ней не знает).
Из-за того, что ссылка на самом деле является адресом памяти (точно так же, как указатель, но с некоторыми ключевыми отличиями), ваш прототип функции Python (retype) неверен. Проверьте [SO]: функция C, вызванная из Python через ctypes, возвращает неверное значение (@ответ CristiFati) для получения более подробной информации.
Примечание: указание указателя вместо этого помогло бы, но технически это все еще неверно (если кто-то хочет быть строгим)
Объекты CTypes — это оболочки Python ([Python.Docs]: Common Object Structures — type PyObject) над фактическими объектами C.
[Python.Docs]: встроенные функции — id(object) возвращает адрес объекта-оболочки Python (который отличается от адреса обернутого объекта).
Вместо этого вы должны использовать ctypes.addressof.
Я подготовил небольшой пример (я определил структуру C на основе Python и имен членов - также сделал вывод, что вы находитесь на Win (но все идеи в ответе не зависят от ОС)).
dl00.cpp:
#include <stdio.h>
#if defined(_WIN32)
# include <Windows.h>
# define DLL00_EXPORT_API __declspec(dllexport)
#else
# define DLL00_EXPORT_API
#endif
struct PEParserBase {
HANDLE hFile;
DWORD dwFileSize;
ULONG bytes;
LPVOID fileBuffer;
};
typedef PEParserBase *PPEParserBase;
#if defined(__cplusplus)
extern "C" {
#endif
DLL00_EXPORT_API PPEParserBase func00ptr(PPEParserBase parg);
#if defined(__cplusplus)
}
#endif
PPEParserBase func00ptr(PPEParserBase parg)
{
printf(" C:\n Address: 0x%0zX\n", reinterpret_cast<size_t>(parg));
if (parg) {
printf(" dwFileSize: %u\n bytes: %u\n", parg->dwFileSize, parg->bytes);
parg->dwFileSize = 123;
}
return parg;
}
code00.py:
#!/usr/bin/env python
import ctypes as cts
import ctypes.wintypes as wts
import sys
DLL_NAME = "./dll00.{:s}".format("dll" if sys.platform[:3].lower() == "win" else "so")
class PEParserBase(cts.Structure):
_fields_ = (
("hFile", wts.HANDLE),
("dwFileSize", wts.DWORD),
("bytes", wts.ULONG),
("fileBuffer", wts.LPVOID),
)
def __str__(self):
ret = [self.__repr__()]
for field, _ in self._fields_:
ret.append(" {:s}: {:}".format(field, getattr(self, field)))
return "\n".join(ret)
PEParserBasePtr = cts.POINTER(PEParserBase)
def print_ctypes_obj(obj):
_id = id(obj)
try:
_addrof = cts.addressof(obj)
except TypeError:
_addrof = 0
print("Id: 0x{:016X}\nAddressOf: 0x{:016X}\n{:s}\n".format(_id, _addrof, str(obj)))
def main(*argv):
dll = cts.CDLL(DLL_NAME)
func00ptr = dll.func00ptr
func00ptr.argtypes = (PEParserBasePtr,)
func00ptr.restype = PEParserBasePtr
use_ptr = 1
if use_ptr:
print("Test pointer export\n")
pb0 = PEParserBase(None, 3141593, 2718282, None)
print_ctypes_obj(pb0)
ppb0 = cts.byref(pb0)
print_ctypes_obj(ppb0)
ppb1 = func00ptr(ppb0)
print()
print_ctypes_obj(ppb1)
pb1 = ppb1.contents
print_ctypes_obj(pb1)
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)
Выход:
[cfati@CFATI-5510-0:e:\Work\Dev\StackExchange\StackOverflow\q075885080]> sopr.bat ### Set shorter prompt to better fit when pasted in StackOverflow (or other) pages ### [prompt]> [prompt]> "c:\Install\pc032\Microsoft\VisualStudioCommunity\2019\VC\Auxiliary\Build\vcvarsall.bat" x64 > nul [prompt]> cl /nologo /MD /DDLL dll00.cpp /link /NOLOGO /DLL /OUT:dll00.dll dll00.cpp Creating library dll00.lib and object dll00.exp [prompt]> [prompt]> "e:\Work\Dev\VEnvs\py_pc064_03.10_test0\Scripts\python.exe" ./code00.py Python 3.10.9 (tags/v3.10.9:1dd9be6, Dec 6 2022, 20:01:21) [MSC v.1934 64 bit (AMD64)] 064bit on win32 Test pointer export Id: 0x0000013F2CB60740 AddressOf: 0x0000013F2C30FE30 <__main__.PEParserBase object at 0x0000013F2CB60740> hFile: None dwFileSize: 3141593 bytes: 2718282 fileBuffer: None Id: 0x0000013F2CBA72B0 AddressOf: 0x0000000000000000 <cparam 'P' (0x0000013F2C30FE30)> C: Address: 0x13F2C30FE30 dwFileSize: 3141593 bytes: 2718282 Id: 0x0000013F2CB607C0 AddressOf: 0x0000013F2CB60808 <__main__.LP_PEParserBase object at 0x0000013F2CB607C0> Id: 0x0000013F2CB60AC0 AddressOf: 0x0000013F2C30FE30 <__main__.PEParserBase object at 0x0000013F2CB60AC0> hFile: None dwFileSize: 123 bytes: 2718282 fileBuffer: None Done.
id()
в CPython возвращает адрес объекта-оболочки Python ctypes
, а не адрес обернутого объекта. Для этого используйте ctypes.addressof()
.
ctypes
также понимает только типы и структуры простых старых данных C (POD). Он не знает пространств имен или ссылок C++, но поскольку ссылки на самом деле являются синтаксическим сахаром C++ для указателей, вы можете использовать указатель в .argtypes
и .restype
в качестве замены.
Вот минимальный пример:
test.cpp
#include <stdio.h>
namespace PEParserNamespace {
struct PEParserBase {
void* hFile;
unsigned long dwFileSize;
unsigned long bytes;
void* fileBuffer;
};
}
extern "C" __declspec(dllexport)
PEParserNamespace::PEParserBase& _cdecl test(PEParserNamespace::PEParserBase* base) {
printf("the C++ function was called\n");
base->bytes = 12345;
return *base;
}
test.py
import ctypes as ct
class PEParserBase(ct.Structure):
_fields_ = (("hFile", ct.c_void_p),
("dwFileSize", ct.c_ulong),
("bytes", ct.c_ulong),
("fileBuffer",ct.c_void_p))
dll = ct.CDLL('./test')
dll.test.argtypes = ct.POINTER(PEParserBase),
dll.test.restype = ct.POINTER(PEParserBase) # Use a pointer here for the reference
base = PEParserBase()
pbase = dll.test(ct.byref(base))
print(hex(ct.addressof(base)), base.bytes)
print(hex(ct.addressof(pbase.contents)), base.bytes) # .contents dereferences the pointer
# so we get the address of the structure
# not the address of the pointer itself.
Выход:
the C++ function was called, base=00000169712ADAF0
0x169712adaf0 12345
0x169712adaf0 12345
Вы можете заменить
id
наctypes.adressof
для сравнения. Если вам действительно нужна здесь идентификация, вы должны сохранить словарь, сопоставляющий адрес с объектом Python, и написать функции для управления этим словарем.