关于我的评论,这里有一些关于从函数返回数组的更多细节:[SO]: Returning an array using C。简而言之:处理此问题的方法:
- 使返回的变量静态
- 动态分配它(使用 malloc (family) 或 new)
- 把它变成函数的附加参数
有两种方法可以让 C 代码在 Python 解释器中运行:
由于他们都在做同样的事情,将它们混合在一起是没有意义的。因此,请选择最适合您的需求。
1。 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,我想提一下,运行您的代码并没有引发您遇到的错误
- 指定函数的 argtypes 和 restype 是强制性的,也使事情变得更容易(在 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。 痛饮
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.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相同(再次,这里不再重复)