DLL初步(2)
有时所编写的动态链接库需要在其他种类的编译器或者语言类别中进行调用。如前面所述,DLL文件生成的名称是自己自己改写过的。
可以在DLLL1工程中添加一个头文件,头文件中添加如下代码:
1 _declspec(dllimport) int add(int a,int b);
2 _declspec(dllimport) int sub(int a,int b);
所有调用这个动态链接库的工程都必须包含这个头文件。所有提供动态链接库的工程还必须提供这个头文件(个人感觉这个方法不太好)。
可以利用宏编译使动态链接库既可以被本链接库使用,也可以被外部程序使用,代码:
cpp:
1 #define DLL_API _declspec(dllexport)//导出
2 #include "DLLL1.h"
3
4 int add(int a,int b)
5 {
6 return a+b;
7 }
8 int sub(int a,int b)
9 {
10 return a-b;
11 }
h:
1 #ifdef DLL_API
2 #else
3 #define DLL_API _declspec(dllimport)//导入
4 #endif
5
6 DLL_API int add(int a,int b);
7 DLL_API int sub(int a,int b);
这里注意dllexport表示输出,dllimport表示输入。动态链接库工程调用.h文件时由于已经定义DLL_API,所以自动被定义成dllexport,表示为动态链接库的输出。而当其他工程包含.h文件时并没有定义DLL_API,所以这时需要重新定义为dllimport,表示输入。这样,既满足了在自己工程里表示输出,在其他包含的工程里表示输入的双重功能。
重新编写DLLL1的.h和.cpp文件,如下所示:
.h:
1 #ifdef DLL_API
2 #else
3 #define DLL_API _declspec(dllimport)//导入
4 #endif
5
6 DLL_API int add(int a,int b);
7 DLL_API int sub(int a,int b);
8
9 class DLL_API Point
10 {
11 public:
12 void output(int x,int y);
13 };
.cpp:
1 #define DLL_API _declspec(dllexport)//导出
2 #include "DLLL1.h"
3 #include<Windows.h>
4 #include<stdio.h>
5
6 int add(int a,int b)
7 {
8 return a+b;
9 }
10 int sub(int a,int b)
11 {
12 return a-b;
13 }
14 void Point::output(int x,int y)
15 {
16 HWND hwnd=GetForegroundWindow();//获得前景窗口的句柄
17 HDC hdc=GetDC(hwnd);//dc的句柄,设备句柄
18 char buf[20];
19 memset(buf,0,20);
20 sprintf(buf,"x=%d,y=%d",x,y);
21 //将""中的字符串付给buf,其中x,y的值由前面给出。
22 TextOut(hdc,0,0,buf,strlen(buf));
23 //需要现实文本的窗口的句柄,在窗口中的坐标,
24 //要现实的字符串,字符串长度
25 ReleaseDC(hwnd,hdc);
26 }
编译DLLL1工程,将.def和.dll文件拷贝到DLLLtest工程下。
在DLLLtest中添加一个按钮,用于现实测试output,其中代码如下:
1 class _declspec(dllimport) Point
2 {
3 public:
4 void output(int x,int y);
5 };
6 void CDLLLtestDlg::OnBtnOutput()
7 {
8 // TODO: Add your control notification handler code here
9 Point pt;
10 pt.output(5,3);
11 }
这里要注意第1~5行代码,发现这样写才能编译,但是让我困惑的是如果这样写的话就要预先知道类中具体的函数和变量内容。而DLL对外提供的时候并不提供这些,所以感觉不符合DLL文件的通用性原则。
另外还可以导出类中的某一个函数,方法是只在类中某个函数之前_declspec(dllexport),和一般函数的方法相同。
有时为了使用C++编写的dll文件可以在C文件中应用,需要在dll工程中做特殊的声明,如下所示:
.cpp:
1 #define DLL_API extern "C" _declspec(dllexport)//导出
2 #include "DLLL1.h"
3 #include<Windows.h>
4 #include<stdio.h>
5
6 int add(int a,int b)
7 {
8 return a+b;
9 }
10 int sub(int a,int b)
11 {
12 return a-b;
13 }
.h:
1 #ifdef DLL_API
2 #else
3 #define DLL_API extern "C" _declspec(dllimport)//导入
4 #endif
5
6 DLL_API int add(int a,int b);
7 DLL_API int sub(int a,int b);
注意这里的C需要大写。
再次运行dumpbin -exports DLLL1.dll时可以发现add和sub后面没有其他字符。
(在调用约定发生改变时,输出的函数名字有可能发生改变,如变成_stdcall)
通过模块定义文件使输出函数名在即使约定改变的情况下输出函数名也不发生改变:
新建一个动态链接库工程,取名为DLLL2,向项目中添加一个C++源文件,再添加一个DLLL2.def文件
在DLLL2.def文件中填写如下代码:
1 LIBRARY DLLL2
2
3 EXPORTS
4 add
5 sub
其中EXPORTS后面跟着的表示输出函数列表,名字在这里固定。
同时在测试时可以通过动态加载的方法,向测试项目中加载动态链接库。动态加载的好处是不用对动态链接库预先写入内存,随用随读,占用空间少,调用灵活。
首先将测试工程DLLLtest恢复成刚建立时的原貌(删除所有和之前测试相关的代码)
1 void CDLLLtestDlg::OnBtnAdd()
2 {
3 HINSTANCE hInst;
4 hInst=LoadLibrary("DLLL2.dll");
5 //加载函数,函数名字组成的字符串
6 typedef int (*ADDPROC)(int a,int b);
7 //用typedef定义一个函数指针类型
8 ADDPROC Add=(ADDPROC)GetProcAddress(hInst,"add");
9 //获取导出函数的地址。模块句柄,函数名
10 //若导出成功,则返回导函数的地址,失败则返回值为NULL
11 CString str;
12 str.Format("a+b=%d",Add(5,3));
13 MessageBox(str);
14 }
将上面步骤中编写的DLLL2工程编译时生成的.lib和.dll文件直接拷贝到DLLLtest文件夹下。可以完成测试。
调用动态链接库并应用完后可以用FreeLibrary(hInst)去释放动态链接库。
其中typedef int (*ADDPROC)(int a,int b);这句表示定义一个函数指针类型这里用到了函数指针的概念。
没有def文件不可以直接使用函数名字进行动态连接,因为函数名字有可能不一致。
根据序号访问动态链接库中的函数:
将上面代码的第8条语句替换为:
1 ADDPROC Add=(ADDPROC)GetProcAddress(hInst,MAKEINTRESOURCE(1));
其中的1表示执行dumpbin时函数名前相应的序号。