C++ DLL动态调用
MFC Dll封装了MFC的库类。所以MFC运行时是必需的。 win32是SDK。你说他们的区别嘛,其实就是MFC和SDK的区别
MFC DLL三种类型
常规DLL:能被MFC程序和win32程序使用。在DLL内部可以用MFC类的, 但是给外部的接口并不提供MFC类的支持,还是提供Win32 DLL同样的接口,与Win32的差别在于,它的内部可以用MFC类 扩展DLL可以有C++的接口,它可以导出C++类给客户端。 导出的函数可以使用C++/MFC数据类型做参数或返回值,导出一个类时客户端能创建类对象或者派生这个类。 同时,在DLL中也可以使用MFC。
Regular statically linked to MFC DLL (标准静态链接MFC DLL):编译时链接代码,占用空间大,不依赖于其它的DLL
Regular using the shared MFC DLL(标准动态链接MFC DLL):编译时不链接代码,占用空间小,依赖于其它的DLL
扩展DLL:Extension MFC DLL(扩展MFC DLL):是MFC类库的扩展,只能被MFC程序使用
Win32 DLL两种类型
动态链接库文件:Win32 Dynamic-Link Library
静态链接库文件:Win32 Static Library:.lib文件,使用C++语言写的,不依赖MFC!
难点
系统中经常用到DLL程序,多数DLL程序工作在底层,为上层应用提供支持,开发DLL程序时应注意以下几点:
1、数据保存与交换
很多DLL程序都是动态加载的(用Loadlibrary函数),DLL中的数据随着Freelibrary也随之消亡,有时DLL中的函数是有联系的,如DLL中函数2需要DLL中函数1生成的数据,此时在执行完函数1时DLL已经Freelibrary了,另一时刻客户重新加载了DLL并且调用DLL中的函数2,那函数2怎样获取到函数1生成的数据呢?是否还需重新调用函数1呢?不用的,此时可以用windows文件的映射机制(IPC)来保存函数1生成的数据,供函数2使用,代码如下:
内存映射文件(共享内存)示例:
//写入内存映射文件
extern "C" __declspec(dllexport) void __cdecl WriteMap(unsigned char* buff, int len=1024)
{
char MapName[16]={0};
HANDLE hMap;
LPVOID pBuffer;
sprintf(MapName,"%s","ShareMemory");
hMap = OpenFileMapping(FILE_MAP_ALL_ACCESS, 0, MapName);
if (hMap==NULL){
hMap = CreateFileMapping(INVALID_HANDLE_VALUE,
NULL,
PAGE_READWRITE,
0,
len+1,
MapName);
}
pBuffer = MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, 0);
memcpy(pBuffer,buff,len);
}
//读取内存映射文件
extern "C" __declspec(dllexport) int __cdecl ReadMap(unsigned char* buff,int len=1024)
{
char MapName[16]={0};
HANDLE hMap;
LPVOID pBuffer;
sprintf(MapName,"%s","ShareMemory");
hMap = OpenFileMapping(FILE_MAP_ALL_ACCESS, 0, MapName);
if (hMap==NULL){
return -1;
}
pBuffer = MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, 0);
memcpy(buff,pBuffer,len);
UnmapViewOfFile(pBuffer);
CloseHandle(hMap);
return 0;
}
WriteMap函数负责将DLL中函数1的数据写入到内存映射文件,ReadMap函数负责读出写入的数据,数据一旦写入,只要客户的程序处于运行的状态,不管DLL已经释放还是处于加载状态,数据就一直存在(此例的内存映射文件相当于共享内存)
2、重入预防和排队
很多DLL执行的是费时通讯或设备IO操作,为安全起见DLL中的函数应防止线程重入,或进行排队,保证操作的正确性,可以使用信号量或临界区等技术对代码进行保护。
3、路径问题
有时需要在DLL中获取DLL文件的路径,此时可如下获得(win32 DLL 无MFC支持)
首先获取并保存DLL句柄
HMODULE m_hModule; //定义m_hModule,保存DLL句柄
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
m_hModule=hModule; //保存DLL句柄
return TRUE;
}
然后用getDLLPath函数用来获取DLL路径:
void getDLLPath(HANDLE hModule, char* DLLPath) {
char DLLPathTemp[1024] = { 0 };
GetModuleFileName((HMODULE)hModule, DLLPathTemp, 1024);
strncpy(DLLPath, DLLPathTemp, strrchr(DLLPathTemp, '\\') - DLLPathTemp + 1); //取出文件的路径
}
这样做的好处是无论DLL改成什么名字,都能动态的获取到DLL的路径。
如果生成DLL时使用了MFC,可以用如下的方法获取DLL的路径,以下是程序片段
// 唯一的一个CMemoryMapDllApp 对象
CMemoryMapDllApp theApp;
// CMemoryMapDllApp 初始化
BOOL CMemoryMapDllApp::InitInstance()
{
CWinApp::InitInstance();
char disp[128]={0};
char DLLPathTemp[1024] = { 0 };
sprintf(disp,"%s.dll",theApp.m_pszAppName);
GetModuleFileName(GetModuleHandle(disp), DLLPathTemp, 1024); //DLLPathTemp中保存了DLL的路径
//MessageBox(NULL,DLLPathTemp,"dll path",0);
return TRUE;
}
4、文件的权限
有时我们的DLL程序需安装在嵌入式wince或xp等嵌入式终端设备上,这些系统对某些目录设定了读写权限,这时我们的DLL对文件操作时就应该注意了,以防止操作错误导致程序运行崩溃。
5、导出函数和_cdecl _stdcall
_stdcall是Pascal程序的缺省调用方式,参数采用从右到左的压栈方式,被调函数自身在返回前清空堆栈。WIN32 Api都采用_stdcall调用方式。_cdecl是C/C++的缺省调用方式,参数采用从右到左的压栈方式,传送参数的内存栈由调用者维护。_cedcl约定的函数只能被C/C++调用,每一个调用它的函数都包含清空堆栈的代码,所以产生的可执行文件大小会比调用_stdcall函数的大。如果我们的函数使用了_cdecl,那么栈的清除工作是由调用者,这样带来了一个的问题,不同的编译器产生栈的方式不尽相同,那么调用者能否正常的完成清除工作呢?答案是不能。如果使用__stdcall,上面的问题就解决了,函数自己解决清除工作。所以,在跨(开发)平台的调用中,我们都使用__stdcall(虽然有时是以WINAPI的样子出现)。那么为什么还需要_cdecl呢?当我们遇到这样的函数如fprintf()它的参数是可变的,不定长的,被调用者事先无法知道参数的长度,事后的清除工作也无法正常的进行,因此,这种情况我们只能使用_cdecl。 到这里我们有一个结论,如果你的程序中没有涉及可变参数,最好使用__stdcall。6、链接问题
DLL程序在编译时要尽量的把所有依赖库都一次性链入程序中,尤其是MFC的DLL要注意编译时选择“在静态库中使用MFC”,以免DLL在发行后在不同的设备上可能出现的错误。
7、Dll打包
有时我们在DLL中动态加载了另外的DLL,这时可以考虑将另外的DLL作为资源打包到我们的DLL中,使用时动态的释放出DLL,代码如下:
HMODULE hThis = hModuleDll; //DLL句柄
HRSRC hRes = FindResource(hThis, MAKEINTRESOURCE(IDR_DLL1), "DLL");
HGLOBAL hGres = LoadResource(hThis, hRes);
HANDLE hFile = CreateFile("TesoLive.dll", GENERIC_WRITE, NULL, NULL, CREATE_NEW, FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_ARCHIVE, NULL);
PVOID pRes = LockResource(hGres);
DWORD dwSize = SizeofResource(hThis, hRes);
DWORD dwSizeWritten = 0;
WriteFile(hFile, pRes, dwSize, &dwSizeWritten, NULL);
CloseHandle(hFile);
下面就可以加载“TesoLive.dll”了。
8、DLL开发工具选择
如果我们的DLL需要用在java或python等环境中,不要选择版本太高的vs,如vs2105环境下的DLL在jdk1.6或python2上就有兼容的问题,现在很多的企业应用还仍然在jdk1.6或python2这样的平台上,所以目前开发DLL使用vs2005是比较合适的,做DLL不能太超前。
参见
Simple modern cross-platform dlopen/loadLibrary wrapper in C++
系统中经常用到DLL程序,多数DLL程序工作在底层,为上层应用提供支持,开发DLL程序时应注意以下几点:1、数据保存与交换很多DLL程序都是动态加载的(用Loadlibrary函数),DLL中的数据随着Freelibrary也随之消亡,有时DLL中的函数是有联系的,如DLL中函