ctypes выделяют больше памяти стеку - PullRequest
0 голосов
/ 19 февраля 2020

У меня есть c -dll, которую я звоню с python. Вывод из dll довольно большой, и я подозреваю, что это вызывает ошибку

OSError: exception: stack overflow

Я почти уверен, что проблема заключается в размере вывода (примерно 4x25x720 удваивается). Уменьшение размера вывода (что я не хочу делать) устраняет ошибку go.

В C# Я могу обойти эту проблему, выделив больше памяти вызывающему потоку, т. Е.

thread = new Thread(() => calculate(ptr_in, ptr_out), 20000000); 

Возможно ли сделать что-то подобное с ctypes?

Это НЕ проблема, опубликованная здесь Ctypes: OSError: исключение: переполнение стека .


РЕДАКТИРОВАТЬ

Размышляя над проблемой, я не думаю, что проблема заключается в размере вывода, а скорее в пространстве, необходимом для самой DLL. Т.е. 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. Ошибка исчезает.

1 Ответ

1 голос
/ 23 февраля 2020

Листинг [Python 3.Docs]: ctypes - библиотека сторонних функций для Python.

Я должен сказать, что не смог воспроизвести поведение ( даже без исправления ошибок ниже), используя либо «обычный» Python, либо I Python. Возможно, в реализации dll_ctypes_test больше, чем кажется на первый взгляд.

Текущие проблемы:

  1. dll_ctypes_test ожидает c_out указатель , но вы передаете обычный c_out экземпляр . Вы должны использовать ctypes.byref (или ctypes.pointer ). Не знаю, почему CTypes не жалуется из-за этого
  2. C и Python определения структур не совпадают , Одним из примеров является arr , который содержит элемент массив in C и элемент массив указателей ( ctypes.POINTER ) в Python. Это Неопределенное поведение , значение 2 должно быть синхронизировано c
  3. Вы пометили функцию экспорта как __stdcall , но вы загружаете . DLL с CDLL . Вы должны использовать WinDLL . Но поскольку вы используете 64bit (в зависимости от ваших путей), это не имеет большого значения

Ниже приведен пример (модифицированная версия вашего кода ).

dll00.h :

#pragma once

#if defined(_WIN32)
#  if defined DLL0_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 DLL0_EXPORTS
#include "dll0.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 sys
import ctypes as ct


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),
    ]


DLL0_NAME = "./dll00.dll"


def main(*argv):
    dll0 = ct.WinDLL(DLL0_NAME)
    dll00Func00 = dll0.dll00Func00
    dll00Func00.argtypes = [ct.c_double, ct.POINTER(Array2D)]
    #dll00Func00.argtypes = [ct.c_double, Array2D]  # !!! Defining the 2nd argument whitout POINTER, triggers the error !!!


    mat = Array2D()
    mat.size = 7
    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 {0:s} {1:d}bit on {2:s}\n".format(" ".join(item.strip() for item in sys.version.split("\n")), 64 if sys.maxsize > 0x100000000 else 32, sys.platform))
    main(*sys.argv[1:])
    print("\nDone.")

Вывод :

e:\Work\Dev\StackOverflow\q060297181>sopr.bat
*** Set shorter prompt to better fit when pasted in StackOverflow (or other) pages ***

[prompt]> "c:\Install\pc032\Microsoft\VisualStudioCommunity\2017\VC\Auxiliary\Build\vcvarsall.bat" x64
**********************************************************************
** Visual Studio 2017 Developer Command Prompt v15.9.20
** Copyright (c) 2017 Microsoft Corporation
**********************************************************************
[vcvarsall.bat] Environment initialized for: 'x64'

[prompt]> dir /b
code00.py
dll0.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
dll0.h
dll00.c
dll00.dll
dll00.exp
dll00.lib
dll00.obj

[prompt]>
[prompt]> "e:\Work\Dev\VEnvs\py_pc064_03.07.06_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)] 64bit on win32

Array sizeof: 4000408 (0x003D0A98)
From C - Outer array size: 7

Done.

При вызове функции или метода (есть некоторые исключения, но они не имеют значения здесь), стек (специальная область памяти) используется для хранения. Сохраняемые общие данные:

  • Аргументы (и возвращаемое значение), которые представляют данные, которыми обмениваются вызывающий и вызываемый
  • Local переменные (в callee ), которые не являются ни stati c, ни явно размещенными в куче (через mallo c, new , .. .)
  • Другие данные (невидимые для программиста, такие как Указатель инструкций , ...)

Как и ожидалось, стек ограничен, поэтому он может хранить максимальное количество данных. Когда объем данных, которые необходимо сохранить, превышает максимальный, происходит переполнение стека (наиболее распространенный сценарий возникновения - во время рекурсии, когда слишком много повторяющихся вызовов, которым необходимо хранить слишком много данных).

Максимальный размер стека определяется параметрами сборки каждого приложения, а значения по умолчанию варьируются в зависимости от компилятора, OS , et c. Начиная с [MS.Docs]: / STACK (распределение стека) ( выделение принадлежит мне):

Резервное значение указывает общее распределение стека в виртуальной среде. Память. Для компьютеров ARM, x86 и x64 размер стека по умолчанию составляет 1 МБ .

Та же информация находится в [MS.Docs]: / F (Установить Размер стека) .

Как видно, Array2D занимает почти 4 МиБ (поэтому он не подходит, если / при попытке храниться в стеке).
Как я указал в комментарии (от code00.py ), определение dll00Func00 2 nd argtype без ct.POINTER вызывает ошибку. Может быть, в коде, который вы фактически запускаете, есть такая опечатка?

В любом случае, некоторые общие рекомендации, чтобы избежать этой ошибки:

  • Избегайте передачи (в качестве аргументов / возвращаемого типа) больших сумм данных по значению (последние 2 подпункта также применяются к локально определенным переменным):
    • Использовать указатели (или ссылки в C ++ )
    • Распределять их динамически (в куче) )
    • Сделать их stati c (менее желательно, так как stati c сегмент данных также ограничен)
  • Убедитесь, что рекурсия не go слишком глубокая (где применимо)
  • Увеличение ("вручную") размера стека при создании приложения

В качестве дополнительного замечания я не думаю, что возможно увеличить (изменить) текущий размер стека процесса в C при Win ( Visual C). Однако это возможно в C#, а также в Nix ( setrlimit ).

...