【问题标题】:CPPYY/CTYPES passing array of strings as char* args[]CPPYY/CTYPES 将字符串数组作为 char* args[] 传递
【发布时间】:2020-09-12 12:21:52
【问题描述】:

我最近才开始使用cppyyctypes,所以这可能是一个有点愚蠢的问题。我有以下 C++ 函数:

float method(const char* args[]) {
    ...
}

在 Python 中,我想将 args 作为字符串列表传递,即:

args = *magic*
x = cppyy.gbl.method(args)

之前找过this,所以用了

def setParameters(strParamList):
    numParams    = len(strParamList)
    strArrayType = ct.c_char_p * numParams
    strArray     = strArrayType()
    for i, param in enumerate(strParamList):
        strArray[i] = param
    lib.SetParams(numParams, strArray)

来自 Python:

args = setParameters([b'hello', b'world'])

c_types.c_char_p 需要一个字节数组。但是,当调用x = cppyy.gbl.method(args) 时,我得到了

TypeError: could not convert argument 1 (could not convert argument to buffer or nullptr)

我不完全确定为什么这是错误的,因为 args<__main__.c_char_p_Array_2> 对象,我认为应该将其转换为 const char* args[]

【问题讨论】:

  • 为什么这个问题被标记为“c++”?很明显它是 C(不是很好的 C)。
  • 方法来自外部.cpp文件
  • @JesperJuhl 不,不是。问题中的代码可以是有效的 C 或 C++。为了让它成为“非常清楚的 C”,它必须在 C++ 中做一些非法的事情,比如将 void * 转换为其他类型的指针而不进行强制转换。
  • @Joseph 好的,很公平。但是你必须告诉我,这显然是用一种非常 C 风格的方式编写的,而不是你用现代 C++ 来处理它的方式。对吧?
  • @JesperJuhl 你的意思是使用std::vector<std::string> 之类的东西作为参数类型?通常我同意你应该在 C++ 中这样做,但这样做不会使通过 FFI 使用变得更加困难,这正是这里的用例?

标签: python c++ arrays string ctypes


【解决方案1】:

ctypes 没有可从 C/C++ 用于扩展编写者的公共 API,因此 cppyy 对 ctypes 的处理必然有些笨拙。出了什么问题,const char* 生成的 ctypes 数组的类型是 const char*[2] 而不是 const char*[] 并且由于 cppyy 对 ctypes 类型进行直接类型匹配,所以失败了。

按原样,某处的某些代码需要将 Python 字符串转换为低级 C 字符串,并在调用期间保留该内存。就我个人而言,我会使用一个小的 C++ 包装器,而不必在 Python 方面考虑问题。关键是 std::vector<std::string> 可以处理必要的转换(例如,不需要 bytes 类型,但如果您愿意,当然可以使用)并且它可以保存临时内存。

所以,如果给你一些像这样的第 3 方接口(将其内联为 cppyy 只是为了示例):

import cppyy

cppyy.cppdef("""
    float method(const char* args[], int len) {
        for (int i = 0; i < len; ++i)
            std::cerr << args[i] << " ";
        std::cerr << std::endl;
        return 42.f;
    }
""")

然后我会生成一个包装器:

# write a C++ wrapper to hide C code
cppyy.cppdef("""
    namespace MyCppAPI {
       float method(const std::vector<std::string>& args) {
           std::vector<const char*> v;
           v.reserve(args.size());
           for (auto& s : args) v.push_back(s.c_str());
           return ::method(v.data(), v.size());
       }
    }
""")

然后用C++版本替换原来的C API:

# replace C version with C++ one for all Python users
cppyy.gbl.method = cppyy.gbl.MyCppAPI.method

对于下游的任何其他人来说,事情都会如预期的那样:

# now use it as expected
cppyy.gbl.method(["aap", "noot", "mies"])

说了这么多,显然 cppyy 没有理由不能自动进行这一位包装。我创建了这个问题:https://bitbucket.org/wlav/cppyy/issues/235/automatically-convert-python-tuple-of

【讨论】:

  • 感谢您的解释以及将解决方案直接集成到存储库中,非常感谢!
【解决方案2】:

为了有一个具体的例子,我将使用它作为.cpp 文件:

#include <cstdlib>

extern "C"
float method(const char* args[]) {
    float sum = 0.0f;
    const char **p = args;
    while(*p) {
        sum += std::atof(*p++);
    }
    return sum;
}

我假设它是用g++ method.cpp -fPIC -shared -o method.so 编译的。鉴于这些假设,下面是一个如何从 Python 中使用它的示例:

#!/usr/bin/env python3

from ctypes import *

lib = CDLL("./method.so")
lib.method.restype = c_float
lib.method.argtypes = (POINTER(c_char_p),)

def method(args):
    return lib.method((c_char_p * (len(args) + 1))(*args))

print(method([b'1.23', b'45.6']))

我们创建一个 C 数组来保存 Python 参数。 len(args) + 1 确保空指针哨兵有空间。

【讨论】:

  • 谢谢!我猜我得到的一些段错误也是由于缺少空字节
猜你喜欢
  • 1970-01-01
  • 2017-04-21
  • 2014-02-03
  • 2020-08-01
  • 1970-01-01
  • 2021-04-27
  • 1970-01-01
  • 2011-03-30
  • 1970-01-01
相关资源
最近更新 更多