LIB和DLL的使用
内容列表
- lib的创建和使用
- 最简单的DLL
- 显式加载DLL
- 用def文件定义输出函数
- 显式调用DLL中的函数
- 使用__declspec(dllexport)定义DLL输出的函数
- 使用extern "c"隐式创建和使用DLL
- __declspec(dllexport)和__declspec(dllimport)配对使用
- DLL中导出全局变量和对象
- VC中编写和调用DLL
参考
DLL编写教程
http://www.blogjava.net/wxb_nudt/archive/2007/09/11/144371.html
比较及创建例子:静态链接库和动态链接库
http://www.cnblogs.com/Winston/archive/2008/07/05/1236273.html
--------------------------------------------------------
LIB相关
- lib的创建
- 建立Win32 Project , 选择lib,支持MFC;之后直接在工程中添加类;
- 编译,生成lib
- 提供给用户头文件和.lib文件
- 注:lib文件输出路径设置Linker-Advanced-Import Library下,例如: ..\lib2007\$(TargetName).lib
- lib的使用
- 包含头文件
- 在工程设置中添加.lib文件,使用类时添加头文件即可。
- 或者在要使用类的cpp文件头部加入下述语句:
- #pragma comment(lib,"**.lib")
-------------------------------------------------------
最简单的DLL
- 程序
- #include <objbase.h>
#include <iostream.h>
BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, void* lpReserved)
{
HANDLE g_hModule;
switch(dwReason)
{
case DLL_PROCESS_ATTACH:
cout<<"Dll is attached!"<<endl;
g_hModule = (HINSTANCE)hModule;
break;
case DLL_PROCESS_DETACH:
cout<<"Dll is detached!"<<endl;
g_hModule=NULL;
break;
}
return true;
} - 参数说明
- hModule:表示本dll的实例句柄
- dwReason:dll当前所处的状态
- DLL_PROCESS_ATTACH表示dll刚刚被加载到一个进程中
- DLL_PROCESS_DETACH表示dll刚刚从一个进程中卸载
-------------------------------------------------------
用LoadLibrary显式加载DLL
- 程序
- #include
<windows.h>
#include <iostream.h>
int main(void)
{
//加载我们的dll
HINSTANCE hinst=::LoadLibrary("dll_nolib.dll");
if (NULL != hinst)
{
cout<<"dll loaded!"<<endl;
}
return 0;
} - DLL的位置
- 将DLL放到sln项目所在的当前目录,可以加载;
- 问题
- 如何放到其他目录,然后通过包含目录的方式加载?
- 解决:在linker-General选项卡下有lib目录的设置
-------------------------------------------------------
用LoadLibraryEx显式加载DLL
- 原型
HMODULE WINAPI LoadLibraryEx(
_In_ LPCTSTR lpFileName,
_Reserved_ HANDLE hFile,
_In_ DWORD dwFlags
);
- 例子
HMODULE m_hModule;
m_hModule = NULL;
CString strPath = teamworkEnv();
strPath += _T("\\Bin\\Win32\\CooBaseInterface.dll");
m_hModule = LoadLibraryEx( strPath, NULL, LOAD_WITH_ALTERED_SEARCH_PATH);
-------------------------------------------------------
使用Def文件定义输出函数
- Def文件格式
- 首先是LIBRARY关键字,指定dll的名字
- 然后一个可选的关键字DESCRIPTION,后面写上版权等信息(不写也可以);
- 最后是EXPORTS关键字,后面写上dll中所有要输出的函数名或变量名,然后接上@以及依次编号的数字(从1到N),最后接上修饰符
- eg:FuncInDll @1 PRIVATE
- 注意@前的空格!
-------------------------------------------------------
显式调用DLL里的函数
- 定义函数指针
- typedef void (* DLLWITHLIB )(void);
- 定义一个函数指针变量
- DLLWITHLIB pfFuncInDll = NULL;
- 加载DLL
- HINSTANCE hinst=::LoadLibrary("dll_def.dll");
- 之后判断hinst是否为NULL
- 使用GetPorcAddress找到dll中导出的函数,赋值给函数指针变量
- pfFuncInDll = (DLLWITHLIB)GetProcAddress(hinst, "FuncInDll");
- 之后判断pfFuncInDll是否为空
- 注意函数
GetPorcAddress
- 用途:这个API是用来查找dll中的函数地址的
- 第一个参数是DLL的句柄,即LoadLibrary返回的句柄
- 第二个参数是dll中的函数名称,即dumpbin中输出的函数名(注意,这里的函数名称指的是编译后的函数名,不一定等于dll源代码中的函数名)。
- 调用dll里的函数
- (*pfFuncInDll)();
-------------------------------------------------------
使用__declspec(dllexport)定义dll的输出函数
- 方法:
- 去掉def文件,并在每个要输出的函数前面加上声明__declspec(dllexport)
- 容易产生的问题
- 编译后的函数名为?FuncInDll@@YAXXZ,而并不是FuncInDll;这是因为c++编译器基于函数重载的考虑,会更改函数名
- 解决方法
- 使用extern“C”指令来命令c++编译器以c编译器的方式来命名该函数
- 修改后的函数声明:extern "C" __declspec(dllexport) void FuncInDll (void)
-------------------------------------------------------
隐式调用DLL
- DLL的创建
- .h文件:包含函数声明
- .cpp文件:包含DllMain函数及函数实现
- 例子:
- 代码如下:
dll_withlibAndH.h
extern "C" __declspec(dllexport) void FuncInDll (void);
dll_withlibAndH.cpp
#include <objbase.h>
#include <iostream.h>
#include "dll_withLibAndH.h"//看到没有,这就是我们增加的头文件
extern "C" __declspec(dllexport) void FuncInDll (void)
{
cout<<"FuncInDll is called!"<<endl;
}
BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, void* lpReserved)
{
HANDLE g_hModule;
switch(dwReason)
{
case DLL_PROCESS_ATTACH:
g_hModule = (HINSTANCE)hModule;
break;
case DLL_PROCESS_DETACH:
g_hModule=NULL;
break;
}
return TRUE;
}
- DLL的使用
- 步骤
- 包含头文件
dll_withLibAndH.h
- .h的路径可以通过C++-常规选项卡下设置
- 加载lib
- #pragma comment(lib,"dll_withLibAndH.lib")
- 也可以通过linker选项卡设置
- lib的路径可以通过linker-常规选项卡下设置
- 将DLL文件拷贝到当前项目目录下
- 如果不拷贝DLL文件,运行时会报错!
- 第三步也是使用DLL方式和使用LIB方式操作上的主要区别!两种方式前两步的设置是一样的!
- 包含头文件
dll_withLibAndH.h
- 客户端代码:
- #include
"dll_withLibAndH.h"
//注意路径,加载 dll的另一种方法是 Project | setting | link 设置里
#pragma comment(lib,"dll_withLibAndH.lib")
int main(void)
{
FuncInDll();//只要这样我们就可以调用dll里的函数了
return 0;
}
- 步骤
-----------------------------------------------------------
问题:
- (显式调用) 如何将DLL放到其他位置?然后通过设置路径调用?
使用LoadLibraryEx, 第一个参数传入DLL路径;
例如:
m_hModule = LoadLibraryEx( strPath, NULL, LOAD_WITH_ALTERED_SEARCH_PATH);
- 说明:DLL生成后直接放置在Debug下,同一个Solution的project之间的引用不用设置也不会出问题;不同的Solution需要将DLL拷贝至使用者的Debug下
-------------------------------------------------------
配对使用__declspec(dllexport)和__declspec(dllimport)
这时要考虑一个情况:若DLL1.CPP是源,DLL2.CPP使用了DLL1中的函数,但同时DLL2也是一个DLL,也要输出一些函数供Client.CPP使用。那么在DLL2中如何声明所有的函数,其中包含了从DLL1中引入的函数,还包括自己要输出的函数。这个时候就需要同时使用__declspec(dllexport)和__declspec(dllimport)了。前者用来修饰本dll中的输出函数,后者用来修饰从其它dll中引入的函数。
DLL1,DLL2,client的创建和使用过程
- 创建DLL1
- 包括.h和.cpp文件,h文件中包含函数声明,cpp文件中包含函数实现
- h文件中要有如下语句:
- #ifdef DLL_DLL1_EXPORTS
- #define DLL_DLL1_API __declspec(dllexport)
- #else
- #define DLL_DLL1_API __declspec(dllimport)
- #endif
- 说明:
- 在头文件中以这种方式定义宏DLL_DLL2_EXPORTS和DLL_DLL2_API,可以确保DLL端的函数用__declspec(dllexport)修饰,而客户端的函数用__declspec(dllimport)修饰。当然,记得在编译dll时加上参数/D “DLL_DLL2_EXPORTS”,或者干脆就在dll的cpp文件第一行加上#define DLL_DLL2_EXPORTS
- h文件中的函数声明写为:
- DLL_DLL1_API void FuncInDll1(void);
- DLL_DLL1_API void FuncInDll1(int);
- cpp文件中要定义宏:
- #define DLL_DLL1_EXPORTS
- cpp文件中的函数实现,开头添加:
- DLL_DLL1_API
- 创建DLL2
- .h和.cpp文件的写法与DLL1中类似
- 不同1:.h中包含DLL1.h
- 需要设置DLL1.h的路径(可以从C++-常规下设置)
- 不同2:.cpp中包含语句
- #pragma comment(lib,"dll1.lib")
- 也可以从linker-input下设置
- 需要设置dll1.lib的路径(可以从linker-常规下设置)
- 创建client
- 需要包含DLL2.h(里边已包含DLL1.h)
- 需要包含lib
- #pragma comment(lib,"dll2.lib")
- #pragma comment(lib,"dll1.lib")
- 自己的Solution中采用的方法
- 将.h文件全放到Solution目录下lib文件夹中
- .lib的路径设置为Solution的Debug目录
- 需要设置project之间的依赖关系(dll2依赖dll1,client依赖上述两个),不然编译时顺序会有问题
- DLL生成后直接放置在Debug下,同一个Solution的project之间的引用不用设置也不会出问题;不同的Solution需要将DLL拷贝至使用者的Debug下。
-------------------------------------------------------
导出全局变量
- 语法
- 头文件中:extern DLL_OBJECT_API int g_nDll;
- cpp文件中:DLL_OBJECT_API int g_nDll = 9;
-------------------------------------------------------
小结
- 显式调用和隐式调用的使用时机
- 只有一个时候使用显式调用是合理的,就是当客户端不是C/C++的时候。这时是无法隐式调用的。
- Def的使用
- 其实def的功能相当于extern “C” __declspec(dllexport),所以它也仅能处理C函数,而不能处理重载函数。
- 而__declspec(dllexport)和__declspec(dllimport)配合使用能够适应任何情况,因此__declspec(dllexport)是更为先进的方法。
- C语言调用DLL
- 若使用extern “C”,则函数名称保持不变,调用较方便,但是不支持函数重载等一系列c++功能
- 若不使用extern “C”,则调用前要查看编译后的符号,非常不方便
- 这两个问题DLL都不能很好的解决,只能说凑合着用。但是在COM中,都得到了完美的解决。所以,要在Windows平台实现语言无关性,还是只有使用COM中间件。
-------------------------------------------------------