使用 python 分配内存以传递给 dll

Allocation of Memory with python to pass to dll

我得到一个 dll,它需要一个指向 C 类型字节数组的内存指针。 dll 将读取和修改数组,并将一些额外的数据放在数组的末尾。

如何在python中分配1MB内存作为C类型字节数组并获取指针?

如何将 python 中的 C 类型字节数组写入此指针?

您可能想知道为什么我要这样做,但不幸的是这是此 dll 的唯一接口:/ 我必须在 python.

中这样做

这是我当前的设置:

import ctypes

# Allocate Memory:
BUFFER_LENGTH = 20

# parse to pointer:
bytes_buffer = bytes([0x13, 0x02, 0x03, 0x04, 0x08, 0xA5]) # dummy data

size_in = len(bytes_buffer)
print(bytes_buffer)
# write binary data to memory in CTypes Byte Array
buffer_in = ctypes.cast(bytes_buffer, ctypes.POINTER(ctypes.c_char*BUFFER_LENGTH) )
adr = ctypes.pointer(buffer_in)
address = id(adr)

# get pointer as int32
pointer_data_hi = ctypes.c_uint32(address) 
pointer_data_lo = ctypes.c_uint32(address >> 32) 
print("in: hi: " + str(pointer_data_hi.value) + ", lo: " + str(pointer_data_lo.value) + ", size: " + str(size_in))

# Load dll
array_modifier = ctypes.windll.LoadLibrary("PythonArrayToDll/modify_array_example/x64/Debug/modify_array_example.dll")

# set pointer of array to dll memory:
array_modifier.setAddrLo(pointer_data_lo)
array_modifier.setAddrHi(pointer_data_hi)

# tell the dll to compute something from the data array:
array_modifier.modifyArray() # this is where it crashes with exception: access violation reading 0xFFFFFFFFFFFFFFFF


# display the results:
for i in range(BUFFER_LENGTH):
    print(buffer_in[i].value)

dll 代码(示例):

#include <WinDef.h>
#include "pch.h"
#include "pointer_fnc.h"

#define DLL_EXPORT __declspec(dllexport)

int addrHi;
int addrLo;

extern "C"
{

    DLL_EXPORT void setAddrLo(int lo)
    {
        addrLo = lo;
    }

    DLL_EXPORT void setAddrHi(int hi)
    {
        addrHi = hi;
    }

    DLL_EXPORT void modifyArray()
    {
        BYTE* my_array = (BYTE*)decode_integer_to_pointer(addrHi, addrLo);

        my_array[0] = my_array[0] * 2;
        my_array[1] = 2;
        my_array[10] = my_array[0];
    }
}

pointer_fnc.cpp 提供:

void* decode_integer_to_pointer(int hi, int lo)
{
#if PTRDIFF_MAX == INT64_MAX
    union addrconv {
        struct {
            int lo;
            int hi;
        } base;
        unsigned long long address;
    } myaddr;
    myaddr.base.lo = lo;
    myaddr.base.hi = hi;
    return reinterpret_cast<void*>(myaddr.address);
#elif PTRDIFF_MAX == INT32_MAX
    return reinterpret_cast<void*>(lo);
#else
#error "Cannot determine 32bit or 64bit environment!"
#endif
}

dll被编译为64位,使用了64位python。

希望你能帮助我:)

清单[Python.Docs]: ctypes - A foreign function library for Python.

问题

  • 我粗略地浏览了 Functional Mock-up Interface ,但我没有看到任何对 API 的引用。无论如何,这个 hi, lo 方法很差(看起来它来自 16bit (段,偏移量)时代)。指针存在了几十年,而这正是它们的目的:处理内存地址

    • 没有大小限制(从(起始)地址“可以走多远”),这意味着 API 消费者最终可能会访问内存他们不拥有(Undefined Behavior - 很可能崩溃)

    以上让我觉得对 API

    存在(很大)误解
  • 不要混用PythonCTypes对象地址,它们是不一样的! [Python.Docs]: Built-in Functions - id(object) returns Python 包装器对象 (PyObject) 地址,而不是实际的指针(你关心)

  • 一个很常见的(缺少函数argtypesrestype):,但它是不影响当前场景

  • 既然你在Win,看看[MS.Docs]: ULARGE_INTEGER union (winnt.h),不用重新发明轮子。无论如何,我删除了它以尽可能独立于平台

  • unsigned long long 中组合 2 ints 可能会对负值(符号hi

    的位集)
  • 更多次要的(不值得单独提及)

我准备了一个小例子

dll00.c:

#include <stdint.h>
#include <stdio.h>


#if defined(_WIN32)
#  define DLL00_EXPORT_API __declspec(dllexport)
#else
#  define DLL00_EXPORT_API
#endif

#define BYTE unsigned char


static uint32_t addrLo = 0;
static uint32_t addrHi = 0;


#if defined(__cplusplus)
extern "C" {
#endif

DLL00_EXPORT_API void setAddrLo(int lo);
DLL00_EXPORT_API void setAddrHi(int hi);
DLL00_EXPORT_API void modifyArray();

#if defined(__cplusplus)
}
#endif


void setAddrLo(int lo) {
    addrLo = (uint32_t)lo;
}

void setAddrHi(int hi) {
    addrHi = (uint32_t)hi;
}

static BYTE* toPtr(uint32_t hi, uint32_t lo) {
#if SIZE_MAX == 0xffffffffffffffffull
    uint64_t quad = ((uint64_t)hi << 32) + lo;
    printf("C  - Addr: 0x%016llX, Hi: 0x%08X, Lo: 0x%08X\n", quad, hi, lo);
    return (BYTE*)quad;
#elif SIZE_MAX == 0xfffffffful
    printf("C  - Addr: 0x%016llX, Hi: 0x%08X, Lo: 0x%08X\n", lo, hi, lo);
    return (BYRE*)lo;
#else
#  error "Neither 64bit nor 32bit architecture"
#endif
}

void modifyArray() {  // A 'size' argument would be make sense to specify maximum array index.
    BYTE *addr = toPtr(addrHi, addrLo);
    if (addr == NULL) {
        printf("C  - NULL pointer!\n");
        return;
    }
    addr[0] *= 2;
    addr[1] = 2;
    addr[10] = addr[0];
}

code00.py:

#!/usr/bin/env python

import sys
import ctypes as ct


DLL_NAME = "./dll00.{:s}".format("dll" if sys.platform[:3].lower() == "win" else "so")
BUF_LEN = 20


def main(*argv):
    dll = ct.CDLL(DLL_NAME)
    set_addr_lo = dll.setAddrLo
    set_addr_lo.argtypes = (ct.c_int,)
    set_addr_lo.restype = None
    set_addr_hi = dll.setAddrHi
    set_addr_hi.argtypes = (ct.c_int,)
    set_addr_hi.restype = None
    modify_array = dll.modifyArray
    modify_array.argtypes = ()
    modify_array.restype = None

    b = b"\x20\x03\x20"
    Array = ct.c_char * BUF_LEN
    buf = Array(*b)
    print("PY - Array: {:}".format(list(ord(i) for i in buf)))
    addr = ct.addressof(buf)  # The reverse of what's done in the .dll (toPtr) - doesn't make much sense
    if sys.maxsize > 0x100000000:
        lo = addr & 0xffffffff
        hi = (addr >> 32) & 0xffffffff
    else:
        hi = 0
        lo = addr
    print("PY - Addr: 0x{:016X}, Hi: 0x{:08X}, Lo: 0x{:08X}".format(addr, hi, lo))
    set_addr_lo(lo)
    set_addr_hi(hi)
    modify_array()
    print("PY - Array: {:}".format(list(ord(i) for i in buf)))


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.")
    sys.exit(rc)

输出:

[cfati@CFATI-5510-0:e:\Work\Dev\Whosebug\q068304564]> sopr.bat
### Set shorter prompt to better fit when pasted in Whosebug (or other) pages ###

[prompt]> "c:\Install\pc032\Microsoft\VisualStudioCommunity19\VC\Auxiliary\Build\vcvarsall.bat" x64 >nul

[prompt]> dir /b
code00.py
dll00.c

[prompt]>
[prompt]> cl /nologo /MD /DDLL dll00.c  /link /NOLOGO /DLL /OUT:dll00.dll
dll00.c
   Creating library dll00.lib and object dll00.exp

[prompt]> dir /b
code00.py
dll00.c
dll00.dll
dll00.exp
dll00.lib
dll00.obj

[prompt]>
[prompt]> "e:\Work\Dev\VEnvs\py_pc064_03.08.07_test0\Scripts\python.exe" code00.py
Python 3.8.7 (tags/v3.8.7:6503f05, Dec 21 2020, 17:59:51) [MSC v.1928 64 bit (AMD64)] 064bit on win32

PY - Array: [32, 3, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
PY - Addr: 0x00000203275B23B0, Hi: 0x00000203, Lo: 0x275B23B0
C  - Addr: 0x00000203275B23B0, Hi: 0x00000203, Lo: 0x275B23B0
PY - Array: [64, 2, 32, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0]

Done.