VC++ dll 动态链接库
静态库:函数和数据被编译进一个二进制文件(.LIB)。在编译连接可执行文件时,连接器从库中复制这些函数和数据并把它们和应用程序的其他模块组合起来创建最终的exe文件。
动态库:往往需要两个文件,一个引入库lib和一个dll。.lib 引入(输入)库文件,包含导出的变量或函数的符号名,dll文件包含实际的函数和数据。在链接时,只需要链接引入库,dll中的函数代码和数据并不复制到可执行文件中,到运行的时候再去加载,访问dll中导出的函数。
DLL可以减少内存的使用。系统给每个进程分配4G虚拟地址空间,当我们的一个进程需要使用动态链接库时,就会将动态链接库的代码页面映射到这个进程的地址空间中,如果有另一个进程也使用这个dll,也是同样的方法,但是他们共用同一块动态链接库虚拟地址空间。
在VC的bin中VCVARS32.bat 来在当前cmd中设置vc的环境变量 直接把它拖入cmd中就可以运行,只对这一个窗口生效
dumpbin -exports dlls.dll 查看动态链接库中的哪些变量和函数是导出的
导出函数 _declspec(dllexport) int add(int a, int b); 生出两个新文件
.lib 引入(输入)库文件,包含导出的变量或函数的符号名
.exp 输出库文件
C++为了实现函数重载,自己会更改我们定义的函数的名字,不同的编译器对函数名进行不同的改编,如果使用不同的编译器就会出现不兼容的问题。
在调用dll库函数的代码中可以声明
_declspec(dllimport) int add(int a, int b); 说明函数是动态链接库中引入的函数,从而效率比用extern int add(int a, int b);高
从而在下面的代码中才能调用
CString str;
str.Format(L"5+3=%d",add(5,3));
MessageBox(str);
TEXT is a macro. If _UNICIODE is defined TEXT becomes L:
The L means the string is wchar_t characters and not char characters.
If _UNICODE is not defined, the TEXT becomes nothing.
This allows your code to switch between ASCII and UNICODE without having to change the code.
把引入库文件.lib文件也要放到调用的exe文件工程目录下,因为它里面包含了导出函数的符号名。lib中没有程序。并在项目属性-连接中输入库中写入dlls.lib
dumpbin -imports **.exe 查看exe文件的导入信息,它用到的库文件如dll文件。
还要将dll文件放到可执行文件目录中,不然函数代码找不到了,只有函数名。
一般需要定义个头文件来来声明dll中的函数,方便用户使用
动态调用dll 不需要.h头文件和lib文件。只是在你需要的时候才加载到内存中,在exe文件中用dumpbin查看就没有这个dll文件。如果是隐式连接,则会在创建进程(运行)时把所有用到的dll函数都加载进进程,占用的资源较多。
HMODULE LoadLibrary("dll文件")返回一个模块句柄,参数为dll文件
FARPROC GetProcAddress( //得到动态链接库导出函数的地址
HMODULE hModule, //动态链接库模块句柄
LPCSTR lpProcName //函数名称,如果参数为一个序号,则必须在低位字中,高位必须为0
)
HINSTANCE hInst; //定义个句柄
hInst = LoadLibrary("dlls.dll"); //得到动态链接库的模块句柄
typedef int (*ADDPROC)(int a, int b); //定义一个函数指针,用来接收GetPro
cAddress()返回的地址,ADDPROC是一个函数指针类型
ADDPROC Add = (*ADDPROC)GetProcAddress(hInst,"add");
//ADDPROC Add = (ADDPROC)GetProcAddress(hInst,MAKEINTRESOURCE(1)); //用序号来确定导出函数
if(!Add) { MessageBoxA("没有得到函数地址!"); }
CString str;
str.Format(L"5+3=%d",Add(5,3)); //调用函数指针
MessageBox(str);
DllMain 动态链接库的入口函数(可选),不要做太复杂调用,也许你的dll加载的比较早,而user32.dll还没没有加载,如果在这里用到了user32.dll的函数,就会出错。
BOOL WINAPI DllMain(
HINSTANCE hinstDLL, //当动态链接库被加载时,它的模块句柄被传到这里
DWORD fdwReason, //是个标记,说明这个入口函数为什么被调用,在哪种情况下被调用
LPVOID lpvReserved //保留字
);
FreeLibrary()减少调用dll模块的计数,如果这个计数为0时,就释放这个动态链接库,在使用完dll之后用来释放
调用约定:如果在定义是采用的是_stdcall标准调用约定,那么在使用的时候也要为标准调用约定
CString str;
str.Format(L"5+3=%d",_stdcall Add(5,3)); //调用函数指针
MessageBox(str);
动态链接库头文件dlls.dll
#ifdef DLLS_API
#else
#define DLLS_API extern "C" _declspec(dllimport)
#endif
//判断是否定义,由于在dll实现中定义过且定义为导出
//在使用dll库的文件中是并没有定义这个宏 所以这个宏应该为引入
//extern "C" 保证C语言兼容,在导出时函数名称不改变,但是不能用于类的成员函数
//_stdcall标准调用约定就是winAPI调用,和C调用不同 导出的函数名称为 函数名@函数参数占用字节数
//delphi 中使用这种调用方式
DLLS_API int add(int a, int b);
DLLS_API int subtract(int a, int b);
class DLLS_API Point{//类被导出,其中的所有函数都被导出
public:
void output(int x, int y);
//对于private的成员是无法访问的
};
/**
class Point1{//调用时仍然可以创建这个类的对象
public:
DLLS_API void output(int x, int y); //只导出这个函数
//对于private的成员是无法访问的
void input(char* s); //不导出这个函数
};
**/
动态链接库实现dlls.cpp
#define DLLS_API extern "C" _declspec(dllexport)
#include "dlls.h"
#include <windows.h>
#include <stdio.h>
int add(int a, int b){
return a+b;
}
int subtract(int a, int b){
return a-b;
}
/**
//显示坐标值函数
void Point::output(int x, int y){
//得到调用这个dll的窗口的句柄
HWND hwnd = GetForegroundWindow();
HDC hdc = GetDC(hwnd); //获得上下文
char buf[20];
memset(buf,0,20);
sprintf(buf,"x=%d,y=%d",x,y);
TextOutA(hdc,0,0,buf,strlen(buf));
ReleaseDC(hwnd,hdc);
}
**/
//模块定义文件dlls.def 添加到工程中 用来指定导出函数的名字,不同的编译器导出名字会不同
LIBRARY dlls //必须和dll文件名一致
EXPORTS
add //导出函数名称[=内部定义的函数名称]
subtract
客户端调用 要先#include 头文件,把.dll和.lib文件都放在工程目录下
void CTestDllDlg::OnBnClickedAdd()
{
// TODO: 在此添加控件通知处理程序代码
CString str;
str.Format(L"5+3=%d",add(5,3));
MessageBox(str);
}
void CTestDllDlg::OnBnClickedShowno()
{
// TODO: 在此添加控件通知处理程序代码
Point pt;
pt.output(3,5);
}
动态库:往往需要两个文件,一个引入库lib和一个dll。.lib 引入(输入)库文件,包含导出的变量或函数的符号名,dll文件包含实际的函数和数据。在链接时,只需要链接引入库,dll中的函数代码和数据并不复制到可执行文件中,到运行的时候再去加载,访问dll中导出的函数。
DLL可以减少内存的使用。系统给每个进程分配4G虚拟地址空间,当我们的一个进程需要使用动态链接库时,就会将动态链接库的代码页面映射到这个进程的地址空间中,如果有另一个进程也使用这个dll,也是同样的方法,但是他们共用同一块动态链接库虚拟地址空间。
在VC的bin中VCVARS32.bat 来在当前cmd中设置vc的环境变量 直接把它拖入cmd中就可以运行,只对这一个窗口生效
dumpbin -exports dlls.dll 查看动态链接库中的哪些变量和函数是导出的
导出函数 _declspec(dllexport) int add(int a, int b); 生出两个新文件
.lib 引入(输入)库文件,包含导出的变量或函数的符号名
.exp 输出库文件
C++为了实现函数重载,自己会更改我们定义的函数的名字,不同的编译器对函数名进行不同的改编,如果使用不同的编译器就会出现不兼容的问题。
在调用dll库函数的代码中可以声明
_declspec(dllimport) int add(int a, int b); 说明函数是动态链接库中引入的函数,从而效率比用extern int add(int a, int b);高
从而在下面的代码中才能调用
CString str;
str.Format(L"5+3=%d",add(5,3));
MessageBox(str);
TEXT is a macro. If _UNICIODE is defined TEXT becomes L:
The L means the string is wchar_t characters and not char characters.
If _UNICODE is not defined, the TEXT becomes nothing.
This allows your code to switch between ASCII and UNICODE without having to change the code.
把引入库文件.lib文件也要放到调用的exe文件工程目录下,因为它里面包含了导出函数的符号名。lib中没有程序。并在项目属性-连接中输入库中写入dlls.lib
dumpbin -imports **.exe 查看exe文件的导入信息,它用到的库文件如dll文件。
还要将dll文件放到可执行文件目录中,不然函数代码找不到了,只有函数名。
一般需要定义个头文件来来声明dll中的函数,方便用户使用
动态调用dll 不需要.h头文件和lib文件。只是在你需要的时候才加载到内存中,在exe文件中用dumpbin查看就没有这个dll文件。如果是隐式连接,则会在创建进程(运行)时把所有用到的dll函数都加载进进程,占用的资源较多。
HMODULE LoadLibrary("dll文件")返回一个模块句柄,参数为dll文件
FARPROC GetProcAddress( //得到动态链接库导出函数的地址
HMODULE hModule, //动态链接库模块句柄
LPCSTR lpProcName //函数名称,如果参数为一个序号,则必须在低位字中,高位必须为0
)
HINSTANCE hInst; //定义个句柄
hInst = LoadLibrary("dlls.dll"); //得到动态链接库的模块句柄
typedef int (*ADDPROC)(int a, int b); //定义一个函数指针,用来接收GetPro
cAddress()返回的地址,ADDPROC是一个函数指针类型
ADDPROC Add = (*ADDPROC)GetProcAddress(hInst,"add");
//ADDPROC Add = (ADDPROC)GetProcAddress(hInst,MAKEINTRESOURCE(1)); //用序号来确定导出函数
if(!Add) { MessageBoxA("没有得到函数地址!"); }
CString str;
str.Format(L"5+3=%d",Add(5,3)); //调用函数指针
MessageBox(str);
DllMain 动态链接库的入口函数(可选),不要做太复杂调用,也许你的dll加载的比较早,而user32.dll还没没有加载,如果在这里用到了user32.dll的函数,就会出错。
BOOL WINAPI DllMain(
HINSTANCE hinstDLL, //当动态链接库被加载时,它的模块句柄被传到这里
DWORD fdwReason, //是个标记,说明这个入口函数为什么被调用,在哪种情况下被调用
LPVOID lpvReserved //保留字
);
FreeLibrary()减少调用dll模块的计数,如果这个计数为0时,就释放这个动态链接库,在使用完dll之后用来释放
调用约定:如果在定义是采用的是_stdcall标准调用约定,那么在使用的时候也要为标准调用约定
CString str;
str.Format(L"5+3=%d",_stdcall Add(5,3)); //调用函数指针
MessageBox(str);
动态链接库头文件dlls.dll
#ifdef DLLS_API
#else
#define DLLS_API extern "C" _declspec(dllimport)
#endif
//判断是否定义,由于在dll实现中定义过且定义为导出
//在使用dll库的文件中是并没有定义这个宏 所以这个宏应该为引入
//extern "C" 保证C语言兼容,在导出时函数名称不改变,但是不能用于类的成员函数
//_stdcall标准调用约定就是winAPI调用,和C调用不同 导出的函数名称为 函数名@函数参数占用字节数
//delphi 中使用这种调用方式
DLLS_API int add(int a, int b);
DLLS_API int subtract(int a, int b);
class DLLS_API Point{//类被导出,其中的所有函数都被导出
public:
void output(int x, int y);
//对于private的成员是无法访问的
};
/**
class Point1{//调用时仍然可以创建这个类的对象
public:
DLLS_API void output(int x, int y); //只导出这个函数
//对于private的成员是无法访问的
void input(char* s); //不导出这个函数
};
**/
动态链接库实现dlls.cpp
#define DLLS_API extern "C" _declspec(dllexport)
#include "dlls.h"
#include <windows.h>
#include <stdio.h>
int add(int a, int b){
return a+b;
}
int subtract(int a, int b){
return a-b;
}
/**
//显示坐标值函数
void Point::output(int x, int y){
//得到调用这个dll的窗口的句柄
HWND hwnd = GetForegroundWindow();
HDC hdc = GetDC(hwnd); //获得上下文
char buf[20];
memset(buf,0,20);
sprintf(buf,"x=%d,y=%d",x,y);
TextOutA(hdc,0,0,buf,strlen(buf));
ReleaseDC(hwnd,hdc);
}
**/
//模块定义文件dlls.def 添加到工程中 用来指定导出函数的名字,不同的编译器导出名字会不同
LIBRARY dlls //必须和dll文件名一致
EXPORTS
add //导出函数名称[=内部定义的函数名称]
subtract
客户端调用 要先#include 头文件,把.dll和.lib文件都放在工程目录下
void CTestDllDlg::OnBnClickedAdd()
{
// TODO: 在此添加控件通知处理程序代码
CString str;
str.Format(L"5+3=%d",add(5,3));
MessageBox(str);
}
void CTestDllDlg::OnBnClickedShowno()
{
// TODO: 在此添加控件通知处理程序代码
Point pt;
pt.output(3,5);
}