1。dll的建立
选择win32的dll lib工程, 建立一个cpp文件,代码如下:
_declspec(dllexport) int add(int a, int b)
{
return a+b;
}
注意前面的标识_declspec(dllexport),表示dll的输出函数。每个输出的函数都要用这个进行标识。
可以进行dll所在目录用dumpbin -exports dll1.dll 查看dll的输出函数信息。可以看到dll有输出,但名字并不是add,
而是?add@@YAHHH@Z 这儿是因为C++编译器对函数名字进行了改编。
2。dll的隐式调用(静态调用)
隐式调用在引用dll文件的lib文件。(把dll1.dll ,dll1.lib两个文件复制到dlltest所在主目录)
在工程setting属性中,link标签页, 填入 dll1.lib
然后测试代码如下:
extern add(int a, int b);void CTestDll::OnBtnTest()
{
int iRet = add(5, 10);
CString strMsg;
strMsg.Format("%d", iRet);
AfxMessageBox(strMsg);
}
可以看到dll中的函数已经被执行了。
vs工具库中depends工具可以选择一个执行文件,查看这个执行文件所引用的dll文件。
另外,对add函数的声明是extern add(int a, int b); 这种不是专用的声明方式,可以用下面的声明
_declspec(dllimport) add(int a, int b);
从而指明函数是从dll中取出的,以增加程序的执行效率。
3。隐式引用,头文件的使用
带头文件的dll的编写:
dll2.h:
_declspec(dllimport) add(int a, int b);
这里是dllimport,是对调用文件而言的。
dll2.cpp:
_declspec(dllexport) int add(int a, int b)
{
return a+b;
}
带头文件的dll的调用:
同样,dll2.lib要引入
调用文件中,不用声明dll2导出的函数, 只在引用头文件即可,如下:
#i nclude "../dll2/dll2.h"
调用函数不变。
4。隐式引用,头文件的使用时,利用宏简化编写
dll3.h:
#ifdef DLL_API
#else
#define DLL_API _declspec(dllimport)
#endifDLL_API add(int a, int b);
//说明:如果DLL_API已经定义,不操作,否则,声明为DLL_API _declspec(dllimport),使用在头文件。
//由于在cpp文件中要引入dll3.h,在dll3.cpp中声明了#define DLL_API _declspec(dllexport), 使用DLL_API
//可以在头文件,源文件中切换dll3.cpp
#define DLL_API _declspec(dllexport)
#i nclude "dll3.h"int add(int a, int b)
{
return a+b;
}
5。类的输出
dll3.h
#ifdef DLL_API
#else
#define DLL_API _declspec(dllimport)
#endifDLL_API int add(int a, int b);
class DLL_API point
{
public:
void output(int a);
};
dll3.cpp
#define DLL_API _declspec(dllexport)
#i nclude "dll3.h"
#i nclude <Windows.h>
#i nclude <iostream.h>
#i nclude <stdio.h>int add(int a, int b)
{
return a+b;
}void point::output(int a)
{
HWND hwnd = GetForegroundWindow();
HDC hdc = GetDC(hwnd);
char buf[20];
memset(buf, 0, sizeof(buf));
sprintf(buf, "%d", a);
TextOut(hdc, 0, 0, buf, sizeof(buf));
ReleaseDC(hwnd, hdc);
}
关键在于类的声明:
class DLL_API point
{
public:
void output(int a);
};
注意在class后面加声明标志
6。输出类中的部分函数
dll3.h
#ifdef DLL_API
#else
#define DLL_API _declspec(dllimport)
#endifDLL_API int add(int a, int b);
class /*DLL_API*/ point
{
public:
DLL_API void output(int a);
void output2(int a);
};
只是在类声明时,把对类的输出去掉,然的在具体要输出的函数的前面加上输出声明标志,实现、调用都没有变化。
7。防止函数名字改编
因为C++编译器对dll导出函数的名字进行了改编,C编译器调用时就可能出错。这里提供一个防止名字改编的方法。
这个解决方法解决C++, C互相调用的问题。
dll4.h
#ifdef DLL_API
#else
#define DLL_API extern "C" _declspec(dllimport)
#endifDLL_API int add(int a, int b);
dll4.cpp
#define DLL_API extern "C" _declspec(dllexport)
#i nclude "dll4.h"int add(int a, int b)
{
return a+b;
}
可以看到,只要在导出函数的声明和实现处加上 extern "C"
但是有个缺点,就是不能导出类及类中的函数
注意这儿的"C" 为大写
8。改变导出函数的调用约定
dll4.h
#ifdef DLL_API
#else
#define DLL_API extern "C" _declspec(dllimport)
#endifDLL_API int _stdcall add(int a, int b);
dll4.cpp
#define DLL_API extern "C" _declspec(dllexport)
#i nclude "dll4.h"int _stdcall add(int a, int b)
{
return a+b;
}
可以看到在函数名的前面加上 _stdcall 即改变了函数的调用约定:标准调用
当vc写的dll, 由dlephi调用时,就要加上_stdcall, 以符合object pascall 的约定。
这儿要注意,如果改变了调用约定,即使使用extern "C" 也会发生名字改编
9。利用模块定义文件防止名字改编
添加一个dll4.def文件到源文件, 内容如下:
LIBRARY dll4
EXPORTS
add
可以看到EXPORTS下面即为输出的函数名。可以有下面的写法:
LIBRARY dll4
EXPORTS
export_add=add
export_add为要输出的函数名,add为源文件中的函数名
但是用模块定义文件定义后,输出的dll, 如果静态调用则会找不到函数入口点。
此时应该如何静态调用?
但至少可以动态调用
10。动态调用dll
dll文件的编写没有变化。(有dll4.def文件防止发生名字改编)
调用如下:
//动态调用
HINSTANCE hIst;
hIst = LoadLibrary("dll4.dll");
typedef int (_stdcall *ADDPROC)(int a, int b);
ADDPROC Add = (ADDPROC)GetProcAddress(hIst, "add"); if(!Add)
{
AfxMessageBox("get address error.");
return;
}
int iRet = Add(5, 10);
CString strMsg;
strMsg.Format("%d", iRet);
AfxMessageBox(strMsg);hIst = LoadLibrary("dll4.dll"); 是加载动态链接库
typedef int (_stdcall *ADDPROC)(int a, int b); 定义一个和dll导出函数一至的原型函数
ADDPROC Add = (ADDPROC)GetProcAddress(hIst, "add"); 根据函数名,得到函数地址
注意:因为dll改变了调用约定,所以在声明函数原型时,也加上了_stdcall ,否则应该如下:
typedef int (*ADDPROC)(int a, int b);
注意:如果dll导出函数发生了名字改编,再用dll中函数的名字则会出错。要用dumpbin中看到的名字。或者用
ADDPROC Add = (ADDPROC)GetProcAddress(hIst, MAKEINTRESOURCE(1));
根据序号来取得函数地址,但这种方法不太好。
在不需要使用动态链接库时,可以调用FreeLibrary(hIst);来释放动态链接库
所以在以后dll的使用中可以:
a. 使用def文件防止名字改编
b. 使用动态调用