最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

python - Why double `ctypes.POINTER` object works for `char***` while triple `ctypes.POINTER` would make more sense? - Stack Ove

programmeradmin0浏览0评论

I have a library my_lib with a C function that takes a char*** parameter, a pointer to an array of char* that is allocated by the function. Here is a minimal reproducible example of such a function:

void getArrayOfStrings(char*** paramPtr)
{
    (*paramPtr) = (char**) malloc(3*sizeof(char*));
    
    (*paramPtr)[0] = (char*) malloc(strlen("Foo")+1);
    strcpy((*paramPtr)[0], "Foo");
    
    (*paramPtr)[1] = (char*) malloc(strlen("Bar")+1);
    strcpy((*paramPtr)[1], "Bar");
    
    (*paramPtr)[2] = 0;
}

It sets the last array element to 0, so that caller can identify it (rather than providing the size as a second parameter). Note that a separate function is provided to free the memory.

I run ctypesgen to generate a Python binding to this function. It generates this code:

getArrayOfStrings = _lib.get("getArrayOfStrings", "cdecl")
getArrayOfStrings.argtypes = [POINTER(POINTER(POINTER(c_char)))]
getArrayOfStrings.restype = None

This generated binding can be called from the Python script below:

import my_lib

import ctypes
names = ctypes.POINTER(ctypes.POINTER(ctypes.c_char))()

my_lib.getArrayOfStrings(names)

if names:
    for name in names:
        name_str = my_lib.String(name)
        if name_str:
            print("Got name: " + str(name_str))
        else:
            break

It works just fine and prints "Foo\nBar\n"

I'm just wondering why using ctypes.POINTER(ctypes.POINTER(ctypes.c_char)), that I understand as being a "point to pointer to char", so a char**, works. Why I should not be using a ctypes.POINTER(ctypes.POINTER(ctypes.POINTER(ctypes.c_char)))?

I tested with ctypes.POINTER(ctypes.POINTER(ctypes.POINTER(ctypes.c_char))), the same code produces the error:

my_lib.getArrayOfStrings(names)
OSError: exception: access violation writing 0x0000000000000000

I have a library my_lib with a C function that takes a char*** parameter, a pointer to an array of char* that is allocated by the function. Here is a minimal reproducible example of such a function:

void getArrayOfStrings(char*** paramPtr)
{
    (*paramPtr) = (char**) malloc(3*sizeof(char*));
    
    (*paramPtr)[0] = (char*) malloc(strlen("Foo")+1);
    strcpy((*paramPtr)[0], "Foo");
    
    (*paramPtr)[1] = (char*) malloc(strlen("Bar")+1);
    strcpy((*paramPtr)[1], "Bar");
    
    (*paramPtr)[2] = 0;
}

It sets the last array element to 0, so that caller can identify it (rather than providing the size as a second parameter). Note that a separate function is provided to free the memory.

I run ctypesgen to generate a Python binding to this function. It generates this code:

getArrayOfStrings = _lib.get("getArrayOfStrings", "cdecl")
getArrayOfStrings.argtypes = [POINTER(POINTER(POINTER(c_char)))]
getArrayOfStrings.restype = None

This generated binding can be called from the Python script below:

import my_lib

import ctypes
names = ctypes.POINTER(ctypes.POINTER(ctypes.c_char))()

my_lib.getArrayOfStrings(names)

if names:
    for name in names:
        name_str = my_lib.String(name)
        if name_str:
            print("Got name: " + str(name_str))
        else:
            break

It works just fine and prints "Foo\nBar\n"

I'm just wondering why using ctypes.POINTER(ctypes.POINTER(ctypes.c_char)), that I understand as being a "point to pointer to char", so a char**, works. Why I should not be using a ctypes.POINTER(ctypes.POINTER(ctypes.POINTER(ctypes.c_char)))?

I tested with ctypes.POINTER(ctypes.POINTER(ctypes.POINTER(ctypes.c_char))), the same code produces the error:

my_lib.getArrayOfStrings(names)
OSError: exception: access violation writing 0x0000000000000000
Share Improve this question asked Jan 19 at 22:12 jpo38jpo38 21.6k12 gold badges81 silver badges180 bronze badges 0
Add a comment  | 

1 Answer 1

Reset to default 3

Because ctypes knows that the function takes char*** due to .argtypes being defined, passing a char** implies ctypes.byref (take the address of), which passes names as char***. Here's a working example with an explicit byref and using your C code as a DLL:

import ctypes as ct

dll = ct.CDLL('./test')
getArrayOfStrings = dll.getArrayOfStrings
getArrayOfStrings.argtypes = ct.POINTER(ct.POINTER(ct.POINTER(ct.c_char))),
getArrayOfStrings.restype = None

names = ct.POINTER(ct.POINTER(ct.c_char))()
dll.getArrayOfStrings(ct.byref(names))
#dll.getArrayOfStrings(names)  # also works but byref implied here.
i = 0
while names[i]:
    print(ct.string_at(names[i]))
    i += 1

Output:

b'Foo'
b'Bar'

When names = ct.POINTER(ct.POINTER(ct.POINTER(ct.c_char)))() is used, ctypes does not have to add the implied byref and assumes you know what you are doing, but in this case the char*** is null. Since it is being used as an output parameter, C tries to write to it and fails.

FYI, ctypes also has c_char_p which is a special handler for null-terminated byte strings (char*), so this works without the extra string extraction:

import ctypes as ct

dll = ct.CDLL('./test')
getArrayOfStrings = dll.getArrayOfStrings
getArrayOfStrings.argtypes = ct.POINTER(ct.POINTER(ct.c_char_p)),
getArrayOfStrings.restype = None

names = ct.POINTER(ct.c_char_p)()
dll.getArrayOfStrings(names)
i = 0
while names[i]:
    print(names[i])
    i += 1

(same output)

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论