运行时库说明,以及与静态库、动态库、调用程序之间的关系
一、什么是运行时库
运行时库是程序在运行时所需 要的库文件,通常运行时库是以LIB或DLL形式提供的。运行时库除了给我们提供必要的库函数调用(如memcpy、printf、malloc等)之外,它提供的另一个最重要的功能是为应用程序添加启动函 数。
运行时库启动函数的主要功能为进行程序的初始化,对全局变量进行赋初值,加载用户程序的入口函数。
二、编译方式
运行时库的编译方式有四种:
动态编译:MD(release),MDd(debug)
静态编译:MT(release),MTd(debug)
此处的动态、静态编译和工程是lib、dll还是exe没有什么关系,是指定运行时库以什么方式让你的工程(lib、dll、exe)调用。如果是动态编译,则大家共同使用一个运行时库dll;如果是静态编译,编译器会把运行时库lib集成进工程(lib、dll、exe)。
1、动态编译MD(release),MDd(debug)
MFC库和运行时库是两种库,但必须使用相同方式编译。如果使用动态编译,在没有运行环境的情况下程序会提示缺少mfc80d.dll,msvcr80d.dll,msvcp80d.dll三个动态库(vc2005,debug方式):
mfc80d.dll:MFC动态库
msvcr80d.dll:C运行时库。提供fopen,printf等C函数。
msvcp80d.dll:C++运行时库,#include <iostream>后会要求此dll。提供fgets,STL库等。
以上三个动态库可用VC工具Depends导入来查看具体提供了什么函数。
2、静态编译MT(release),MTd(debug)
编译器会把如下lib集成进工程:
Reusable Library | Switch | Library | Macro(s) Defined |
Single Threaded | /ML | LIBC.LIB | (none) |
Static MultiThread | /MT | LIBCMT.LIB | _MT |
Dynamic Link (DLL) | /MD | MSVCRT.LIB | _MT and _DLL |
Debug Single Threaded | /MLd | LIBCD.LIB | _DEBUG |
Debug Static MultiThread | /MTd | LIBCMTD.LIB | _DEBUG and _MT |
Debug Dynamic Link (DLL) | /MDd | MSVCRTD.LIB | _DEBUG, _MT, and _DLL |
注:从Visual C++ 2005开始,libcp.lib和libcpd.lib(老的/ML和/MLd选项)已经被移除。通过/MT和/MTd使用libcpmt.lib和libcpmtd.lib取代。
三、与静态库、动态库、调用程序之间的关系
以各种方式编译后的对比:
Lib | 调用程序 | 结果 | Dll | 调用程序 | 结果 |
MT | MT | 正常 | MT | MT | 正常 |
MT | MD | 链接错误,函数重定义 | MT | MD | 正常 |
MD | MT | 链接错误,函数重定义 | MD | MT | 正常 |
MD | MD | 正常 | MD | MD | 正常 |
MTd | MTd | 正常 | MTd | MTd | 正常 |
MTd | MDd | 链接错误,函数重定义 | MTd | MDd | 正常 |
MDd | MTd | 链接错误,函数重定义 | MDd | MTd | 正常 |
MDd | MDd | 正常 | MDd | MDd | 正常 |
可见,使用静态库封装时需要向调用者提供4种编译版本,或者向调用者说明本静态库是什么编译方式。
注:DLL和调用程序任何一方进行静态编译后,dll内部new的内存指针在调用程序里delete时会报“堆被破坏”的错,所以两者必须同是动态编译才能dll内部new外部delete,原因是new/delete和malloc/free函数都是运行时库提供的,库内部保存了一个进程默认堆的句柄,同是动态编译时会使用同一个堆句柄,如果某一方是静态编译,双方就不会使用同一个堆句柄(静态编译后工程都会调用其本身的运行时库lib),外部delete时使用的堆句柄不一致导致提示堆被破坏。如遇到此问题可以使用HeapCreate系列函数,此系列函数会创建一个自定义堆,dll和工程都可以使用这个自定义堆。但建议不要这样做,应该谁申请的内存谁去释放。
附一个HeapCreate系列函数的使用例子:
//创建一个自定义堆
HANDLE myheap = HeapCreate(HEAP_GENERATE_EXCEPTIONS,4096,4096*64);
//在自定义堆上分配内存。
int *head = (int *)HeapAlloc(myheap,HEAP_ZERO_MEMORY|HEAP_GENERATE_EXCEPTIONS,sizeof(int));
//释放自定义堆上分配的内存。
HeapFree(myheap,HEAP_NO_SERIALIZE,headTemp);
//释放自定义堆
HeapDestroy(myheap);
//获取当前进程默认堆句柄,使用此句柄可直接用HeapAlloc/ HeapFree操作默认堆。
HANDLE myheap = GetProcessHeap()
另附一个题外话:
ADO等com对象在被封装到DLL后(ado对象为全局对象,函数内没有问题),调用dll的程序一定要使用动态加载dll(自己控制dll的卸载),否则会出现程序在退出时崩溃的问题。我调试中发现程序(MFC)在退出后会先隐式调用CoUninitialize()函数关闭主线程的com库,然后才卸载DLL,此时如果dll内部有全局com对象在调用release等释放com资源的操作就会引起程序崩溃。
EXE(MFC)静态加载DLL的顺序是:
运行时:
1、DLL:DLL全局类对象首先被构造。
2、DLL:初始化DLL,DllMain函数的参数ul_reason_for_call值为DLL_PROCESS_ATTACH。
3、EXE:调用APP类构造函数。
4、EXE:调用EXE全局类对象构造函数。
5、EXE:执行APP类initinstance-》等一系列程序初始化操作开始,程序开始运行。
退出时:
1、EXE:各个类析构-》调用APP类exitInstance。
2、EXE:EXE全局类对象析构。
3、EXE:APP类析构。
4、DLL:调用DllMain函数的参数ul_reason_for_call值为DLL_PROCESS_DETACH。
5、DLL:DLL全局类对象析构。
对于com最好是每个线程的开始一次CoInitialize,退出的时候一次CoUninitialize,但如果有其他模块在调用CoUninitialize,本模块就会出问题,我又不能告诉调用者自己去CoInitialize和CoUninitialize,所以我只CoInitialize不CoUninitialize,这样不知道会有什么问题,我操作数据库会用到ADO,目前没什么问题。
DLL主函数
BOOL APIENTRY DllMain( HMODULE hModule,//本dll句柄
DWORD ul_reason_for_call,//调用原因
LPVOID lpReserved//保留)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH://进程第一次调用本dll时会进入。
break;
case DLL_THREAD_ATTACH://每创建一个线程都会进入。
break;
case DLL_THREAD_DETACH://每个线程返回时会进入,异常中断、调用TerminateThread退出的线程不会进入。
break;
case DLL_PROCESS_DETACH://进程退出后会进入。
break;
}
return TRUE;
}
DLL动态加载
//载入dll
HINSTANCE mydll = LoadLibrary("D:\\mydll.dll");
//声明dll中函数类型,类型是一个函数指针。
typedef bool (*dllfunType)(int a, int b);
//取出dll中函数地址,并赋值给一个函数指针。参数是dll句柄,参数是dll中实际的函数名称。
//函数的实际名称要看dll的函数导出方式,以C方式导出就是函数名,其他方式导出请用VC工具Depends查看。
dllfunType dbcon = (dllfunType)GetProcAddress(mydll, "dbcon");
//调用函数指针。
dbcon(1,2);
//释放dll资源
FreeLibrary(mydll);