【问题标题】:Pass str as an int array to a Python C extended function (extended using SWIG)将 str 作为 int 数组传递给 Python C 扩展函数(使用 SWIG 扩展)
【发布时间】:2018-04-26 20:14:36
【问题描述】:

如何将使用 python 代码获得的 str 值(包含 3000 个 {'0', '1'} 字节)作为参数传递给需要 int * 的 python c 扩展函数(使用 SWIG 扩展)(固定长度的 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 返回语法错误:超过 255 个参数。这仍然不能帮助我传递分配的 str 值而不是初始化的 ctypes int 数组。

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

【问题讨论】:

  • 从函数返回本地数组是一件棘手的事情,因为它们驻留在堆栈中,并且在超出范围时被销毁,因此稍后当返回的地址将被取消引用时,您很可能得到一个 segfault。要么将其设为static,要么动态分配它,或者将其作为第二个(输出)参数添加到您的函数中。我会选择#3。

标签: python arrays string int swig


【解决方案1】:

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

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

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

由于他们都在做同样的事情,将它们混合在一起是没有意义的。因此,请选择最适合您的需求。


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;
}

注意事项

  • 基于 cmets,我将函数中的类型从 int* 更改为 char*,因为它的紧凑性提高了 4 倍(尽管它仍然 ~700% 效率低下,因为每个 char 有 7 位被忽略而不是只使用其中一个;可以修复,但需要按位处理)
  • 我把 a 变成了第二个nd 参数 (bitsOut)。我认为这是最好的,因为分配和取消分配数组是调用者的责任(从一开始就是 3rd 选项)
  • 我还修改了索引范围(不更改功能),因为使用低索引值并在一个地方添加一些东西而不是高索引值并在另一个地方减去(相同)一些东西更有意义地点
  • 返回值是设置的位数(显然,在本例中为 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"\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 教程中记录)
  • 我正在打印 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试图将其内容设置为超过大小
  • 对于这个(简单)函数,ctypes 变体更容易(至少对我来说,因为我不经常使用 swig),但对于更复杂的函数/ 项目这将成为一种矫枉过正,切换到 swig 将是正确的做法

输出(在Win上使用Python3.5运行):

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。 痛饮

  • 几乎 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 文件包含(这不是强制性的,但这样会更优雅)
  • 其余的都差不多

test_swig.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()

注意事项

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

输出

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]


3。普通的Python C API

  • 我将此部分添加为个人练习
  • 这是 swig 所做的,但“手动”

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.pytest_swig.py 相似,但有一个(明显的)区别:from swig_demo import exposekey 应替换为 from capi_demo import exposekey
  • 输出也与test_swig.py相同(再次,这里不再重复)

【讨论】:

  • 非常感谢。这完美地工作。但就像你说的那样,我正在尝试将精简逻辑应用于复杂的功能/项目,因此使用 SWIG。
  • 在上面的代码中,你定义了要传递的输入数组,如果有一个 0 和 1 的输入字符串(type= python str)我如何将它作为输入传递给这个 ctypes 函数?再次感谢。
  • 如果你想传递一个字符串,你需要改变函数(所以它的bits参数是char*),同样来自Python,它的@ 987654344@。将 0/1 值(1 位)存储在 char(8 位)中肯定比在 int(32 位)中存储更好,但是它仍然比存储的 效率低 700% PoV(因为没有使用char 7 个其他位)。在这个主题上,如果更改bits 是否也更改out(更改为char*)不是一个好主意?或者你可以走简单的路(像这样初始化bits):for i, char in enumerate(your_string):bits[2000 + i] = ord(char) - ord("0")
  • 您尝试过 swig 变体吗?
  • 尝试了您建议传递给 char * 的方法,并在 c 端使用 atoi() 进行位转换,而不是在 python 端使用 ord()。谢谢
猜你喜欢
  • 2020-11-01
  • 1970-01-01
  • 1970-01-01
  • 2014-04-22
  • 2012-05-25
  • 1970-01-01
  • 2010-10-01
  • 2012-07-26
  • 1970-01-01
相关资源
最近更新 更多