将str作为int数组传递给Python C扩展函数(使用SWIG扩展) [英] Pass str as an int array to a Python C extended function (extended using SWIG)

查看:143
本文介绍了将str作为int数组传递给Python C扩展函数(使用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];
}
返回a;
}

我尝试过的是使用 ctypes (见下面的代码):

  import ctypes 
ldpc = ctypes.cdll.LoadLibrary( './_ldpc.so')
arr =(ctypes.c_int * 3072)(<下面提及>)
ldpc.exposekey(arr)

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



其他建议包括使用SWIG类型映射但是如何将str转换为 int * ?在此先感谢。

解决方案

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


  1. 使返回的变量 static

  2. 动态分配(使用 malloc (系列)或 new

  3. 将其转换为函数的附加参数

获取 C 可以通过两种方式在 Python 解释器中运行代码:





由于他们都在做同样的事情,因此将它们混合在一起毫无意义。因此,选择最适合您需求的那个。






1。 ctypes




  • 这就是你的开始

  • 它是一个使用 ctypes做事的方式



ctypes_demo.c

  #include< stdio.h> 


int exposekey(char * bitsIn,char * bitsOut){
int ret = 0;
printf(来自C代码的消息...... \ n);
for(int j = 0; j< 1000; j ++)
{
bitsOut [j] = bitsIn [j + 2000];
ret ++;
}
返回ret;
}

注释




  • 根据评论,我将函数中的类型从 int * 更改为 char * ,因为它的紧凑性要高4倍(尽管它仍然是700%效率低,因为每个char的7位被忽略,而只使用其中一个;这可以修复,但需要按位处理)

  • 我拿了 a 并转入2 nd 参数( bitsOut )。我认为这是最好的,因为调用者负责分配和释放数组(从一开始就是3 rd 选项)

  • 我还修改了索引范围(没有更改功能),因为使用低索引值并在一个地方添加内容更有意义,而不是高索引值并在另一个地方减去(相同)某些内容

  • 返回值是设置的位数(显然,在这种情况下为1000),但它只是一个示例

  • printf 它只是虚拟,以显示 C 代码被执行

  • 在处理此类数组时,建议也传递它们的维度,以避免超出界限错误。此外,错误处理是一个重要方面



test_ctypes.py

 来自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(返回代码:{}。format (ret))


如果__name__ ==__ main__:
main()

备注




  • 1 st ,我想提一下,运行代码并没有引发你的错误

  • 指定函数的 argtypes restype 不是强制性的,但会使事情变得更容易(记录在 ctypes 教程)

  • 我正在打印 bits_out 数组(只有第一个 - 和相关 - 部分,如同其余的都是0)以证明 C 代码完成了它的工作

  • 我初始化 bits_in 数组在开头有2000个虚拟0,因为这些值在这里不相关。此外,输入字符串( bits_string )长度不是3000个字符(原因很明显)。如果你的 bits_string 是3000个字符长,你可以简单地初始化 bits_in ,如: bits_in = create_string_buffer( bits_string.encode())

  • 不要忘记初始化 bits_out 到达一个足够大的数组(在我们的示例中为1000),否则 segfault 可能会在尝试将其内容设置为超出大小时出现

  • For这个(简单)函数, ctypes 变体更容易(至少对我而言,因为我不经常使用 swig ),但对于更复杂的函数/项目,它将成为一种矫枉过正,转而 swig 将是正确的事情



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


  C:\Work\Dev\StackOverflow\q47276327> 中C:\Work\Dev\VEnvs\py35x64_test\Scripts\python.exe test_ctypes.py 
之前:[]
来自C代码的消息...
之后:[010011000110101110101110101010010111011101101010101]
返回代码:1000





2。 swig




  • 几乎来自 ctypes 部分的所有内容,也适用于此处



swig_demo.c

  #include< malloc.h> 
#include< stdio.h>
#includeswig_demo.h


char * exposekey(char * bitsIn){
char * bitsOut =(char *)malloc(sizeof(char) * 1000);
printf(来自C代码的消息...... \ n);
for(int j = 0; j< 1000; j ++){
bitsOut [j] = bitsIn [j + 2000];
}
返回bitsOut;
}

swig_demo.i

 %module swig_demo 
%{
#includeswig_demo.h
%}

%newobject exposekey;
%包括swig_demo.h

swig_demo.h

  char * exposekey(char * bitsIn); 

注释




  • 这里我正在分配数组并返回它(从头开始的2 nd 选项)

  • .i 文件是标准的 swig 接口文件


    • 定义模块及其导出(通过%include

    • 值得一提的是%newobject 指令取消分配 exposekey 返回的指针以避免内存泄漏


  • .h 文件只包含函数声明,以便包含在 .i 文件中(这不是强制性的,但这样的方式更优雅)

  • 其余几乎相同



test_swig.py



'pre> 从swig_demo进口exposekey

bits_in = 010011000110101110101110101010010111011101101010101


DEF主():
位_out = exposekey(\ 0* 2000 + bits_in)
print(C函数返回:[{}]。format(bits_out))


if __name__ ==__ main__:
main()

备注




  • Python 程序员的 PoV 开始,事情变得更有意义

  • 代码短得多(因为 swig 在幕后做了一些魔术):


    • .i 文件生成的包装器 .c 包装器文件有~120K

    • swig_demp.py 生成的模块有~3K


  • 我在字符串的开头使用了与2000 0相同的技术



输出


  c:\Work\Dev \StackOverflow \ q47276327>c:\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ 
来自C代码的消息...
C fun ction返回:[010011000110101110101110101010010111011101101010101]





3。普通 Python C API




  • 我将此部分作为个人练习添加

  • 这是 swig 的作用,但手动



capi_demo.c

  #includePython.h
#includeswig_demo.h

#define MOD_NAMEcapi_demo


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


静态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){
返回PyModule_Create(& moduleDef);
}

注释




  • 它需要 swig_demo.h swig_demo.c (此处不会复制其内容)

  • Python3 一起工作(实际上我有些头疼使其工作,特别是因为我习惯了 PyString_AsString 不再存在)

  • 错误处理不佳

  • test_capi.py 类似于 test_swig.py ,有一个(显而易见的)差异:来自swig_demo导入的 应该由capi_demo导入的替换exposekey

  • 输出也与 test_swig.py 相同(再次,不会在此处复制)


How can I pass a str value (containing 3000 {'0', '1'} bytes) obtained using python code as an argument to a python c extended function (extended using SWIG) which requires int * (fixed length int array) as an input argument? My code is such:

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

What I've tried was to use ctypes (see below code):

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

with 3072 {0, 1} entered in the position. Python returns syntax error : more than 255 arguments. This still doesn't help me to pass assigned str value instead of the initialized ctypes int array.

Other suggestion included using SWIG typemaps but how would that work for converting a str into int * ? Thanks in advance.

解决方案

Regarding my comment, here are some more details bout returning arrays from functions: [SO]: Returning an array using C. In short: ways handle this:

  1. Make the returned variable static
  2. Dynamically allocate it (using malloc (family) or new)
  3. Turn it into an additional argument for the function

Getting that piece of C code to run within the Python interpreter is possible in 2 ways:

Since they both are doing the same thing, mixing them together makes no sense. So, pick the one that best fits your needs.


1. ctypes

  • This is what you started with
  • It's one of the ways of doing things using ctypes

ctypes_demo.c:

#include <stdio.h>


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

Notes:

  • Based on comments, I changed the types in the function from int* to char*, because it's 4 times more compact (although it's still 700% inefficient since 7 bits of each char are ignored versus only one of them being used; that can be fixed, but requires bitwise processing)
  • I took a and turned into the 2nd argument (bitsOut). I think this is best because it's caller responsibility to allocate and deallocate the array (the 3rd option from the beginning)
  • I also modified the index range (without changing functionality), because it makes more sense to work with low index values and add something to them in one place, instead of a high index values and subtract (the same) something in another place
  • The return value is the number of bits set (obviously, 1000 in this case) but it's just an example
  • printf it's just dummy, to show that the C code gets executed
  • When dealing with such arrays, it's recommended to pass their dimensions as well, to avoid out of bounds errors. Also, error handling is an important aspect

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

Notes:

  • 1st, I want to mention that running your code didn't raise the error you got
  • Specifying function's argtypes and restype is not mandatory, but makes things easier (documented in the ctypes tutorial)
  • I am printing the bits_out array (only the first - and relevant - part, as the rest are 0s) in order to prove that the C code did its job
  • I initialize bits_in array with 2000 dummy 0s at the beginning, as those values are not relevant here. Also, the input string (bits_string) is not 3000 characters long (for obvious reasons). If your bits_string is 3000 characters long you can simply initialize bits_in like: bits_in = create_string_buffer(bits_string.encode())
  • Do not forget to initializebits_out to an array with a size large enough (in our example 1000) for its purpose, otherwise segfault might arise when trying to set its content past the size
  • For this (simple) function, the ctypes variant was easier (at least for me, since I don't use swig frequently), but for more complex functions/projects it will become an overkill and switching to swig would be the right thing to do

Output (running with Python3.5 on 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

  • Almost everything from the ctypes section, applies here as well

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

Notes:

  • Here I'm allocating the array and return it (the 2nd option from the beginning)
  • The .i file is a standard swig interface file
    • Defines the module, and its exports (via %include)
    • One thing that is worth mentioning is the %newobject directive that deallocates the pointer returned by exposekey to avoid memory leaks
  • The .h file just contains the function declaration, in order to be included by the .i file (it's not mandatory, but things are more elegant this way)
  • The rest is pretty much the same

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

Notes:

  • Things make much more sense from Python programmer's PoV
  • Code is a lot shorter (that is because swig did some "magic" behind the scenes):
    • The wrapper .c wrapper file generated from the .i file has ~120K
    • The swig_demp.py generated module has ~3K
  • I used the same technique with 2000 0s at the beginning of the string

Output:

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. Plain Python C API

  • I added this part as a personal exercise
  • This is what swig does, but "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);
}

Notes:

  • It requires swig_demo.h and swig_demo.c (not going to duplicate their contents here)
  • It only works with Python3 (actually I got quite some headaches making it work, especially because I was used to PyString_AsString which is no longer present)
  • Error handling is poor
  • test_capi.py is similar to test_swig.py with one (obvious) difference: from swig_demo import exposekey should be replaced by from capi_demo import exposekey
  • The output is also the same to test_swig.py (again, not going to duplicate it here)

这篇关于将str作为int数组传递给Python C扩展函数(使用SWIG扩展)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆