ctypes如何获取NULL c_void_p字段的地址?

时间:2018-11-29 04:21:40

标签: ctypes

我需要获取NULL无效指针的地址。如果我在Python中使用NULL c_void_p,则获取其地址没有问题:

ptr = c_void_p(None)
print(ptr)
print(ptr.value)
print(addressof(ptr))

给予

c_void_p(None)
None
4676189120

但是我有一个

class Effect(structure):
    _fields_ = [("ptr", c_void_p)]

其中ptr在C中被初始化为NULL。当我在python中访问它时

myclib.get_effect.restype = POINTER(Effect)
effect = myclib.get_effect().contents
print(effect.ptr)

给出了None,所以我不能接受addressof(effect.ptr)

如果我将字段类型更改为指向任何ctype类型的指针

class Effect(structure):
    _fields_ = [("ptr", POINTER(c_double)]

# get effect instance from C shared library
print(addressof(effect.ptr))

我检查了C端堆上的地址是否正确

140530973811664

不幸的是,不能从c_void_p更改字段类型。我该怎么办?

说明

这是@CristiFati之后的C代码,适合我的具体情况。 struct是在C中分配的,我在Python中得到了ptr的信息,现在我需要在struct中传递对ptr的引用。首先,如果我将ptr加倍,那就没问题!

#include <stdio.h>
#include <stdlib.h>

#define PRINT_MSG_2SX(ARG0, ARG1) printf("From C - [%s] (%d) - [%s]:  ARG0: [%s], ARG1: 0x%016llX\n", __FILE__, __LINE__, __FUNCTION__, ARG0, (unsigned long long)ARG1)

typedef struct Effect {
    double* ptr;
} Effect;

void print_ptraddress(double** ptraddress){
    PRINT_MSG_2SX("Address of Pointer:", ptraddress);
}

Effect* get_effect(){
    Effect* pEffect = malloc(sizeof(*pEffect));
    pEffect->ptr = NULL;
    print_ptraddress(&pEffect->ptr);
    return pEffect;
}

在Python中

from ctypes import cdll, Structure, c_int, c_void_p, addressof, pointer, POINTER, c_double, byref
clibptr = cdll.LoadLibrary("libpointers.so")

class Effect(Structure):
    _fields_ = [("ptr", POINTER(c_double))]

clibptr.get_effect.restype = POINTER(Effect)
pEffect = clibptr.get_effect()

effect = pEffect.contents
clibptr.print_ptraddress(byref(effect.ptr))

提供匹配的地址:

从C-[pointers.c](11)-[print_ptraddress]:ARG0:[指针地址:],ARG1:0x00007FC2E1AD3770 从C-[pointers.c](11)-[print_ptraddress]:ARG0:[指针的地址:],ARG1:0x00007FC2E1AD3770

但是如果我将double *更改为v​​oid *和c_void_p,则会收到错误消息,因为python中的c_void_p设置为None

2 个答案:

答案 0 :(得分:1)

ctypes [Python 3]: ctypes - A foreign function library for Python)旨在能够从 Python 与“ C ”对话,从而使其< em> Python 友好,这意味着没有指针,内存地址……任何东西(至少,尽可能精确些)。
因此,在幕后,它起到了一些“魔术作用”,在这种情况下,它位于您与目标之间。

@ EDIT0 :更新了答案以更好地解决(已阐明的)问题。

示例:

>>> import ctypes
>>> s0 = ctypes.c_char_p(b"Some dummy text")
>>> s0, type(s0)
(c_char_p(2180506798080), <class 'ctypes.c_char_p'>)
>>> s0.value, "0x{:016X}".format(ctypes.addressof(s0))
(b'Some dummy text', '0x000001FBB021CF90')
>>>
>>> class Stru0(ctypes.Structure):
...     _fields_ = [("s", ctypes.c_char_p)]
...
>>> stru0 = Stru0(s0)
>>> type(stru0)
<class '__main__.Stru0'>
>>> "0x{:016X}".format(ctypes.addressof(stru0))
'0x000001FBB050E310'
>>> stru0.s, type(stru0.s)
(b'Dummy text', <class 'bytes'>)
>>>
>>>
>>> b = b"Other dummy text"
>>> char_p = ctypes.POINTER(ctypes.c_char)
>>> s1 = ctypes.cast((ctypes.c_char * len(b))(*b), char_p)
>>> s1, type(s1)
(<ctypes.LP_c_char object at 0x000001FBB050E348>, <class 'ctypes.LP_c_char'>)
>>> s1.contents, "0x{:016X}".format(ctypes.addressof(s1))
(c_char(b'O'), '0x000001FBB050E390')
>>>
>>> class Stru1(ctypes.Structure):
...     _fields_ = [("s", ctypes.POINTER(ctypes.c_char))]
...
>>> stru1 = Stru1(s1)
>>> type(stru1)
<class '__main__.Stru1'>
>>> "0x{:016X}".format(ctypes.addressof(stru1))
'0x000001FBB050E810'
>>> stru1.s, type(stru1.s)
(<ctypes.LP_c_char object at 0x000001FBB050E6C8>, <class 'ctypes.LP_c_char'>)
>>> "0x{:016X}".format(ctypes.addressof(stru1.s))
'0x000001FBB050E810'

这是两种在理论上是相同的类型之间的平行点:

  1. ctypes.c_char_p:如您所见, s0 已自动转换为 bytes 。这是有道理的,因为它是 Python ,这里不需要使用指针。每次使用它时,都必须将每个成员从 ctypes 转换为普通的 Python (反之亦然),这也很烦人。
    当前场景不是“快乐流程”的一部分,它只是一个极端情况,没有任何功能(或者至少我不知道有什么功能)

  2. ctypes.POINTER(ctypes.c_char)(命名为 char_p ):它更接近 C ,并提供了您需要的功能,但是看起来还很多(从 Python 角度来看)更难使用

问题在于ctypes.c_void_p #1。 类似,因此您没有想要的 OOTB 功能,而且没有ctypes.c_void #2。 一起使用。但是,可以做到这一点,但是还需要其他工作。

众所周知的( C )规则是:
AddressOf(Structure.Member)= AddressOf(Structure)+ OffsetOf(Structure,Member) ((提防注意内存对齐,他们可以“玩弄脏记”)。

在这种情况下,事情再简单不过了。这是一个示例:

dll.c

#include <stdio.h>
#include <stdlib.h>

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

#define PRINT_MSG_2SX(ARG0, ARG1) printf("From C - [%s] (%d) - [%s]:  ARG0: [%s], ARG1: 0x%016llX\n", __FILE__, __LINE__, __FUNCTION__, ARG0, (unsigned long long)ARG1)


static float f = 1.618033;

typedef struct Effect {
    void *ptr;
} Effect;


DLL_EXPORT void test(Effect *pEffect, int null) {
    PRINT_MSG_2SX("pEffect", pEffect);
    PRINT_MSG_2SX("pEffect->ptr", pEffect->ptr);
    PRINT_MSG_2SX("&pEffect->ptr", &pEffect->ptr);
    pEffect->ptr = !null ? NULL : &f;
    PRINT_MSG_2SX("new pEffect->ptr", pEffect->ptr);
}

code.py

#!/usr/bin/env python3

import sys
from ctypes import CDLL, POINTER, \
    Structure, \
    c_int, c_void_p, \
    addressof, pointer


DLL = "./dll.dll"


class Effect(Structure):
    _fields_ = [("ptr", c_void_p)]


def hex64_str(item):
    return "0x{:016X}".format(item)


def print_addr(ctypes_inst, inst_name, heading=""):
    print("{:s}{:s} addr: {:s} (type: {:})".format(heading, "{:s}".format(inst_name) if inst_name else "", hex64_str(addressof(ctypes_inst)), type(ctypes_inst)))


def main():
    dll_dll = CDLL(DLL)
    test_func = dll_dll.test
    test_func.argtypes = [POINTER(Effect), c_int]

    effect = Effect()
    print_addr(effect, "effect")
    test_func(pointer(effect), 1)
    print(effect.ptr, type(effect.ptr))  # Not helping, it's Python int for c_void_p
    try:
        print_addr(effect.ptr, "effect.ptr")
    except:
        print("effect.ptr: - wrong type")
    print_addr(effect, "effect", "\nSecond time...\n    ")
    print("Python addrs (irrelevant): effect: {:s}, effect.ptr: {:s}".format(hex64_str(id(effect)), hex64_str(id(effect.ptr))))


if __name__ == "__main__":
    print("Python {:s} on {:s}\n".format(sys.version, sys.platform))
    main()

输出

(py35x64_test) e:\Work\Dev\StackOverflow\q053531795>call "c:\Install\x86\Microsoft\Visual Studio Community\2015\vc\vcvarsall.bat" x64

(py35x64_test) e:\Work\Dev\StackOverflow\q053531795>dir /b
code.py
dll.c

(py35x64_test) e:\Work\Dev\StackOverflow\q053531795>cl /nologo /DDLL /MD dll.c  /link /NOLOGO /DLL /OUT:dll.dll
dll.c
   Creating library dll.lib and object dll.exp

(py35x64_test) e:\Work\Dev\StackOverflow\q053531795>dir /b
code.py
dll.c
dll.dll
dll.exp
dll.lib
dll.obj

(py35x64_test) e:\Work\Dev\StackOverflow\q053531795>"e:\Work\Dev\VEnvs\py35x64_test\Scripts\python.exe" code.py
Python 3.5.4 (v3.5.4:3f56838, Aug  8 2017, 02:17:05) [MSC v.1900 64 bit (AMD64)] on win32

effect addr: 0x000001FB25B8CB10 (type: <class '__main__.Effect'>)
From C - [dll.c] (21) - [test]:  ARG0: [pEffect], ARG1: 0x000001FB25B8CB10
From C - [dll.c] (22) - [test]:  ARG0: [pEffect->ptr], ARG1: 0x0000000000000000
From C - [dll.c] (23) - [test]:  ARG0: [&pEffect->ptr], ARG1: 0x000001FB25B8CB10
From C - [dll.c] (25) - [test]:  ARG0: [new pEffect->ptr], ARG1: 0x00007FFFAFB13000
140736141012992 <class 'int'>
effect.ptr: - wrong type

Second time...
    effect addr: 0x000001FB25B8CB10 (type: <class '__main__.Effect'>)
Python addrs (irrelevant): effect: 0x000001FB25B8CAC8, effect.ptr: 0x000001FB25BCC9F0

可以看到, effect 的地址与 effect ptr 的地址相同。但这又是最简单的情况。但是,如所解释的,一般的解决方案是优选的。但是,这不可能,但是可以解决:

  • 使用上面的公式并使用[SO]: Getting elements from ctype structure with introspection?来获取字段偏移量(很长,我很难解决当前的解决方案-尤其是因为有两种容器类型( Structure Array )嵌套的可能性;希望它没有错误(或尽可能接近):))
  • C 接口修改为Effect *get_effect(void **ptr),并将地址存储在参数中
  • 修改( Python Effect 结构,而不是ctypes.c_void_p字段包含涉及 POINTER 的内容(例如:{ {1}})。定义与 C 不同,从语义上讲,它们不是 OK ,但最后它们都是指针

注意:请不要忘记拥有一个破坏 get_effect 返回的指针的函数(以避免内存泄漏

答案 1 :(得分:1)

因此,在python bug跟踪器中提出此问题之后,Martin Panter和Eryk Sun提供了更好的解决方案。

确实存在未记录的offset属性,该属性使我们无需进行任何自省即可访问内存中的正确位置。我们可以使用

找回指针
offset = type(Effect).ptr.offset
ptr = (c_void_p).from_buffer(effect, offset)

通过使用私有字段并添加属性,我们可以更优雅地将其包装到我们的类中:

class Effect(Structure):
    _fields_ = [("j", c_int),
                ("_ptr", c_void_p)]
    @property
    def ptr(self):
        offset = type(self)._ptr.offset
        return (c_void_p).from_buffer(self, offset)

我在指针之前添加了一个整数字段,因此偏移量不只是零。为了完整起见,下面是上面的代码与该解决方案相适应,表明它可以正常工作。在C中:

#include <stdio.h>
#include <stdlib.h>

#define PRINT_MSG_2SX(ARG0, ARG1) printf("%s : 0x%016llX\n", ARG0, (unsigned long long)ARG1)

typedef struct Effect {
    int j;
    void* ptr;
} Effect;

void print_ptraddress(double** ptraddress){
    PRINT_MSG_2SX("Address of Pointer:", ptraddress);
}

Effect* get_effect(){
    Effect* pEffect = malloc(sizeof(*pEffect));
    pEffect->ptr = NULL;
    print_ptraddress(&pEffect->ptr);
    return pEffect;
}

在Python中(忽略上述效果定义):

from ctypes import cdll, Structure, c_int, c_void_p, POINTER, byref
clibptr = cdll.LoadLibrary("libpointers.so")

clibptr.get_effect.restype = POINTER(Effect)
effect = clibptr.get_effect().contents
clibptr.print_ptraddress(byref(effect.ptr))

收益

Address of Pointer: : 0x00007F9EB248FB28
Address of Pointer: : 0x00007F9EB248FB28

再次感谢大家的快速建议。有关更多信息,请参见here