ctypes 为堆栈分配更多内存
ctypes allocate more memory to the stack
我有一个从 python 调用的 c-dll。 dll 的输出非常大,我怀疑这会导致错误
OSError: exception: stack overflow
我很确定问题出在输出的大小上(大约 4x25x720 双倍大小)。减小输出的大小(我不想这样做)会使错误消失。
在 C# 中,我可以通过为调用线程分配更多内存来解决这个问题,即
thread = new Thread(() => calculate(ptr_in, ptr_out), 20000000);
是否可以用 ctypes
做类似的事情?
这不是这里发布的问题 Ctypes: OSError: exception: stack overflow。
编辑
反思问题,我认为问题不是输出的大小,而是实际dll本身需要的space。即 c_out inner_out
在 ctypes_test.c 中定义。不管怎样,问题还是一样。
在C中我定义了一个测试dll dll_ctypes_test
ctypes_testT.h
#pragma once
#define N_ELEMENTS 1000
#define N_ARRAYS 50
typedef struct
{
double var_0;
double var_1;
double var_2;
double var_3;
double var_4;
double var_5;
double var_6;
double var_7;
double var_8;
double var_9;
} element;
typedef struct
{
int n_elements;
element elements[N_ELEMENTS];
} arr;
typedef struct
{
int n_arrays;
arr arrays[N_ARRAYS];
} c_out;
ctypes_test.c
#include "ctypes_testT.h"
__declspec(dllexport) void _stdcall dll_ctypes_test(double in, c_out *out)
{
c_out inner_out;
//Some caluclations on inner_out
//Wrap values of inner arr to out
}
和 Python 代码
import ctypes
N_ELEMENTS = 1000
N_ARRAYS = 50
class element(ctypes.Structure):
_fields_ = [('var_0', ctypes.c_double),
('var_1', ctypes.c_double),
('var_2', ctypes.c_double),
('var_3', ctypes.c_double),
('var_4', ctypes.c_double),
('var_5', ctypes.c_double),
('var_6', ctypes.c_double),
('var_7', ctypes.c_double),
('var_8', ctypes.c_double),
('var_9', ctypes.c_double)]
class arr(ctypes.Structure):
_fields_ = [('n_elements', ctypes.c_int),
('elements', element * N_ELEMENTS)]
class c_out(ctypes.Structure):
_fields_ = [('n_arrays', ctypes.c_int),
('arrays', arr * N_ARRAYS)]
dll = ctypes.WinDLL(r'C:\repos\ctypes_test\x64\Debug\ctypes_test.dll')
dll.dll_ctypes_test.argtypes = [ctypes.c_double, ctypes.POINTER(c_out)]
dll.dll_ctypes_test.restype = None
dll.dll_ctypes_test(5, ctypes.byref(c_out()))
调用 Python 代码生成
Traceback (most recent call last):
File "<ipython-input-15-7c8b287888d0>", line 1, in <module>
dll.dll_ctypes_test(5, c_out())
OSError: exception: access violation writing 0x00000062BA400000
如果我将 N_ARRAYS
从 50
更改为 10
。错误消失了。
清单[Python.Docs]: ctypes - A foreign function library for Python.
我必须说,我无法使用“常规”Python 或 IPython。也许 dll_ctypes_test 的实现远比表面看起来的要复杂。
当前问题:
- dll_ctypes_test 期望 c_out 指针 ,但是你正在传递一个普通的 c_out 实例。您应该使用 ctypes.byref(或 ctypes.pointer)。不知道为什么 CTypes 不会因此而抱怨
- C 和 Python 结构定义不匹配。一个例子是 arr,它在 C[=123= 中包含一个 element array ],以及中的元素指针数组(ctypes.POINTER) Python。这是未定义的行为,2 必须同步
- 您将导出函数标记为 __stdcall,但您加载 .dll 时 CDLL。您应该使用 WinDLL。但是因为你在 064bit(基于你的路径),这并没有太大的区别
下面是一个示例(您的代码的修改版本)。
dll00.h:
#pragma once
#if defined(_WIN32)
# if defined DLL00_EXPORTS
# define DLL00_EXPORT_API __declspec(dllexport)
# else
# define DLL00_EXPORT_API __declspec(dllimport)
# endif
#else
# define DLL00_EXPORT_API
#endif
#define ELEMENT_COUNT 1000
#define ARRAY_COUNT 50
typedef struct {
double var0, var1, var2, var3, var4,
var5, var6, var7, var8, var9;
} Element;
typedef struct {
int size;
Element data[ELEMENT_COUNT];
} Array1D;
typedef struct {
int size;
Array1D data[ARRAY_COUNT];
} Array2D;
#if defined(__cplusplus)
extern "C" {
#endif
DLL00_EXPORT_API void __stdcall dll00Func00(double in, Array2D *pOut);
#if defined(__cplusplus)
}
#endif
dll00.c:
#define DLL00_EXPORTS
#include "dll00.h"
#include <stdio.h>
void dll00Func00(double in, Array2D *pOut) {
if (pOut == NULL) {
printf("From C - NULL array passed\n");
return;
};
Array2D arr2d;
printf("From C - Outer array size: %d\n", pOut->size);
}
code00.py:
#!/usr/bin/env python
import ctypes as ct
import sys
ELEMENT_COUNT = 1000
ARRAY_COUNT = 50
class Element(ct.Structure):
_fields_ = list(("var{0:d}".format(i), ct.c_double) for i in range(10))
class Array1D(ct.Structure):
_fields_ = [
("size", ct.c_int),
("data", Element * ELEMENT_COUNT),
]
class Array2D(ct.Structure):
_fields_ = [
("size", ct.c_int),
("data", Array1D * ARRAY_COUNT),
]
DLL_NAME = "./dll00.{:s}".format("dll" if sys.platform[:3].lower() == "win" else "so")
def main(*argv):
dll00 = ct.WinDLL(DLL_NAME)
dll00Func00 = dll00.dll00Func00
dll00Func00.argtypes = (ct.c_double, ct.POINTER(Array2D))
#dll00Func00.argtypes = (ct.c_double, Array2D) # !!! Defining the 2nd argument without POINTER, triggers the error !!!
mat = Array2D()
mat.size = 7
#print(ct.sizeof(Element), Element._fields_, dir(Element))
print("Array sizeof: {0:d} (0x{1:08X})".format(ct.sizeof(mat), ct.sizeof(mat)))
dll00Func00(5, ct.byref(mat))
#dll00Func00(5, mat)
if __name__ == "__main__":
print("Python {:s} {:03d}bit on {:s}\n".format(" ".join(elem.strip() for elem in sys.version.split("\n")),
64 if sys.maxsize > 0x100000000 else 32, sys.platform))
rc = main(*sys.argv[1:])
print("\nDone.")
sys.exit(rc)
输出:
e:\Work\Dev\Whosebug\q060297181>sopr.bat
### Set shorter prompt to better fit when pasted in Whosebug (or other) pages ###
[prompt]> "c:\Install\pc032\Microsoft\VisualStudioCommunity17\VC\Auxiliary\Build\vcvarsall.bat" x64 > nul
[prompt]> dir /b
code00.py
dll00.h
dll00.c
[prompt]> cl /nologo /MD /DDLL dll00.c /link /NOLOGO /DLL /OUT:dll00.dll
dll00.c
Creating library dll00.lib and object dll00.exp
[prompt]> dir /b
code00.py
dll00.h
dll00.c
dll00.dll
dll00.exp
dll00.lib
dll00.obj
[prompt]>
[prompt]> "e:\Work\Dev\VEnvs\py_pc064_03.07_test0\Scripts\python.exe" code00.py
Python 3.7.6 (tags/v3.7.6:43364a7ae0, Dec 19 2019, 00:42:30) [MSC v.1916 64 bit (AMD64)] 064bit on win32
Array sizeof: 4000408 (0x003D0A98)
From C - Outer array size: 7
Done.
在调用函数或方法时(也有一些例外,但这里无关紧要),栈(一种特殊的内存区域)用于存储。存储的常见内容:
- 参数(和 return 值),表示 caller 和 callee[=134= 之间交换的数据]
- 局部变量(在callee中),既不是静态的,也不是在堆上显式分配的(通过malloc,新, ...)
- 其他数据(程序员不可见,如指令指针, ...)
不出所料,堆栈是有限的,所以它可以存储最大量的数据。当需要存储的数据量超过最大值时,会出现Stack Overflow(最常见的情况是在递归时,需要存储的循环调用过多时)很多数据)。
最大堆栈大小由每个应用程序构建选项决定,默认值因编译器而异,OS、等 .来自[MS.Docs]: /STACK (Stack Allocations)(重点是我的):
The reserve value specifies the total stack allocation in virtual memory. For ARM, x86 and x64 machines, the default stack size is 1 MB.
可以在 [MS.Docs]: /F (Set Stack Size) 找到相同的信息。
正如所见,Array2D 几乎需要 4 MiB(所以它不适合如果/当试图存储在堆栈中时)。
正如我在评论中指定的那样(来自code00.py),定义dll00Func00的2 nd argtype without ct.POINTER 触发错误。也许你的代码中有这样的错字 运行?
无论如何,一些避免此错误的一般准则:
- 避免按值传递(作为参数/return 类型)大量数据(最后 2 个子点也适用于局部定义的变量):
- 使用指针(或 C++ 中的引用)
- 动态分配它们(在堆上)
- 使它们成为静态的(不太理想,因为静态数据段也受到限制)
- 确保递归不会太深(如果适用)
- 构建应用程序时增加(“手动”)堆栈大小
顺便说一句,我认为在 C 下 Win 中增加(修改)当前进程堆栈大小是不可能的](Visual C)。然而,在 C# 和 Nix 中是可能的(setrlimit)。
我有一个从 python 调用的 c-dll。 dll 的输出非常大,我怀疑这会导致错误
OSError: exception: stack overflow
我很确定问题出在输出的大小上(大约 4x25x720 双倍大小)。减小输出的大小(我不想这样做)会使错误消失。
在 C# 中,我可以通过为调用线程分配更多内存来解决这个问题,即
thread = new Thread(() => calculate(ptr_in, ptr_out), 20000000);
是否可以用 ctypes
做类似的事情?
这不是这里发布的问题 Ctypes: OSError: exception: stack overflow。
编辑
反思问题,我认为问题不是输出的大小,而是实际dll本身需要的space。即 c_out inner_out
在 ctypes_test.c 中定义。不管怎样,问题还是一样。
在C中我定义了一个测试dll dll_ctypes_test
ctypes_testT.h
#pragma once
#define N_ELEMENTS 1000
#define N_ARRAYS 50
typedef struct
{
double var_0;
double var_1;
double var_2;
double var_3;
double var_4;
double var_5;
double var_6;
double var_7;
double var_8;
double var_9;
} element;
typedef struct
{
int n_elements;
element elements[N_ELEMENTS];
} arr;
typedef struct
{
int n_arrays;
arr arrays[N_ARRAYS];
} c_out;
ctypes_test.c
#include "ctypes_testT.h"
__declspec(dllexport) void _stdcall dll_ctypes_test(double in, c_out *out)
{
c_out inner_out;
//Some caluclations on inner_out
//Wrap values of inner arr to out
}
和 Python 代码
import ctypes
N_ELEMENTS = 1000
N_ARRAYS = 50
class element(ctypes.Structure):
_fields_ = [('var_0', ctypes.c_double),
('var_1', ctypes.c_double),
('var_2', ctypes.c_double),
('var_3', ctypes.c_double),
('var_4', ctypes.c_double),
('var_5', ctypes.c_double),
('var_6', ctypes.c_double),
('var_7', ctypes.c_double),
('var_8', ctypes.c_double),
('var_9', ctypes.c_double)]
class arr(ctypes.Structure):
_fields_ = [('n_elements', ctypes.c_int),
('elements', element * N_ELEMENTS)]
class c_out(ctypes.Structure):
_fields_ = [('n_arrays', ctypes.c_int),
('arrays', arr * N_ARRAYS)]
dll = ctypes.WinDLL(r'C:\repos\ctypes_test\x64\Debug\ctypes_test.dll')
dll.dll_ctypes_test.argtypes = [ctypes.c_double, ctypes.POINTER(c_out)]
dll.dll_ctypes_test.restype = None
dll.dll_ctypes_test(5, ctypes.byref(c_out()))
调用 Python 代码生成
Traceback (most recent call last):
File "<ipython-input-15-7c8b287888d0>", line 1, in <module>
dll.dll_ctypes_test(5, c_out())
OSError: exception: access violation writing 0x00000062BA400000
如果我将 N_ARRAYS
从 50
更改为 10
。错误消失了。
清单[Python.Docs]: ctypes - A foreign function library for Python.
我必须说,我无法使用“常规”Python 或 IPython。也许 dll_ctypes_test 的实现远比表面看起来的要复杂。
当前问题:
- dll_ctypes_test 期望 c_out 指针 ,但是你正在传递一个普通的 c_out 实例。您应该使用 ctypes.byref(或 ctypes.pointer)。不知道为什么 CTypes 不会因此而抱怨
- C 和 Python 结构定义不匹配。一个例子是 arr,它在 C[=123= 中包含一个 element array ],以及中的元素指针数组(ctypes.POINTER) Python。这是未定义的行为,2 必须同步
- 您将导出函数标记为 __stdcall,但您加载 .dll 时 CDLL。您应该使用 WinDLL。但是因为你在 064bit(基于你的路径),这并没有太大的区别
下面是一个示例(您的代码的修改版本)。
dll00.h:
#pragma once
#if defined(_WIN32)
# if defined DLL00_EXPORTS
# define DLL00_EXPORT_API __declspec(dllexport)
# else
# define DLL00_EXPORT_API __declspec(dllimport)
# endif
#else
# define DLL00_EXPORT_API
#endif
#define ELEMENT_COUNT 1000
#define ARRAY_COUNT 50
typedef struct {
double var0, var1, var2, var3, var4,
var5, var6, var7, var8, var9;
} Element;
typedef struct {
int size;
Element data[ELEMENT_COUNT];
} Array1D;
typedef struct {
int size;
Array1D data[ARRAY_COUNT];
} Array2D;
#if defined(__cplusplus)
extern "C" {
#endif
DLL00_EXPORT_API void __stdcall dll00Func00(double in, Array2D *pOut);
#if defined(__cplusplus)
}
#endif
dll00.c:
#define DLL00_EXPORTS
#include "dll00.h"
#include <stdio.h>
void dll00Func00(double in, Array2D *pOut) {
if (pOut == NULL) {
printf("From C - NULL array passed\n");
return;
};
Array2D arr2d;
printf("From C - Outer array size: %d\n", pOut->size);
}
code00.py:
#!/usr/bin/env python
import ctypes as ct
import sys
ELEMENT_COUNT = 1000
ARRAY_COUNT = 50
class Element(ct.Structure):
_fields_ = list(("var{0:d}".format(i), ct.c_double) for i in range(10))
class Array1D(ct.Structure):
_fields_ = [
("size", ct.c_int),
("data", Element * ELEMENT_COUNT),
]
class Array2D(ct.Structure):
_fields_ = [
("size", ct.c_int),
("data", Array1D * ARRAY_COUNT),
]
DLL_NAME = "./dll00.{:s}".format("dll" if sys.platform[:3].lower() == "win" else "so")
def main(*argv):
dll00 = ct.WinDLL(DLL_NAME)
dll00Func00 = dll00.dll00Func00
dll00Func00.argtypes = (ct.c_double, ct.POINTER(Array2D))
#dll00Func00.argtypes = (ct.c_double, Array2D) # !!! Defining the 2nd argument without POINTER, triggers the error !!!
mat = Array2D()
mat.size = 7
#print(ct.sizeof(Element), Element._fields_, dir(Element))
print("Array sizeof: {0:d} (0x{1:08X})".format(ct.sizeof(mat), ct.sizeof(mat)))
dll00Func00(5, ct.byref(mat))
#dll00Func00(5, mat)
if __name__ == "__main__":
print("Python {:s} {:03d}bit on {:s}\n".format(" ".join(elem.strip() for elem in sys.version.split("\n")),
64 if sys.maxsize > 0x100000000 else 32, sys.platform))
rc = main(*sys.argv[1:])
print("\nDone.")
sys.exit(rc)
输出:
e:\Work\Dev\Whosebug\q060297181>sopr.bat ### Set shorter prompt to better fit when pasted in Whosebug (or other) pages ### [prompt]> "c:\Install\pc032\Microsoft\VisualStudioCommunity17\VC\Auxiliary\Build\vcvarsall.bat" x64 > nul [prompt]> dir /b code00.py dll00.h dll00.c [prompt]> cl /nologo /MD /DDLL dll00.c /link /NOLOGO /DLL /OUT:dll00.dll dll00.c Creating library dll00.lib and object dll00.exp [prompt]> dir /b code00.py dll00.h dll00.c dll00.dll dll00.exp dll00.lib dll00.obj [prompt]> [prompt]> "e:\Work\Dev\VEnvs\py_pc064_03.07_test0\Scripts\python.exe" code00.py Python 3.7.6 (tags/v3.7.6:43364a7ae0, Dec 19 2019, 00:42:30) [MSC v.1916 64 bit (AMD64)] 064bit on win32 Array sizeof: 4000408 (0x003D0A98) From C - Outer array size: 7 Done.
在调用函数或方法时(也有一些例外,但这里无关紧要),栈(一种特殊的内存区域)用于存储。存储的常见内容:
- 参数(和 return 值),表示 caller 和 callee[=134= 之间交换的数据]
- 局部变量(在callee中),既不是静态的,也不是在堆上显式分配的(通过malloc,新, ...)
- 其他数据(程序员不可见,如指令指针, ...)
不出所料,堆栈是有限的,所以它可以存储最大量的数据。当需要存储的数据量超过最大值时,会出现Stack Overflow(最常见的情况是在递归时,需要存储的循环调用过多时)很多数据)。
最大堆栈大小由每个应用程序构建选项决定,默认值因编译器而异,OS、等 .来自[MS.Docs]: /STACK (Stack Allocations)(重点是我的):
The reserve value specifies the total stack allocation in virtual memory. For ARM, x86 and x64 machines, the default stack size is 1 MB.
可以在 [MS.Docs]: /F (Set Stack Size) 找到相同的信息。
正如所见,Array2D 几乎需要 4 MiB(所以它不适合如果/当试图存储在堆栈中时)。
正如我在评论中指定的那样(来自code00.py),定义dll00Func00的2 nd argtype without ct.POINTER 触发错误。也许你的代码中有这样的错字 运行?
无论如何,一些避免此错误的一般准则:
- 避免按值传递(作为参数/return 类型)大量数据(最后 2 个子点也适用于局部定义的变量):
- 使用指针(或 C++ 中的引用)
- 动态分配它们(在堆上)
- 使它们成为静态的(不太理想,因为静态数据段也受到限制)
- 确保递归不会太深(如果适用)
- 构建应用程序时增加(“手动”)堆栈大小
顺便说一句,我认为在 C 下 Win 中增加(修改)当前进程堆栈大小是不可能的](Visual C)。然而,在 C# 和 Nix 中是可能的(setrlimit)。