静态链接库-----动态链接库
静态链接库:
以上是三种静态连接库的创建方法,其中Win32下的“Win32控制台应用程序”和“Win32项目”创建的连接库的其实内容是一样的,也就是一样的连接库
MFC静态规则DLL:如果静态链接到 MFC,则由于 DLL 会加载自己的私有 MFC 库代码副本,DLL 的文件大小会较大,且有可能占用额外的内存。
动态链接到 MFC 的一个缺点是必须用 DLL 发布共享 DLL:Mfcx0.dll 和 Msvcrt.dll(或类似的文件)。当不想用这些dll时使用此方法。
建立win32类型静态库的方法:
vc++如何创建并引入静态库.lib
创建篇:
用工程向导,选择Win32 Static Library 下一步把两个选项都选上,点击完成。
新建文件,进行类/模块编写。。
引入篇: 在工程的STDAFX.cpp
加入这一句:
#pragma comment(lib, “Calendar.lib”) //生成的lib文件
编译,完成!
例子:
//对应工程代码文件:static_lib.rar
建立静态库
1。建立工程:win32 static library static_lib
2。源代码: //文件:StdAfx.h
#ifndef LIB_H
#define LIB_H
extern "C" int add2(int x,int y);
#endif
//文件:stdafx.cpp
#include "stdafx.h"
int add2(int x,int y)
{
return x + y;
}
3。编译后在debug下生成.lib文件
注:静态链接库中可以再stdafx.h和stdafx.cpp中直接定义函数用标准C接口声明,即extern “C”(同一个编译器下,不用也可,它的作用只是保证导出的函数名和参数顺序的定义统一,使这个lib文件在另外一个编译器下可以使用),也可以在静态库中定义类,在用到它的应用程序中引入头文件即可使用。
在应用程序中:
首先引入该lib文件:
#pragma comment(lib,”XX.lib”)
引入相应的头文件后即可使用
动态链接库:
非MFC动态连接库、动态MFC规则DLL,MFC扩展DLL
非MFC动态连接库:
不采用MFC类库类型,其导出函数为标准的C接口,能MFC和非MFC编写的程序调用。
导出函数、导出变量、导出类,应用程序使用方法
导入导出声明:
-------------------------------------------------------------------------------------------------------------------------------
_declspec(dllexport)、_declspec(dllimport)
_declspec(dllexport)是用来导出dll中的函数、变两或者类的
_declspec(dllimport)是用来在应用程序的导入使用的(实际上用的并不多,好多情况下不用他也没关系,网上查资料说它主要是为了体现对称美,有导入就有导出,还有就是在导出类中有被static修饰的函数时,在外部调用会失败)
导入全局、静态或者类成员变量需要__declspec(dllimport)。
#define DllImport __declspec(dllimport)
DllImport int j;
__declspec(dllexport)是用于避免需要自己写DEF文件的。编译器会为被__declspec(dllexport)修饰的函数自动添加一个导出函数入口。如果你在其他模块中包含__declspec(dllexport)的头文件,这些项目的导出表中也会生成一个同名导出函数。
用_declspec(dllimport)只是为了良好的程序设计
注:
__declspec用于指定所给定类型的实例的与Microsoft相关的存储方式。其它的有关存储方式的修饰符如static与extern等是C和C++语言的ANSI规范,而__declspec是一种扩展属性的定义。扩展属性语法简化并标准化了C和C++语言关于Microsoft的扩展。
用法:__declspec ( extended-decl-modifier )
extended-decl-modifier参数如下,可同时出现,中间有空格隔开:
align (C++)
dllimport
dllexport
…
__declspec关键字应该出现在简单声明的前面。对于出现在*或&后面或者变量声明中标识符的前面的__declspec,编译器将忽略并且不给出警告。
例子:align:
格式:__declspec(align(n)) declarator
其中,n是对齐参数,其有效值是2的整数次幂(从1到8192字节),如2,4,8,16,32或64。参数declarator是要设置对齐方式的数据。
使用__declspec(align(n))来精确控制用户自定义数据的对齐方式。你可以在定义struct,union,class或声明变量时使用__declspec(align(n))。
-------------------------------------------------------------------------------------------------------------------------------
extern “C”的作用:
<一>:
C中函数编译后命名会在函数名前加以"_",比如add函数编译成obj文件时的实际命名为_add,而c++命名则不同,为了实现函数重载同样的函数名add因参数的不同会被编译成不同的名字 。这就是为什么当用C写的一个.h文件和.c文件要放到C++写的程序中使用时会报编译错误的原因。解决办法就是在C++调用C中的文件时调用extern “C”修饰重新声明函数,
<二>替代连接说明:
如果在c++中编写一个程序需要用到c的库,那该如何?如果这样声明一个c函数:
void f(int a,char b);
c++编译器就会将这个名字变成相应的修饰名,比如:?f@@YAXHD@Z.
然而,c编译器编译的库的内部函数名(连接器使用)是完全不同的.这样,当c++连接器连接c的函数库时,将会产生内部使用函数不匹配.
故,c++中提供了一个替代连接说明(alternate linkage specification),它是通过重载extern关键字来实现的.
extern后跟一个字符串来指定想声明的函数的连接类型,后面是函数声明,比如:
extern "C" void f(int a,char b);
这样,就是告诉编译器是c连接,这样就不会转换函数名了.此例中,编译后的内部函数名是_f.
-------------------------------------------------------------------------------------------------------------------------------
_stdcall的作用:
这是一种函数调用约定,什么是函数调用约定?
函数调用约定是指当调用一个函数时,参数会被传递给被调用函数和返回值会被传递给调用参数,函数调用约定就是描述参数是怎么被传递的和有谁平衡堆栈的,当然还有返回值。
函数调用约定有:__stdcall,__cdecl,__fastcall,__thiscall,__nakedcall,__pascal
按照参数传递顺序分类:
1. 从右到左入栈:__stdcall、__cdecl、__thiscall(都是两个下划线)
2. 从左到右入栈:__pascal、__fastcall(都是两个下划线)
堆栈清理:
1. 调用者清除栈
2. 被调用函数返回后清楚栈
_stdcall是Wind32 API函数绝大多数采用的调用约定方式,WINAPI也只是_stdcall的一个别名而已。
__stdcall调用约定的主要特征是:
1、参数是从右往左传递的,也是放在堆栈中。
3、在函数名的前面用下划线修饰,在函数名的后面由@来修饰并加上栈需要的字节数的空间
为什么要有函数的调用约定?
一般是在当跨语言编程时使用,不同语言使用的函数调用约定方式不同
C/C++的缺省调用方式是__cdecl,VC使用的默认调用约定方式也是__cdecl,windows API用的是__stdcall调用方式,在dll中导出函数时为了跟windows API保持一直,建议使用__stdcall调用方式。
__stdcall和__cdecl说明:
1、__cdecl
__cdecl调用约定又称为 C 调用约定,是 C/C++ 语言缺省的调用约定。参数按照从右至左的方式入栈,函数本身不清理栈,此工作由调用者负责,返回值在EAX中。由于由调用者清理栈,所以允许可变参数函数存在,如int sprintf(char* buffer,const char* format,...);。
2、__stdcall
__stdcall 很多时候被称为 pascal 调用约定。pascal 语言是早期很常见的一种教学用计算机程序设计语言,其语法严谨。参数按照从右至左的方式入栈,函数自身清理堆栈,返回值在EAX中。
注:
C 语言有 __cdecl、__stdcall、__fastcall、naked、__pascal。
C++ 语言有 __cdecl、__stdcall、__fastcall、naked、__pascal、__thiscall,比 C 语言多出一种__thiscall 调用方式。
参考:http://blog.csdn.net/xt_xiaotian/article/details/5363633
-------------------------------------------------------------------------------------------------------------------------------
导出函数:
建立空白项目(或者使用Wizard自动生成),使用默认的DllMain,或者手写DllMain,建立一个.h的头文件在这个头文件中声明导出函数(用__declspec(dllexport)修饰),然后再.cpp中定义。或者直接在.cpp文件中定义加导出。还有一种办法是新建一个定义文档.def文件,def文件的格式如下:
LIBRARY MyDll
EXPORTS
MyFunc @1
要使用注释时,使用“;”分号,并且换行,不能与代码行处于同一行,使用此方法导出函数时,在使用此dll的应用程序中需要使用__declspec(dllimport)。建议使用__declspec(dllexport)和__declspec(dllimport)来导出函数。函数的掉用分为隐式调用和显式调用
隐式连接:
隐式链接就是在程序开始执行时就将DLL文件加载到应用程序当中
#include”MyDll.h”
#pragam comment(lib,”MyDll.lib”)
Void main()
{
//调用dll中导出的函数
MyFunc(iTest);
Return;
}
显式连接:
显式链接是应用程序在执行过程中随时可以加载DLL文件,也可以随时卸载DLL文件,这是隐式链接所无法作到的,所以显式链接具有更好的灵活性,对于解释性语言更为合适.
Void main()
{
Typedefint (*pfnMyFunc)(int iTest);
HISTANCEHInst = LoadLibrary(“MyDll.dll”);
If(HInst ==NULL)
{
AfxMessageBox(“加载dll失败“);
Return;
}
pfnMyFunc MyFunc= (pFnMyFunc)GetProcAddress(HInst,”MyFunc”);
MyFunc(1);
freeLibrary(HIst);
Return;
}
使用GetProcAddress()函数时,可以利用MAKEINTRESOURCE()函数直接使用DLL中函数出现的顺序号
导出变量:
Dll中的全局变量可以被调用进程访问,Dll也可以访问调用进程中的全局变量。
1.用def文件导出变量的方法:
LIBRARY MyDll
EXPORTS
;MyParam CONSTANT
MyParam DATA
现在大多使用MyParam DATA导出
用__declspec进行导出
在全局变量所在的头文件中:__declspec(dllexport) extern int iTest;来导出变量。
变量的调用:
隐式调用:
用extern导入变量
#pragam comment(lib,”MyDll.lib”)
Extern int iTest;
Void main(void)
{
Printf(“%s”,*(int*)iTest);
Return;
}
用此种方法导出的并不是变量本身,而是变量的地址,所以必须使用强制类型转换。在给变量赋值时注意不能直接赋值。
使用__declspec(dllimport)导入
这是导入变量的更好的办法。
#pragam comment(lib,”MyDll.lib”)
Extern int __declspec(dllimport) iTest;
Void main(void)
{
Printf(“%s”,iTest);
Return;
}
这种情况下,导出的是变量本身,而不在是其地址,可以直接对其进行赋值等操作,看以看作是调用进程内的全局变量一样。
显示调用:
Void main()
{
IntiTest;
HINSTANCEHInst = LoadLibrary(“MyDll.dll”);
If(HInst == NULL)
{
AfxMessageBox(“加载dll失败“);
Return;
}
iTest = *(int *)GetProcAdress(HInst,”iTest”);
printf(“%d”,iTest);
freeLibrary(HIst);
return;
}
由此获得全局变量的地址可以对其进行操作
导出类:
与导出函数差别不大
在dll中只需要在要导出的类前加上(其实是宏定义,这里为了方便都是只写了一个)__declspec(dllexport)即可。在调用进程中使用时只能使用隐式连接,使用方法与函数的隐式连接一样。
为什么不能使用.def文件导出类或使用显式连接方式使用dll哪?
因为C++的链接器对symbol进行了修改,也就是连接时对函数名进行了修改,因为C++有重载等机制。一般从dll中导出的类中的成员都不是是我们在类中定义的函数名,因为我们没有为类中的成员指定命名方式,导出的类中的成员函数,类似于乱码方式。比如:
int Test1(char *var1,unsigned long) -----“?Test1@@YGHPADK@Z”
void Test2() -----“?Test2@@YGXXZ”
1. "?"标识函数名的开始,后跟函数名
2. 函数名后面以"@@YG"标识参数表的开始,后跟参数表
3.参数表以代号表示:
X——void,
D——char,
E——unsigned char,
F——short,
H——int,
I——unsigned int,
J——long,
K——unsigned long,
M——float,
N——double,
_N——bool,
....
PA——表示指针,后面的代号表明指针类型,如果相同类型的指针连续出现,以"0"代替,一个"0"代表一次重复;
4.参数表的第一项为该函数的返回值类型,其后依次为参数的数据类型,指针标识在其所指数据类型前;
5.参数表后以"@Z"标识整个名字的结束,如果该函数无参数,则以"Z"标识结束
-------------------------------------------------------------------------------------------------------------------------------
MFC的规则DLL:
继承自CWinApp类,但其无消息循环,能被MFC和非MFC编写的程序调用(任何语言编写的程序),这类DLL动态链接到 MFC DLL(也称作共享 MFC DLL),它的运行依赖MFCx0.dll 和 Msvcrt.dll(或类似的文件)。
在使用MFC共享库的时候,默认情况下,MFC使用主应用程序的资源句柄来加载资源模块,这样当DLL中的资源ID与应用程序中的ID相同的时候(即资源重复的问题),系统可能不能正确的获取资源。
如果所调用的dll中使用了资源,则可以使用静态连接的方式(连接Lib文件),这样便可以避免资源重复的问题。当然也可以使用动态连接,但是需要进行模块切换。
当所调用的dll中没有使用资源时,他的动态连接便于win32dll的动态连接无异。
模块切换的三种方法:
1.在dll的接口函数中使用
AFX_MANAGE_STAGE(AfxGetStaticModuleStatic());
即在函数入口处加上该函数进行模块的切换
2.在DLL接口函数中使用:
AfxGetResourceHandle();
AfxSetResourceHandle(HINSTANCE xxx);
如下列代码:
void ShowDlg(void)
{
HINSTANCE save_hInstance=AfxGetResourceHandle();
AfxSetResourceHandle(theApp.m_hInstance);
CDialog dlg(IDD_DLL_DIALOG);
dlg.DoModal;
AfxSetResourceHandle(save_hInstance);
}
3. 由调用DLL的应用程序自身切换
资源模块的切换除了可以由DLL接口函数完成以外,由应用程序自身也能完成。
//获取exe模块句柄
HINSTANCE exe_hInstance= GetModuleHandle(NULL);
//或者HINSTANCEexe_hInstance = AfxGetResourceHandle();
//获取DLL模块句柄
HINSTANCE dll_hInstance =GetModuleHandle("SharedDll.dll");
//切换状态
AfxSetResourceHandle(dll_hInstance);
//调用DLL函数
ShowDlg();
//资源模块恢复
AfxSetResourceHandle(exe_hInstance);
导出类:AFX_EXT_CLASS来修饰类的声明,AFX_EXT_CLASS实际就是对__declspec的宏定义,其他的使用与非MFC dll一样。
MFC扩展DLL
MFC扩展 DLL 是通常实现从现有 Microsoft 基础类库类派生的可重用类的 DLL。扩展 DLL 是使用 MFC 动态链接库版本(也称作共享 MFC 版本)生成的。只有用共享 MFC 版本生成的 MFC 可执行文件(应用程序或规则 DLL)才能使用扩展 DLL。使用扩展 DLL,可以从 MFC 派生新的自定义类,然后将此“扩展”版本的 MFC 提供给调用 DLL 的应用程序。
使用时和MFC规则dll差不多