You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python调用C++ DLL时结构体内存重分配引发堆错误的解决咨询

问题描述

我用C++编写了一个DLL,想要在Python中调用。该DLL包含一个带多个字段的结构体,其中一个字段为float*类型。当DLL内的方法修改这个“数组”的大小时,Python会抛出堆错误(heap error)。

目前我已通过ctypes在Python中实现了对应结构体,调用仅修改固定长度字段(如int、float、固定长度char数组等)的DLL方法时一切正常,但只要调用修改float*字段大小的方法就会触发堆错误。

请问有没有解决办法?还是必须保证float*数组在调用DLL方法前就拥有正确长度?


可复现代码示例

C++ 文件

MyCode.h

#pragma once
#include <sal.h>

typedef struct MyStruct
{
    // 数组'A'的数据点数量
    unsigned int    length;

    // 数值字段
    float    value;

    // 数据数组
    float*    A;
}MyStruct;

extern "C" {
    void __cdecl ChangeValue(_Inout_ MyStruct* data, float newValue);
    void __cdecl ResizeDataArray(_Inout_ MyStruct* data, unsigned int newLength);
}

MyCode.cpp

#include <sal.h>
#include "MyCode.h"

extern "C" void __cdecl ChangeValue(_Inout_ MyStruct * data, float newValue)
{
    data->value = newValue;
}

extern "C" void __cdecl ResizeDataArray(_Inout_ MyStruct * data, unsigned int newLength)
{
    if (data == nullptr)
    {
        return;
    }

    // 若目标长度不同则调整数组大小
    if (data->length != newLength)
    {
        if (data->length > 0 && data->A != nullptr)
        {
            delete[] data->A;
            data->length = 0U;
        }

        // 分配内存并初始化为0
        data->A = new float[newLength];
        memset(data->A, 0, newLength * sizeof(float));

        data->length = newLength;
    }
}

StructTool.def

LIBRARY    "StructTool"

EXPORTS
    ChangeValue
    ResizeDataArray

Python 代码

import os
import ctypes as c

class MyStruct(c.Structure):
    _fields_ = [
        ("length", c.c_uint),
        ("value", c.c_float),
        ("A", c.POINTER(c.c_float))
        ]

if __name__ == '__main__':

    dllPath = r'C:\MyFolder'
    dllFullPath = os.path.join(dllPath, 'StructTool.dll')
    if not os.path.exists(dllFullPath):
        print('DLL '+ dllFullPath +' not found')
        exit(1)

    os.chdir(dllPath)
    my_dll = c.CDLL(dllFullPath)  # 加载DLL

    # 创建结构体实例
    testStruct = MyStruct()

    # 分配float数组并初始化结构体
    array_size = 10
    testStruct.A = (c.c_float * array_size)()
    testStruct.length = array_size
    testStruct.value = 0.12345

    print("testStruct.length: ", testStruct.length)
    print("testStruct.value: ", testStruct.value, "\n")

    # 修改value字段 - 运行正常
    my_dll.ChangeSingleValue.argtypes = [c.POINTER(MyStruct), c.c_float]
    newValue = 123.456
    my_dll.ChangeSingleValue(c.byref(testStruct), newValue)
    
    print("testStruct.value: ", testStruct.value, "\n")
    
    # 修改A字段长度 - 触发堆错误!
    my_dll.ResizeDataArray.argtypes = [c.POINTER(MyStruct), c.c_uint]
    newLength = 15
    my_dll.ResizeDataArray(c.byref(testStruct), newLength)    # 此处抛出堆错误

    print("testStruct.length: ", testStruct.length)

解决方案

堆错误的核心原因是跨堆内存操作:

  • Python通过(c.c_float * array_size)()分配的内存属于Python进程的堆(由Python关联的C运行时库管理);
  • DLL中用delete[]释放这块内存时,是用DLL自身的C运行时堆管理器去操作不属于它的内存,直接导致堆损坏。

方法一:让DLL全权负责内存生命周期(推荐)

修改Python代码,初始化时不手动分配数组,而是通过DLL的函数完成内存分配;同时新增DLL函数负责内存释放,避免跨堆操作。

调整后的Python初始化代码

# 初始化结构体时,不要手动分配A
testStruct = MyStruct()
testStruct.length = 0
testStruct.value = 0.12345
testStruct.A = None  # 初始化为空指针

# 调用DLL函数分配初始大小的数组
my_dll.ResizeDataArray.argtypes = [c.POINTER(MyStruct), c.c_uint]
initial_size = 10
my_dll.ResizeDataArray(c.byref(testStruct), initial_size)

DLL新增内存释放函数

在MyCode.h中添加声明:

extern "C" {
    // ... 原有函数声明
    void __cdecl FreeDataArray(_Inout_ MyStruct* data);
}

在MyCode.cpp中实现:

extern "C" void __cdecl FreeDataArray(_Inout_ MyStruct * data)
{
    if (data != nullptr && data->A != nullptr)
    {
        delete[] data->A;
        data->A = nullptr;
        data->length = 0U;
    }
}

Python中调用释放函数

my_dll.FreeDataArray.argtypes = [c.POINTER(MyStruct)]
my_dll.FreeDataArray(c.byref(testStruct))

方法二:统一内存分配器(兼容性差)

确保Python和DLL使用完全相同版本的动态链接C运行时库(如均用/MD编译选项),让两者共享同一个堆管理器。但该方法依赖运行环境的一致性,一旦运行时版本匹配失败就会再次出现错误,不推荐用于生产环境。


内容的提问来源于stack exchange,提问作者Ace76

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 18:14:58