动态链接库的总结

 

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)
#endif

DLL_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)
#endif

DLL_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)
#endif

DLL_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)
#endif

DLL_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)
#endif

DLL_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. 使用动态调用

你可能感兴趣的:(动态链接库的总结)