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_outctypes_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_ARRAYS50 更改为 10。错误消失了。

清单[Python.Docs]: ctypes - A foreign function library for Python.

我必须说,我无法使用“常规”PythonIPython。也许 dll_ctypes_test 的实现远比表面看起来的要复杂。

当前问题:

  1. dll_ctypes_test 期望 c_out 指针 ,但是你正在传递一个普通的 c_out 实例。您应该使用 ctypes.byref(或 ctypes.pointer)。不知道为什么 CTypes 不会因此而抱怨
  2. CPython 结构定义不匹配。一个例子是 arr,它在 C[=123= 中包含一个 element array ],以及中的元素指针数组ctypes.POINTER) Python。这是未定义的行为,2 必须同步
  3. 您将导出函数标记为 __stdcall,但您加载 .dllCDLL。您应该使用 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 值),表示 callercallee[=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++ 中的引用)
    • 动态分配它们(在堆上)
    • 使它们成为静态的(不太理想,因为静态数据段也受到限制)
  • 确保递归不会太深(如果适用)
  • 构建应用程序时增加(“手动”)堆栈大小

顺便说一句,我认为在 CWin 中增加(修改)当前进程堆栈大小是不可能的](Visual C)。然而,在 C#Nix 中是可能的(setrlimit)。