【问题标题】:How to treat output arguments like char** array in python?如何在 python 中处理输出参数,如 char** 数组?
【发布时间】:2020-09-29 13:28:37
【问题描述】:

我正在尝试从 Python 脚本调用 C 方法,但在调用将 char** 数组作为参数输出的方法时遇到了问题。 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

【问题讨论】:

    标签: python c++ char ctypes


    【解决方案1】:

    我已将函数简化为专注于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,那么动态分配内存以便稍后释放可以使其更加灵活。这对于 char*** 来说相当难看,但它可以工作:

    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']
    

    【讨论】:

    • 如何在不知道名称大小的情况下在 getNames(char** names) 中为 **names 分配内存。这里 ARR3 = c_char_p * 3,表示分配了 3 个字符串。假设如果我们不知道字符串的大小,我们该如何分配
    • @sandhya 在第二个示例中,它根据向量的大小分配内存。我对矢量内容进行了硬编码,但您不必这样做。将尽可能多的字符串添加到要返回的向量中。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多