如何在python中处理像char**array这样的输出参数?

2024-05-04 00:43:41 发布

您现在位置:Python中文网/ 问答频道 /正文

我试图从Python脚本中调用C方法,但在调用将char**array作为参数输出的方法时遇到了一个问题。 C层中的方法如下所示helper.c文件:

//This method takes filename as input and oNames as output
  extern C int GetNames(char* iFilename, char** oNames)
{
    int oNumNames, oStatus;
    /*io* pIo = GetIoInstance();*/
    std::vector<EString> names;
    CreateIoInstance(iFilename);
    oStatus = pIo->get_names(names);
    oNumNames = (int)names.size();

    for (int ii = 0; ii < oNumNames; ii++)
    {
        strcpy(oNames[ii], names[ii].c_str());
    }
    return 0;
}

请帮助我从python脚本调用此方法

from ctypes import *

dll = CDLL('D:\\python\\working.dll')
dll.GetNames = dll.GetNames
dll.GetNames.argtypes = (c_char_p, POINTER(c_char_p))
dll.GetStageNames.restype = c_int

filename = "in.h5"
def GetNames(filename):
    ostagenames = POINTER(c_char_p)
    err = dll.GetStageNames(filename, ostagenames)
    return err, ostagenames.value

Tags: 方法脚本namesasfilenameintiidll
1条回答
网友
1楼 · 发布于 2024-05-04 00:43:41

我简化了函数,将重点放在char**参数上。在编写示例函数时,它假定内存已预分配,但没有接口指示用于输出的数组大小或数组中的单个字符串。在本例中,内存预分配给一个3元素数组,每个字符串最多20个字符,但实际上,用户必须预分配足够长的字符串,以便在实际情况下保存返回值

test.cpp:

#define API __declspec(dllexport)
#include <vector>
#include <string>
#include <cstring>

using namespace std;

//This method takes filename as input and oNames as output
extern "C" API void GetNames(char** oNames)
{
    vector<string> names { "one", "two", "three" };
    for (size_t i = 0; i < names.size(); ++i) {
        strcpy(oNames[i], names[i].c_str());
    }
}

test.py:

from ctypes import *

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

def get_names():
    ARR3 = c_char_p * 3  # equivalent to char*[3] type in C

    # list of 3 mutable pointers to buffers
    buffers = [cast(create_string_buffer(20),c_char_p) for _ in range(3)]

    names = ARR3(*buffers) # array initalized with buffers
    dll.GetNames(names)
    return list(names)

print(get_names())

输出:

[b'one', b'two', b'three']

如果您可以随意更改API,那么动态分配内存以便以后可以释放它会使其更加灵活。这是一个非常丑陋的字符***,但它的工作:

test.cpp:

#define API __declspec(dllexport)
#include <vector>
#include <string>
#include <cstring>

using namespace std;

extern "C" {

API void GetNames(char*** oNames) {
    vector<string> names { "one", "two", "three" };
    auto arr = new char*[names.size() + 1];
    for (size_t i = 0; i < names.size(); ++i) {
        auto len = names[i].size() + 1;
        arr[i] = new char[len];
        strcpy_s(arr[i], len, names[i].c_str());
    }
    arr[names.size()] = nullptr;
    *oNames = arr;
}

API void FreeNames(char** names) {
    if(names) {
        for(size_t i = 0; names[i]; ++i)
            delete [] names[i];
        delete [] names;
    }
}

}

test.py:

from ctypes import *

# ctypes.c_char_p has special handling for strings,
# but hides the pointer value.  Deriving a type
# from c_char_p prevents this special handling
# and allows access to the pointer, so we can later
# free it.
class PCHAR(c_char_p):
    pass

dll = CDLL('./test')
dll.GetNames.argtypes = POINTER(POINTER(PCHAR)),
dll.GetNames.restype = None
dll.FreeNames.argtypes = POINTER(PCHAR),
dll.FreeNames.restype = None

def get_names():
    pnames = POINTER(PCHAR)() # allocate char**
    dll.GetNames(byref(pnames)) # pass char***
    i = 0
    names = []
    while pnames[i]:  # returned char* array is terminated with null
        names.append(pnames[i].value) # create and save the Python byte string
        i += 1
    dll.FreeNames(pnames)
    return names

print(get_names())

输出:

[b'one', b'two', b'three']

相关问题 更多 >