使用ctypes调用C DLL遇栈溢出,能否像C#一样分配线程栈内存?
解决ctypes调用C DLL时的栈溢出问题
首先得明确问题根源:你的C函数里在栈上声明了c_out inner_out,这个结构体的实际大小算下来接近4MB(50个数组×每个数组1000个元素×每个元素10个double),而Windows默认线程栈大小仅1MB左右——Python主线程用的就是这个默认栈,直接调用自然触发栈溢出。C#里你能解决是因为创建了自定义栈大小的新线程,Python里也有两种可行方案:
方案一:修改C代码(推荐)
最稳妥的方式是把栈上的大结构体改成堆分配,彻底摆脱线程栈大小的限制,跨平台兼容性也更好。修改你的C实现代码如下:
#include "ctypes_testT.h" #include <stdlib.h> // 引入malloc/free __declspec(dllexport) void _stdcall dll_ctypes_test(double in, c_out *out) { // 在堆上分配内存,避免占用线程栈 c_out *inner_out = (c_out*)malloc(sizeof(c_out)); if (inner_out == NULL) { // 处理内存分配失败的异常情况 return; } // 执行你的计算逻辑,操作inner_out->xxx inner_out->n_arrays = N_ARRAYS; for (int i = 0; i < N_ARRAYS; i++) { inner_out->arrays[i].n_elements = N_ELEMENTS; // 填充元素数据... } // 将计算结果复制到输出参数 *out = *inner_out; // 释放堆内存,避免泄漏 free(inner_out); }
方案二:在Python中创建大栈线程调用
如果无法修改C代码,可以在Python里创建一个带自定义栈大小的线程来执行DLL调用。Python的threading.Thread没有直接设置栈大小的参数,但底层的_thread模块的start_new_thread函数支持指定stacksize参数,示例代码如下:
import ctypes import _thread from threading import Event 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 # 准备输出数据结构 output = c_out() # 创建事件用于线程同步 done_event = Event() def call_dll(in_val, out_ptr, event): try: dll.dll_ctypes_test(in_val, out_ptr) finally: event.set() # 创建新线程,指定栈大小为20MB(和你C#里的设置一致) _thread.start_new_thread(call_dll, (5, ctypes.byref(output), done_event), stacksize=20*1024*1024) # 等待线程执行完成 done_event.wait() # 现在可以正常使用output中的数据 print(f"处理完成,共收到 {output.n_arrays} 个数组")
注意:stacksize参数单位是字节,该设置仅在Windows平台有效,Linux等其他平台可能受系统配置限制;另外_thread属于底层模块,使用时需注意线程安全问题。
总结来说,方案一从根源上解决了栈依赖问题,是更优选择;方案二适合无法修改C代码的临时场景。
内容的提问来源于stack exchange,提问作者mortysporty




