如何从 python 调用带有 Char** 参数和 int* 参数的 C 方法?

Posted

技术标签:

【中文标题】如何从 python 调用带有 Char** 参数和 int* 参数的 C 方法?【英文标题】:how to call C methods with Char** argument and int* arguments from python? 【发布时间】:2020-09-09 09:54:54 【问题描述】:

这是加载 erf_utils_io_D.dllmodule.py script,其中包含 io.cio.h 文件 我成功加载了库并传递了 ctype 参数,例如

c_int, c_float, POINTER(c_int), POINTER(c_Float)

module.py

//python code module.py
    import sys
    from ctypes import *
    #load the required library and make sure the folder where    erf_utils_io_D.dll is present

dll = CDLL('D:\\erf_utils_python\\erf_utils_io.dll')
getContourResults = dll.getContourResults

class Utility(object):
    def __init__(self):
        print('i am inside init')
        self.stagename = "post"
        self.stateids = (c_int * 1) (2934)
        self.stateidcount = 1
        self.entityidcount = 1
        self.entityid = (c_int * 1) (1)
        self.entitytype = "FPM"
        self.variablecount = 1
        self.ores = [1.0]
        self.filename ='allinone_RESULT.erfh5'
        #This is how char** is treted in python for variablegroup
        self.variablegroup = ["FPM_Mach_Number"]
        self.string_length = len(self.variablegroup)
        self.select_type = (c_wchar_p * self.string_length)
        self.select = self.select_type()
        for key, item in enumerate(self.variablegroup):
                 self.select[key] = item


        #This is how char** is treated infor variable
        self.variable = ["FPM_Mach_Number"]
        self.var_len = len(self.variable)
        self.var_type = (c_wchar_p * self.var_len)
        self.variable_list = self.var_type()
        for key, item in enumerate(self.variable):
              self.variable_list[key] = item


    def run(self):
        getContourResults.argtypes = (POINTER(c_char_p), POINTER(c_char_p), c_int, POINTER(c_int),
                              c_int, POINTER(c_int), POINTER(c_char), c_int, self.select_type ,
                              self.var_type, POINTER(c_float))

        getContourResults.restype = (c_int)


        err = getContourResults(self.filename, self.stagename, self.stateidcount,
        self.stateids, self.entityidcount,self.entityid, self.entitytype, self.variablecount, self.select, self.variable_list, self.ores)


reader = Utility()
reader.run()

code.cpp 看起来像这样

extern "C"

#endif
    __declspec(dllexport) int getContourResults(char* iFilename, char* iStagename, int iStateidCnt, int* Stateids,
        int iEntityIdCount, int* iEntityids, char* iEntityType,
        int iVariablecnt, char** iVariablegroup, char** ivariable,
        float* oResults);

请告诉我如何将参数从 python 脚本传递给方法 getContourResults() 存在于 io.c

【问题讨论】:

通常char** 应指向以 NUL 结尾的 char*s 数组的开头;你确定这里是否真的是这样吗? 顺便说一句,这将很难测试答案,因为不是你的人不会有erf_utils_io.dll。在标准库实用程序之间找到可比较的调用约定并围绕它构建minimal reproducible example 通常是获得更好结果的途径,因此回答您的问题的人将能够实际测试他们的答案(以及对这些答案进行判断和投票的人,同样)。 Python 不能直接调用这样的函数。您将需要创建一个包装函数,该函数公开与Python's C API 一致的接口以适应您的目标函数。您可以在 C 中直接将这样的包装器写入 C API,或者您也应该能够在 Cython 中编写它,这更像 Python。 前两个参数是getContourResultsc_char_p,c_char_p不是POINTER(c_char_p),POINTER(c_char_p) 【参考方案1】:

没有迹象表明该功能的实现,所以我做了一些假设。通过此示例,您应该能够适应您的实际用例。主要修复是 .argtypes 更正和使用 c_char_p 而不是 c_wchar_p 和一个简单的帮助函数将列表转换为 ctypes 数组。

以下是关于类型的一些说明:

c_char_p == char* 在 C 中。传递一个字节字符串,例如b'string'c_wchar_p == wchar_t* 在 C 中。传递一个 Unicode 字符串,例如'string'POINTER(c_char_p) == char** 在 C 中。 POINTER(c_float) == float* 在 C 中。创建存储 (c_float()) 并传递 byref。使用.value 成员将返回的浮点数作为 Python 整数访问。

test.cpp

#include <stdio.h>

extern "C" __declspec(dllexport) int getContourResults(char* iFilename, char* iStagename, int iStateidCnt, int* Stateids,
    int iEntityIdCount, int* iEntityids, char* iEntityType,
    int iVariablecnt, char** iVariablegroup, char** ivariable,
    float* oResults)

    printf("iFilename = %s\n"
           "iStagename = %s\n"
           ,iFilename,iStagename);
    for(int i = 0; i < iStateidCnt; ++i)
        printf("Stateids[%d] = %d\n",i,Stateids[i]);
    for(int i = 0; i < iEntityIdCount; ++i)
        printf("iEntityids[%d] = %d\n",i,iEntityids[i]);
    printf("iEntityType = %s\n",iEntityType);
    for(int i = 0; i < iVariablecnt; ++i) 
        printf("iVariablegroup[%d] = %s\n",i,iVariablegroup[i]);
        printf("ivariable[%d] = %s\n",i,ivariable[i]);
    
    *oResults = 1.2f;
    return 5;

test.py

from ctypes import *

dll = CDLL('./test')
dll.getContourResults = dll.getContourResults
dll.getContourResults.argtypes = (c_char_p,c_char_p,c_int,POINTER(c_int),c_int,POINTER(c_int),c_char_p,
                              c_int,POINTER(c_char_p),POINTER(c_char_p),POINTER(c_float))
dll.getContourResults.restype = c_int

def make_array(ctype,arr):
    return len(arr),(ctype * len(arr))(*arr)

def getContourResults(filename,stagename,sids,eids,entitytype,groups,variables):
    stateidcount,stateids = make_array(c_int,sids)
    entityidcount,entityid = make_array(c_int,eids)
    groups = [b'group1',b'group2']
    variables = [b'var1',b'var2']
    if len(groups) != len(variables):
        raise ValueError('assuming groups and variables same length')
    _,variablegroup = make_array(c_char_p,groups)
    variablecount,variable = make_array(c_char_p,variables)
    ores = c_float()
    err = dll.getContourResults(filename,stagename,stateidcount,stateids,entityidcount,entityid,
                                entitytype,variablecount,variablegroup,variable,byref(ores))
    return err,ores.value

sids = [1,2,3]
eids = [4,5,6]
groups = [b'group1',b'group2']
variables = [b'var1',b'var2']
err,ores = getContourResults(b'filename',b'stagename',sids,eids,b'entitytype',groups,variables)
print(f'err = err')
print(f'ores = ores:.2f')

输出:

iFilename = filename
iStagename = stagename
Stateids[0] = 1
Stateids[1] = 2
Stateids[2] = 3
iEntityids[0] = 4
iEntityids[1] = 5
iEntityids[2] = 6
iEntityType = entitytype
iVariablegroup[0] = group1
ivariable[0] = var1
iVariablegroup[1] = group2
ivariable[1] = var2
err = 5
ores = 1.20

【讨论】:

如果我们需要从一个方法中输出 char **值,我们如何在脚本中声明 argtypes 以及我们如何返回 @sandhya 使用minimal reproducible example 提出新问题。如果这个答案回答了这个问题,请考虑支持/接受它。见What to do when someone answers my question。 int GetNames(char* iFilename, char** oNames) int oNumNames, oStatus; /*io* pIo = GetIoInstance();*/ std::vector&lt;EString&gt; names; CreateIoInstance(iFilename); oStatus = pIo-&gt;get_names(names); oNumNames = (int)names.size(); for (int ii = 0; ii &lt; oNumNames; ii++) strcpy(oNames[ii], names[ii].c_str()); return 0; 这是C层的代码我需要从python层调用这个方法,我担心第二个参数如何为onames分配内存 @sandhya I answered 在您的其他问题中。 在上面的示例中,它输出 err 和 ores.value 并在输出中打印 1.20,所以我的问题是如果 *oresults = 1.20, 1.30, 1.50 那么我们如何为此分配内存float 数组以及如何在输出上打印它。(而且我们不知道 python 中矿石的大小来分配内存)

以上是关于如何从 python 调用带有 Char** 参数和 int* 参数的 C 方法?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 django 模板中调用带有参数的 python 函数? [复制]

将 char 数组发送到 DLL 调用“不知道如何转换参数”时出错

关于delphi调用C++的DLL中char*参数的问题

如何使用 JNI 从 JAVA 调用带有 C++ 参数的函数?

如何从 unix shell 调用带有参数的子例程

从python调用c++函数