将str作为int数组传递给Python C扩展函数(使用SWIG扩展)

2024-10-03 00:30:34 发布

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

如何将使用python代码作为参数获得的str值(包含3000{'0','1'}字节)传递给需要int *(固定长度int数组)作为输入参数的python c扩展函数(使用SWIG扩展)?我的代码是这样的:

int *exposekey(int *bits) {
    int a[1000];
    for (int j=2000; j < 3000; j++) {
        a[j - 2000] = bits[j];
    }
    return a;
}

我试图使用ctypes(请参见下面的代码):

import ctypes
ldpc = ctypes.cdll.LoadLibrary('./_ldpc.so')
arr = (ctypes.c_int * 3072)(<mentioned below>)
ldpc.exposekey(arr)

3072{0,1}进入位置。Python返回语法错误:超过255个参数。这仍然不能帮助我传递指定的str值,而不是初始化的ctypesint数组。

其他建议包括使用SWIG类型映射,但是如何将str转换成int *?提前谢谢。


Tags: 函数代码for参数return字节数组ctypes
1条回答
网友
1楼 · 发布于 2024-10-03 00:30:34

关于我的注释,这里有一些关于从函数返回数组的详细信息:[SO]: Returning an array using C。简而言之:处理这个问题的方法:

  1. 将返回的变量设为静态变量
  2. 动态分配(使用malloc(family)或new
  3. 将其转换为函数的附加参数

让这段C代码在Python解释器中运行有两种方法:

既然他们都在做同一件事,把他们混在一起是没有意义的。所以,选一个最适合你需要的。


一。ctypes

  • 这就是你开始的
  • 这是用ctypes做事情的方式之一

ctypes演示.c:

#include <stdio.h>

#if defined(_WIN32)
#  define CTYPES_DEMO_EXPORT_API __declspec(dllexport)
#else
#  define CTYPES_DEMO_EXPORT_API
#endif


CTYPES_DEMO_EXPORT_API int exposekey(char *bitsIn, char *bitsOut) {
    int ret = 0;
    printf("Message from C code...\n");
    for (int j = 0; j < 1000; j++)
    {
        bitsOut[j] = bitsIn[j + 2000];
        ret++;
    }
    return ret;
}

注意:

  • 基于注释,我将函数中的类型从int*更改为char*,因为它的紧凑性是原来的4倍(尽管由于忽略每个字符的7位而只使用其中一个字符,它仍然效率低下;这是可以修复的,但需要按位处理)
  • 我把a转换成2nd参数(bitsOut)。我认为这是最好的,因为分配和释放数组是调用方的责任(从一开始就是3rd选项)
  • 我还修改了索引范围(不改变功能),因为使用低索引值并在一个地方向它们添加一些内容比使用高索引值在另一个地方减去(相同的)内容更有意义
  • 返回值是设置的位数(显然,在本例中是1000位),但它只是一个例子
  • printf这只是一个伪代码,以显示执行了C代码
  • 在处理这样的数组时,建议也传递它们的维度,以避免出现越界错误。此外,错误处理也是一个重要方面

测试类型.py

from ctypes import CDLL, c_char, c_char_p, c_int, create_string_buffer


bits_string = "010011000110101110101110101010010111011101101010101"


def main():
    dll = CDLL("./ctypes_demo.dll")
    exposekey = dll.exposekey

    exposekey.argtypes = [c_char_p, c_char_p]
    exposekey.restype = c_int

    bits_in = create_string_buffer(b"\0" * 2000 + bits_string.encode())
    bits_out = create_string_buffer(1000)
    print("Before: [{}]".format(bits_out.raw[:len(bits_string)].decode()))
    ret = exposekey(bits_in, bits_out)
    print("After: [{}]".format(bits_out.raw[:len(bits_string)].decode()))
    print("Return code: {}".format(ret))


if __name__ == "__main__":
    main()

注意:

  • 1st,我想说的是,运行您的代码并没有引发您得到的错误
  • 指定函数的argtypesrestype是必需的,而且还可以简化操作(在ctypes教程中有文档说明)
  • 我正在打印位数组(只有第一部分和相关部分,其余部分0),以证明C代码完成了它的工作
  • 我用2000 dummy0初始化数组中的位,因为这些值与这里无关。此外,输入字符串(bits_string)不长3000个字符(原因显而易见)。如果您的位字符串有3000个字符长,您可以简单地初始化位,例如:bits_in = create_string_buffer(bits_string.encode())
  • 不要忘记bits_out初始化为具有足够大(在我们的示例1000中)的数组,否则当试图将其内容设置为超过该大小时可能会出现segfault问题
  • 对于这个(简单的)函数,ctypes变量更容易(至少对我来说,因为我不经常使用swig),但是对于更复杂的函数/项目,它将变得过于复杂,切换到swig将是正确的做法

输出(使用Python3.5打开Win运行):

c:\Work\Dev\StackOverflow\q47276327>"c:\Work\Dev\VEnvs\py35x64_test\Scripts\python.exe" test_ctypes.py
Before: [                                                   ]
Message from C code...
After: [010011000110101110101110101010010111011101101010101]
Return code: 1000


2。swig

  • 几乎ctypes部分的所有内容都适用于这里

swig_demo.c:

#include <malloc.h>
#include <stdio.h>
#include "swig_demo.h"


char *exposekey(char *bitsIn) {
    char *bitsOut = (char*)malloc(sizeof(char) * 1000);
    printf("Message from C code...\n");
    for (int j = 0; j < 1000; j++) {
        bitsOut[j] = bitsIn[j + 2000];
    }
    return bitsOut;
}

swig_demo.i:

%module swig_demo
%{
#include "swig_demo.h"
%}

%newobject exposekey;
%include "swig_demo.h"

swig_demo.h:

char *exposekey(char *bitsIn);

注意:

  • 在这里,我分配数组并返回它(从头开始的2nd选项)
  • .i文件是标准的swig接口文件
    • 定义模块及其导出(通过%include
    • 值得一提的是%newobject指令,该指令释放exposekey返回的指针,以避免内存泄漏
  • .h文件只包含函数声明,以便包含在.i文件中(这不是必需的,但这样做更优雅)
  • 其他的都差不多

测试开关py

from swig_demo import exposekey

bits_in = "010011000110101110101110101010010111011101101010101"


def main():
    bits_out = exposekey("\0" * 2000 + bits_in)
    print("C function returned: [{}]".format(bits_out))


if __name__ == "__main__":
    main()

注意:

  • PythonprogrammerPoV
  • 代码要短得多(这是因为swig在幕后做了一些“魔术”:
    • .i文件生成的包装器.c文件具有~120K
    • swig demo.py生成的模块具有~3K
  • 我用了同样的方法,在字符串的开头加上20000

输出

c:\Work\Dev\StackOverflow\q47276327>"c:\Work\Dev\VEnvs\py35x64_test\Scripts\python.exe" test_swig.py
Message from C code...
C function returned: [010011000110101110101110101010010111011101101010101]


三。普通的Python C API
  • 我把这部分作为个人练习加上去了
  • 这就是swig所做的,但是“手动”

capi演示.c:

#include "Python.h"
#include "swig_demo.h"

#define MOD_NAME "capi_demo"


static PyObject *PyExposekey(PyObject *self, PyObject *args) {
    PyObject *bitsInArg = NULL, *bitsOutArg = NULL;
    char *bitsIn = NULL, *bitsOut = NULL;
    if (!PyArg_ParseTuple(args, "O", &bitsInArg))
        return NULL;
    bitsIn = PyBytes_AS_STRING(PyUnicode_AsEncodedString(bitsInArg, "ascii", "strict"));
    bitsOut = exposekey(bitsIn);
    bitsOutArg = PyUnicode_FromString(bitsOut);
    free(bitsOut);
    return bitsOutArg;
}


static PyMethodDef moduleMethods[] = {
    {"exposekey", (PyCFunction)PyExposekey, METH_VARARGS, NULL},
    {NULL}
};


static struct PyModuleDef moduleDef = {
    PyModuleDef_HEAD_INIT, MOD_NAME, NULL, -1, moduleMethods
};


PyMODINIT_FUNC PyInit_capi_demo(void) {
    return PyModule_Create(&moduleDef);
}

注意:

  • 它需要swig-demo.h和swig-demo.c(这里不会复制它们的内容)
  • 它只与Python 3一起工作(实际上,让它工作起来让我很头疼,特别是因为我习惯于使用不再存在的PyString-AsString)
  • 错误处理不好
  • test_capi.py与test_swig.py相似,但有一个(明显的)区别:from swig_demo import exposekey应替换为from capi_demo import exposekey
  • 输出也与test_swig.py相同(这里不重复)

相关问题 更多 >