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

Pass str as an int array to a Python C extended function (extended using SWIG)

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

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 returns 语法错误:超过 255 个参数。这仍然不能帮助我传递分配的 str 值而不是初始化的 ctypes int 数组。

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

关于我的评论,这里有一些关于从函数 returning 数组的更多细节:[SO]: Returning an array using C。简而言之:处理此问题的方法:

  1. 使 returned 变量 static
  2. 动态分配它(使用malloc(family)或new
  3. 把它变成函数的附加参数

Python 解释器中将那段 C 代码转换为 运行 有两种方法:

既然他们都在做同样的事情,把他们混在一起是没有意义的。因此,请选择最适合您需求的一款。


1。 ctypes

  • 这就是你开始的地方
  • 这是使用 ctypes
  • 一种 处理方式

ctypes_demo.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 倍(尽管它仍然是 ~700% 效率低下,因为每个字符的 7 位被忽略,而只有其中一个被使用;这可以修复,但需要 按位 处理)
  • 我取了a,变成了2nd参数(bitsOut)。我认为这是最好的,因为调用者有责任分配和释放数组(从一开始的 3rd 选项)
  • 我还修改了索引范围(不改变功能),因为使用低索引值并在一个地方添加一些东西比使用高索引值并减去(相同)一些东西更有意义在另一个地方
  • return 值是设置的位数(显然,在本例中为 1000)但这只是一个示例
  • printf 它只是虚拟的,表明 C 代码被执行
  • 在处理此类数组时,建议也传递它们的维度,以避免出现越界错误。此外,错误处理 是一个重要方面

test_ctypes.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"[=11=]" * 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 教程中)
  • 我正在打印 bits_out 数组(只有第一部分和相关部分,其余部分是 0 ) 为了证明 C 代码完成了它的工作
  • 我在开始时用 2000 个虚拟 0 初始化 bits_in 数组,因为这些值在这里不相关。此外,输入字符串 (bits_string) 的长度不是 3000 个字符(原因很明显)。如果你的 bits_string 是 3000 个字符,你可以简单地初始化 bits_in 像:bits_in = create_string_buffer(bits_string.encode())
  • 不要忘记bits_out 初始化为一个大小足够大(在我们的示例中为 1000)的数组它的目的,否则 segfault 可能会在尝试将其内容设置为超过 size
  • 时出现
  • 对于这个(简单的)函数,ctypes 变体更简单(至少对我来说,因为我不使用 swig经常),但对于更复杂的功能/项目,它会变得矫枉过正,切换到 swig 将是正确的做法

输出 (运行 Python3.5Win ):

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


2。 痛饮

  • 几乎 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);

备注:

  • 我在这里分配数组 return 它(从一开始的 2nd 选项)
  • .i文件是一个标准的swig接口文件
    • 定义模块及其导出(通过 %include
    • 值得一提的是 %newobject 指令,它释放了 return 由 exposekey 编辑的指针以避免内存泄漏
  • .h 文件只包含函数声明,以便被 .i 文件包含(这不是强制性的,但这样事情会更优雅)
  • 其他差不多

test_swig.py:

from swig_demo import exposekey

bits_in = "010011000110101110101110101010010111011101101010101"


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


if __name__ == "__main__":
    main()

备注:

  • Python 程序员的 PoV
  • 让事情变得更有意义
  • 代码短了很多(那是因为swig在幕后做了一些"magic"):
    • .i文件生成的包装器.c包装器文件有~120K
    • swig_demo.py生成的模块有~3K
  • 我对字符串开头的 2000 0 使用了相同的技术

输出:

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


3。平原 Python C API

  • 我将这部分添加为个人练习
  • 这就是 swig 所做的,但是 "manually"

capi_demo.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.hswig_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 相同(同样,这里不重复)