[转载]有关DLL中New和外部Delete以以及跨DLL传递对象的若干问题

本文探讨了DLL中内存管理的问题,特别是在不同模块间new和delete对象时出现的异常。提出了通过虚函数free来安全释放资源的方法,并讨论了DLL接口设计原则,包括如何合理划分模块及使用Facade模式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Q:   
    我们需要在Dll中new一个class或者struct的实例,通过接口把这个实例传给了主程序(调用dll的程序),最后这个实例的释放(delete)是由主程序来执行的,这时我们的问题出现了,vs.net抛出一个debug模式下的异常,是在delete函数中。后来我试了另外一种情况,即在主程序中new的实例在dll中delete也会有相同的问题。哪位大侠知道为什么?


    另外,还有一个小问题,就是在dll中的导出(dllexport)类中,如果有stl的变量存在如list<int>等,在编译的时候会有一个有关dll 接口不一致的warning,不知道怎么解决这个问题。

A:
    不同的DLL中完全可能会维护不同的局部堆,这与编译器的实现有关。有些编译器可以选择使用进程共享的局部堆,但性能会受影响。

    如果是不同的局部堆,当你在DLL中new时,是在DLL的堆中分配的;而当你在EXE中delete时,EXE会认为它是在EXE的局部堆中分配的,从而用EXE的堆信息去释放它,从而可能导致错误。具体情况与堆管理代码的实现有关。

    最好为你的class或struct提供一个虚的free方法,在里面调用delete。不管是从EXE中分配的还是从DLL中分配的,都可以安全地用free方法释放,因为free是虚的,它的代码会和分配它的代码编译在同一个可执行体中。

    我不赞成用引出类的方法提供功能,因为类成员的签名生成并没有一个标准,很难使用。

    用DLL提供的类最好是只引出创建对象的函数,返回类型是一个“纯抽象类”指针,我说的“纯抽象类”指没有成员变量,没有非抽象方法的类。并且有一个公开的抽象的free方法负责释放。使用时调用创建函数创建一个(衍生类) 的对象并返回,然后调用对象的成员方法来完成操作。

----------------------------------------------------------------------------
Q:
   这边还有一个小问题,如果为每一个导出的class都做一个wrap,如果导出的类很多不是开销很大?如果有些类是比较简单的,这样也需要为每个类做一个接口吗?

A:
    这里面有一个模块划分的问题。对于需要跨模块调用的功能,最好是分处于不同层次的代码,相互之间不要有很多共用的对象。有时不同层次有相似的对象,但它们只是相似而已,并不是同一类型的对象,因为它们处于不同层次。而对于某一独立模块,引出的对象类型应该很少,用Facade模式封装起来是个不错的主意。

    C++中的类要分成两种,一种是对基本数据类型的扩展,比如大家经常用到的std::string等,特点是值语义、无多态操作等,这种类没有必要跨模块共享;另一种是逻辑对象的分类,它们是否要共享要根据对象的职责来确定,有些是模块内部使用的,有些则是负责与其它模板交互的,只有后者才是应该共享的。

    此外,共享使用的对象内部使用的对象是不需要这个接口的——如果在模块内部它本身就不需要的话。

------------------------------------------------------------------------------
Q:
   因为我现在存在这样一种困惑,一般我们都会有一些平常比较常用的fucntion,class,这些都会代码都会跨项目的被使用到,所以我把这些代码做成dll,方便以后的需要。基于这样一个目的,我们去构架这个dll,会发现其中有很多简单明了的类,比如一个CVector3f,一个 CMatrix44,或者一个类似std::string这样的类,那么这些类的导出该以怎样一种规则来制定呢?

A:
    我觉得Dll不应该作为简单的代码重用手段,而应该作为逻辑重用手段。也就是说,用一个DLL维护一组相关的逻辑。事实上,与其说它是代码重用手段,还不如说是代码组织手段更合适。

    象vector、matrix等这些只是用来封装类似于“基本数据类型扩展”的类的代码,不应该用DLL去重用,而应该是以源代码或是静态库方式重用。此外,如果你要重用的是模板,那么就只有源代码一种重用方法了,因为模板只有实例化之后才能产生可以加入到静态库或DLL中去的代码。

    当你使用DLL调用进行重用时,你就不要去考虑多态的代价了,因为DLL调用本身就已经有代价了。

<think>嗯,用户问的是为什么在C++中DLL进行newdelete会出问题。这个问题我之前在学习动态链接库的时候遇到过,但具体原因可能需要深入分析。首先,我需要回忆一下C++的内存管理机制。newdelete是C++中用于动态内存分配释放的操作符,它们会调用构造函数析构函数。 然后,DLL问题DLL是动态链接库,不同的DLL可能有自己的堆管理。假设一个DLL中的new分配内存,而另一个DLL中的delete去释放,这时候可能因为堆不同而导致错误。比如,每个DLL可能有自己独立的堆,如果在不同的堆上分配释放,就会导致内存错误。 另外,C++的标准库实现可能也是一个因素。不同的DLL如果使用不同版本的运行时库(比如MSVC的不同版本),它们的newdelete实现可能不同,导致不兼容。例如,一个DLL用VC++2015的运行时分配内存,另一个用VC++2019的运行时释放,这时候可能出问题。 还有一种情况是,如果DLL调用它的程序在编译时的设置不同,比如一个是Debug模式,另一个是Release模式,这样可能导致内存管理的方式不同,进而引发错误。Debug版本可能会有额外的内存跟踪信息,而Release版本没有,这样在释放时结构不一致,导致崩溃。 还可能涉及到导出函数的问题。如果在一个DLL中导出了一个类,而这个类在另一个DLL中被删除,这时候如果类的析构函数没有被正确导出或处理,也会有问题。比如,如果析构函数不在DLL的导出符号中,调用delete时可能找不到正确的析构函数实现,导致未定义行为。 另外,DLL边界传递对象时,如果对象的所有权不明确,也容易导致问题。比如,DLL A创建了一个对象并返回指针给DLL B,DLL B在不知道如何正确释放的情况下调用delete,这时可能出错。 解决方法的话,可能需要确保内存的分配释放在同一模块中进行。比如,DLL提供创建销毁对象的函数,内部使用同一个堆。或者使用智能指针,指定正确的删除器。另外,确保所有模块使用相同版本的运行时库,同样的编译设置,比如Debug/Release,以及使用相同的编译器,这样可以减少兼容性问题。 不过,可能还有一些其他情况,比如静态变量、线程局部存储等是否会影响堆的管理。或者操作系统层面的堆管理策略,比如Windows中不同的堆分配器。这些都是可能的因素。 总结起来,DLLnewdelete问题主要是因为不同模块可能使用不同的堆或运行时库实现,导致内存管理不兼容。解决方法包括统一内存管理接口,使用相同的编译器设置,或者避免模块的内存操作。</think>在C++中,DLL(动态链接库)进行`new``delete`操作可能导致内存管理问题,根本原因在于**内存分配释放的堆不一致**,以及**运行时库实现的差异**。以下是具体原因解决方案的分析: -- ### **1. 堆的独立性** 每个DLL可能有**独立的堆内存管理器**: - 如果一个对象DLL A中通过`new`分配内存(使用DLL A的堆),但在DLL B中通过`delete`释放(使用DLL B的堆),两个堆的管理方式不同,会导致**未定义行为**(如崩溃或内存泄漏)。 - 类似地,Windows API中的`HeapAlloc``HeapFree`需要成对使用,否则也会失败。 --- ### **2. 运行时库(Runtime Library)差异** 不同DLL可能使用**不同版本的C++运行时库**(如MSVC的`MT`/`MD`选项): - 如果DLL A使用Debug版本的运行时库分配内存(可能添加了额外调试信息),而DLL B使用Release版本释放,内存布局不匹配会导致问题。 - 不同编译器(如GCCMSVC)的`new`/`delete`实现也可能不兼容。 --- ### **3. 析构函数的可见性** 如果对象DLL A中定义,但在DLL B中调用`delete`: - 析构函数可能未在DLL B中正确导出或实现,导致无法正确销毁对象。 - 尤其是虚析构函数未被正确定义时,问题更常见。 --- ### **4. 解决方案** #### **(1) 同一模块内分配释放** - **最佳实践**:在DLL中提供**创建销毁对象的接口函数**,确保内存操作在同一堆中进行: ```cpp // DLL导出函数 extern "C" __declspec(dllexport) MyClass* CreateObject() { return new MyClass(); } extern "C" __declspec(dllexport) void DestroyObject(MyClass* obj) { delete obj; } ``` #### **(2) 使用智能指针指定删除器** - 通过自定义删除器,确保调用正确的释放逻辑: ```cpp std::shared_ptr<MyClass> obj(CreateObject(), [](MyClass* p) { DestroyObject(p); }); ``` #### **(3) 统一运行时库配置** - 确保所有DLL主程序使用**相同运行时库**(如`/MD`或`/MT`)**编译器版本**。 #### **(4) 使用操作系统级别的内存管理** - 通过`VirtualAlloc`/`VirtualFree`等系统API直接管理内存(跳过C++堆)。 #### **(5) 显式导出析构函数** - 确保类的析构函数在DLL中可见: ```cpp class __declspec(dllexport) MyClass { public: virtual ~MyClass(); // 必须导出 }; ``` --- ### **总结** DLL的`new`/`delete`问题本质是**堆管理不一致****编译器实现的碎片化**。通过规范接口、统一编译环境或使用系统级内存管理,可以有效避免此类问题
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值