您的位置:首页 > 其它

DLL开发的问题

2017-01-24 00:05 134 查看

DLL开发的问题

导出类问题

使用VS2010开发的DLL项目工程在VS2013中使用有问题:

 

// MathLibrary.h
 
#ifndef MATHLIBRARY_H
#define
MATHLIBRARY_H
 
#ifdef DLLPROJECT_EXPORTS
 
#define MATH_LIBRARY_API
__declspec(dllexport)
 
#else
 
#define
MATH_LIBRARY_API __declspec(dllimport)
 
#endif
// 定义类导出
 
#include
<string>
 
class
MATH_LIBRARY_API CMathLibrary
{
public:
    CMathLibrary(std::string& str);
    ~CMathLibrary();
 
    static 
int Summary(int n);
    static 
int Factorial(int n);
    std::string& getName();
 
private:
    std::string s;
};
 
#endif

测试:

#include
<iostream>
#include
<string>
#include"MathLibrary.h"
#pragma
comment(lib,"DLLProject.lib")
 
int
_tmain(int argc,
_TCHAR* argv[])
{
    std::string str =
"test";
    CMathLibrary math(str);
 
    str = math.getName();
    std::cout << "Values: " <<
CMathLibrary::Factorial(5) <<
" " << str << std::endl;
 
    return 0;
}

 

上述问题采用/MDd方式解决。(DLL 中使用STL都可以通过这种方式编译成功)

 

这是因为STL里的string类并非是一个DLL的输出类,这会导致类的成员变量name无法正确输出。   

  在你将CTest定义为一个导出类后,CTest中的所有成员函数都将编译连接为DLL的输出函数。包括一些隐含的构造/析构函数以及operator=等。如果一个类的一些PUBLIC成员变量不是输出类,那么DLL的使用者对这些公共成员的方法的访问可能会导致访问不同的VC运行库。   

  分析下面的代码:     

  CTest     test;      

  //   调用CTest的构造函数,由于CTest是输出类,构造函数也是DLL的输出函数,构造函数调用的string的构造函数,string调用VC运行库分配内存,这个时候string类使用的运行库是与CTest实现所在的DLL进行连接的那个VC运行库   

 test.intValue     =     3;                      //没有问题       

  //   没有问题,因为对整数的赋值与运行库无关     

 test.name     =    "name";                    // 编译、连接都通过,运行时出现内存访问异常:  //test.name中的数据指针是非法的堆指针   

  //   由于string不是DLL的输出类,所以string的operator=不是一个DLL内部的函数,   

      对string的operator=成员的访问会导致编译器调用当前的模块(EXE)使用的VC运行库中的函数,这便产生了问题。      

  //   同样在析构的时候也可能产生这样的问题   

    

  解决的办法是  

      在编译两个模块时选择相同类型的DLL版本的VC运行库,相同类型的静态的VC运行库不解决问题,因为在这种情况下,不同的模块分别包含VC静态运行库的静态数据和代码。

 

建议:  

不要在win32  dll中导出类。如果要导出就使用MFC扩展DLL或win32静态库。   

 

 其实问题很简单,为了便于说明问题,假定CTest实现所在的DLL是TEST.DLL,   CTest的使用者是CLIENT.EXE。   

     在构造CTest的时候,CTest的构造函数调用string的构造函数,这个时候string使用的是与TEST.DLL连接的运行库中的内存分配函数。   

     当CLIENT.EXE代码中对string赋值的时候,会导致string重新分配内存,这个时候string使用的是与CLIENT.EXE连接的运行库中的内存分配函数。在这个过程中,string会将在构造时分配的内存释放掉,这些内存是由与TEST.DLL连接的运行库的内存分配函数分配的,但是却由与CLIENT.EXE连接的运行库的内存分配函数释放,如果TEST.DLL和Client.EXE使用的是同样类型的运行库DLL(都使用Debug   Multi-thread  DLL,   或者Release
  Multi-Thread  DLL),   那么是不会有问题的,因为这个时候VC的运行库中的malloc,   free使用同一的CRT内部数据。     

     如果使用不同的CRT库,那么实际上在CLIENT.EXE运行的时候,进程的地址空间里实际上存在两份不同的CRT的代码和数据(分别被TEST.DLL和CLIENT.EXE使用),   当一个CRT的free函数企图释放由另一个CRT通过malloc分配的函数的时候,就会出现问题,这个时候CrtIsValidHeapPointer函数就会认为这不是一个有效的堆指针。   

     那么如果使用同样的静态库呢?这个问题同样存在,因为如果使用静态库,在CLIENT.EXE运行的时候,进程的地址空间里还是会存在两份CRT的代码和数据,尽管他们的代码和数据结构很多都一样。    

     类似的问题有通过一个CRT的fopen获得一个FILE指针,然后由另一个CRT的fclose关闭等等。   

     否则,在释放内存的时候,那么CrtIsValidHeapPointer就会告诉你这快内存不是由这个CRT分配的。   

 

跨模块内存管理

A.exe中加载B.dll.  在A.exe中用new申请了一片内存,在B.dll中执行delete导致程序崩溃。

 

原因:跨模块内存管理不一致导致。A.exe是MD链接,B.dll是MT链接,有2个C++运行库运行在同一个地址空间上,有可能读写同样内存区域,导致程序崩溃。

解决办法:

 

每个模块自己管理内存。不要跨模块申请/释放内存.

 

1. A.exe中调用B.dll中的对应的函数,由其分配内存,然后再调用B.dll中的函数去释放。

2. A申请的内存,由A来释放。

3.B.dll使用MD链接,而且B编译时使用和A编译时相同版本的运行时库。

 

在Windows系统中,进程和dll的内存管理是由Runtime Library实现的,而MT和MD影响了Runtim Library的链接方式,从而导致了此问题。

l MT 是多线程静态链接运行时库。

l MD是多线程动态链接运行时库。

简单点说,当使用静态库链接时,会有多份运行时库,而且每份库都拷贝一份自己的内存管理。而使用动态链接后,由于都是链接的同一个运行时库,这样就保证内存管理只用一份了。但是动态链接时必须两个模块使用同样版本的运行时库,不同版本的依旧会有多份。

在该案例中,由于A.exe是MD动态链接,B.dll是MT静态链接,导致有2份运行时库存在,在进行内存管理时就出错了,从而导致程序崩溃。

所以跨模块的内存管理,最好是由每个模块提供自己的分配和销毁接口函数,然后在模块外部通过这些接口的调用来控制对象的生命期,而不是在外部 new/delete。如果实在要用,可以使用微软提供了GlobalAlloc/GlobalFree这样的全局内存API,用它们的话跨模块也没有问题的。

 

运行时库

/MT和/MTd表示采用多线程CRT库的静态lib版本。该选项会在编译时将运行时库以静态lib的形式完全嵌入。该选项生成的可执行文件运行时不需要运行时库dll的参加,会获得轻微的性能提升,但最终生成的二进制代码因链入庞大的运行时库实现而变得非常臃肿。当某项目以静态链接库的形式嵌入到多个项目,则可能造成运行时库的内存管理有多份,最终将导致致命的“Invalid Address specifiedto RtlValidateHeap”问题。另外托管C++和CLI中不再支持/MT和/MTd选项。

/MD和/MDd表示采用多线程CRT库的动态dll版本,会使应用程序使用运行时库特定版本的多线程DLL。链接时将按照传统VC链接dll的方式将运行时库MSVCRxx.DLL的导入库MSVCRT.lib链接,在运行时要求安装了相应版本的VC运行时库可再发行组件包(当然把这些运行时库dll放在应用程序目录下也是可以的)。 因/MD和/MDd方式不会将运行时库链接到可执行文件内部,可有效减少可执行文件尺寸。当多项目以MD方式运作时,其内部会采用同一个堆,内存管理将被简化,跨模块内存管理问题也能得到缓解。

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: