在初级的编程应用中,一般是添加程序的头文件以及源文件,进行连接调用,但是在长期或大工程应用中,往往会耗掉大量内存,所以为了实现代码的共享,应用动态库完成模块化功能。共享代码分为静态链接库和动态链接库,在软件发布时会发布静态链接库,但是不利于升级和修改。在程序中往往会生成lib文件,lib文件在静态调用和动态调用时功能是不一样,静态时lib文件则会有函数的声明以及定义,动态时往往只会有一些dll文件中函数的标识或序号。在动态库生成的程序中会有一些书写标准,进而从动态库中提取函数接口比较方便。动态库链接时分为显示连接和隐式链接,隐式链接通常就是之前自己常用的方法,通过添加。lib、dll文件目录减少代码书写,其实就是要添加引用路径。显示链接则是通过《window.h》文件里的loadlibrary进行动态加载,而且经常结合函数getprocaddress函数进行调用。上述函数其实就是将动态库中函数提取出来,参数类型为函数的句柄以及序号,最后是得到所加载DLL模块中函数地址,一般在函数应用中首先是宏定义个函数指针,表明指针类型,然后将指针实例化,最后将函数地址赋值给实值化的变量。这样就完成了动态库的调用,最后别忘了,在显示链接时,最后一定要释放DLL模块。总结以上内容,DLL需要以某种特定方式声明导出函数,再用某一特定方式调用dll导出函数。
在实际应用中,显示链接中,为了方便讲动态库输出,往往会在添加一个DLLMAIN源文件,将动态库函数进行说明,一般里面内容还是一致的。同时加上采用.def模块文件,添加函数:LIBRARY dlltest(文件名);export 函数。上述规则可以总结为,def文件1、library语句说明.def文件相应的dll;2、exports语句要列出列出导出函数名称,可以在导出函数后加&n,表示导出函数的序号为n。
上一段讲到lib与DLL文件的区别,lib是在编译阶段起作用,只有在运行时dll文件才会被调用。
在windows加载dll文件时,需要一个入口函数,就像一个控制台程序,需要入口函数main一样,需要入口函数dllmain,有时不需要入口函数,因为,缺省时,系统会从其他运行库引入不作任何操作的缺省dllmain函数版本。根据编写规范,还是有必要写dllmain作为函数加载dll,函数调用时,在缺省情况下声明为_cdecl,标准声明为_stdcall.
显示调用;非常适合大工程应用调用,毕竟是动态调用。主要是指在应用程序中用loadlibrary或MFC提供的Afxloadlibrary显示调用,再用getprocaddress(0获取想要的函数,loadli返回HINSTANCE参数,上述函数将符号或者标识号转换为DLL内部地址。在输出函数上通常添加extern "c" ,意味可以在其他代码调用,如不写,则只可在c++中调用。不过实现显式链接要麻烦一些。在应用程序中用 LoadLibrary或MFC提供的AfxLoadLibrary显式的将自己所做的动态链接库调进来,动态链接库的文件名即是上述两个函数的参数,此后再用GetProcAddress()获取想要引入的函数。自此,你就可以象使用如同在应用程序自定义的函数一样来调用此引入函数了。在应用程序退出之前,应该用FreeLibrary或MFC提供的AfxFreeLibrary释放动态链接库。
下面展示编程结果:
首先是建立一个DLL项目,可以空项目也可以是基于MFC的DLL项目文件,向里添加.h文件和.cpp文件或自动生成的文件。如下:
// STUDYDLL.h : STUDYDLL DLL 的主头文件 // #pragma once #ifndef __AFXWIN_H__ #error "在包含此文件之前包含“stdafx.h”以生成 PCH 文件" #endif #define ExportDLL extern "C" _declspec(dllexport)// extern "C"表示C语言编译环境下,外部函数声明。_declspec(dllexport) //使用导出函数关键字_declspec(dllexport)创建MyDll.dll,在创建如此自定义动态库导出函数比较简捷。 #include "resource.h" // 主符号 // CSTUDYDLLApp // 有关此类实现的信息,请参阅 STUDYDLL.cpp // class CSTUDYDLLApp : public CWinApp { public: CSTUDYDLLApp(); // 重写 public: virtual BOOL InitInstance(); DECLARE_MESSAGE_MAP() }; ExportDLL int ADD(int m,int n);//声明出的导出函数
// STUDYDLL.cpp : 定义 DLL 的初始化例程。 // #include "stdafx.h" #include "STUDYDLL.h" #ifdef _DEBUG #define new DEBUG_NEW #endif // //TODO: 如果此 DLL 相对于 MFC DLL 是动态链接的, // 则从此 DLL 导出的任何调入 // MFC 的函数必须将 AFX_MANAGE_STATE 宏添加到 // 该函数的最前面。 // // 例如: // // extern "C" BOOL PASCAL EXPORT ExportedFunction() // { // AFX_MANAGE_STATE(AfxGetStaticModuleState()); // // 此处为普通函数体 // } // // 此宏先于任何 MFC 调用 // 出现在每个函数中十分重要。这意味着 // 它必须作为函数中的第一个语句 // 出现,甚至先于所有对象变量声明, // 这是因为它们的构造函数可能生成 MFC // DLL 调用。 // // 有关其他详细信息, // 请参阅 MFC 技术说明 33 和 58。 // // CSTUDYDLLApp BEGIN_MESSAGE_MAP(CSTUDYDLLApp, CWinApp) END_MESSAGE_MAP() // CSTUDYDLLApp 构造 CSTUDYDLLApp::CSTUDYDLLApp() { // TODO: 在此处添加构造代码, // 将所有重要的初始化放置在 InitInstance 中 } // 唯一的一个 CSTUDYDLLApp 对象 CSTUDYDLLApp theApp; const GUID CDECL _tlid = { 0x2A63CFC3, 0xF75A, 0x47F7, { 0xB1, 0xA0, 0xA2, 0x4F, 0x2, 0x2B, 0x1D, 0x9D } }; const WORD _wVerMajor = 1; const WORD _wVerMinor = 0; // CSTUDYDLLApp 初始化 BOOL CSTUDYDLLApp::InitInstance() { CWinApp::InitInstance(); // 将所有 OLE 服务器(工厂)注册为运行。这将使 // OLE 库得以从其他应用程序创建对象。 COleObjectFactory::RegisterAll(); return TRUE; } // DllGetClassObject - 返回类工厂 STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv) { AFX_MANAGE_STATE(AfxGetStaticModuleState()); return AfxDllGetClassObject(rclsid, riid, ppv); } // DllCanUnloadNow - 允许 COM 卸载 DLL STDAPI DllCanUnloadNow(void) { AFX_MANAGE_STATE(AfxGetStaticModuleState()); return AfxDllCanUnloadNow(); } // DllRegisterServer - 将项添加到系统注册表 STDAPI DllRegisterServer(void) { AFX_MANAGE_STATE(AfxGetStaticModuleState()); if (!AfxOleRegisterTypeLib(AfxGetInstanceHandle(), _tlid)) return SELFREG_E_TYPELIB; if (!COleObjectFactory::UpdateRegistryAll()) return SELFREG_E_CLASS; return S_OK; } // DllUnregisterServer - 将项从系统注册表中移除 STDAPI DllUnregisterServer(void) { AFX_MANAGE_STATE(AfxGetStaticModuleState()); if (!AfxOleUnregisterTypeLib(_tlid, _wVerMajor, _wVerMinor)) return SELFREG_E_TYPELIB; if (!COleObjectFactory::UpdateRegistryAll(FALSE)) return SELFREG_E_CLASS; return S_OK; } //添加导出函数实现部分,与常用函数没有什么区别 int ADD(int m,int n) { int result=0; result=m+n; return result; }
#include <iostream> #include <windows.h> #include <stdio.h> #include <WinDef.h> #include <WinBase.h> #include <tchar.h> //_T文件的包含文件 //#include <Windows.h> using namespace std; int main() { int a,b; a=2; b=3; HINSTANCE g_myhistance=NULL;//首先是定义一个动态库接受句柄,加载动态库 //在编译程序之前,首先要将DLL文件拷贝到工程所在的目录或Windows系统目录下 g_myhistance=LoadLibrary("STUDYDLL.dll");//加载动态库,动态库存储路径一定清楚正确 if (g_myhistance==NULL) { MessageBox(NULL,_T("加载动态库失败!"),_T("结果显示"),MB_YESNO); return false; } //第二步使用类型定义关键字typedef,定义指向和DLL中相同的函数原型对象与函数指针,用于调用函数 typedef int(LPadd)(int,int); LPadd *ladd=NULL; ladd=(LPadd*)GetProcAddress(g_myhistance,"ADD");//第三通过GetProcAddress加载动态库指定函数 if (ladd==NULL) { MessageBox(NULL,_T("加载函数失败"),_T("结果展示"),MB_YESNO); return false; } int num=0; num=(*ladd)(a,b);//第四此处用函数指针执行函数调用,对形参赋值。 printf("%d\n", num); system("pause");//显示输出结果 }