一、C语言动态库
1、创建C语言动态库,并封装函数:
1)创建新工程:Win32 Dynamic-Link Library
2)添加SourceFiles文件:Cdll.c
Cdll.c中的内容:
//创建C的动态库
//_declspec(dllexport)声明导出
_declspec(dllexport)int Cdll_add(int add1,int add2){
return add1+add2;
}
_declspec(dllexport)int Cdll_sub(int sub1,int sub2){
return sub1-sub2;
}
3)编译、链接
注意:调用动态库中的函数时,须执行函数导出,库函数的导出有两种方法:
①方法一:用_declspec(dllexport)声明导出
②方法二:模块定义文件.def
如:LIBRARY 库文件名(无后缀)
EXPORTS
函数名1 @1
函数名2 @2
Build后在当前工程的Debug文件夹下生成Cdll.dll和Cdll.lib文件
这里生成的Cdll.dll文件中保存了函数的实际偏移地址和对应编号,Cdll.lib文件中并非函数的源代码而是存放的动态库dll中的函数偏移地址编号
将Cdll.dll文件置于工作区下的bin文件夹中,将Cdll.lib文件置于工作区下的lib文件夹中
2、调用C语言动态库中的函数:
①静态调用(隐式链接)
注意:静态调用(隐式链接)调用动态库中的函数时,在声明函数时须在函数原型前加_declspec(dllimport)声明导入
但我们这里是用C程序来调用C语言动态库中的函数,无须函数声明
1)创建工作工程:Win32 Console Application
2)添加SourceFiles文件:UseCdll.c
UseCdll.c中的内容:
//静态调用C的动态库
//告诉链接器去哪儿抓偏移地址编号
#pragma comment(lib,"../lib/Cdll.lib")
int main(){
int sum,sub;
sum=Cdll_add(5,3);
sub=Cdll_sub(5,3);
printf("sum=%d,sub=%d\n",sum,sub);
return 0;
}
3)编译、链接
注意:调用动态库的情况下,须将生成的dll文件(即Cdll.dll)与执行文件(即UseCdll.exe)放在同一目录下,程序才可运行
这里因为之前已将Cdll.dll文件统一置于工作区下的bin文件夹中,因此在本次调用时须修改VC6的菜单栏->工程->设置->连接->输出文件名:../bin/UseCdll.exe
②动态调用(显式链接)
二、C++动态库
1、创建C++动态库,并封装函数:
注意:调用动态库中的函数时需要执行函数导出,库函数的导出有两种方法:
①方法一:用_declspec(dllexport)声明导出
②方法二:模块定义文件.def
如:
LIBRARY 库文件名(无后缀)
EXPORTS
函数名1 @1
函数名2 @2
注:.def 文件中的注释格式为“;注释”,且为单行注释。
①“_declspec(dllexport)声明导出”方式创建C++动态库
1)创建新工程:Win32 Dynamic-Link Library
2)添加SourceFiles文件:CPPdll.cpp
CPPdll.cpp中的内容:
//用"声明导出_declspec(dllexport)"方式创建C++动态库
//函数导出
_declspec(dllexport)int CPPdll_add(int add1,int add2){
return add1+add2;
}
_declspec(dllexport)int CPPdll_sub(int sub1,int sub2){
return sub1-sub2;
}
3)编译、链接
Build后,在当前工程的Debug文件夹下生成CPPdll.dll和CPPdll.lib文件
CPPdll.dll文件保存了函数的实际偏移地址和对应编号,CPPdll.lib文件并非函数源代码而是存放的动态库dll中的函数名和偏移地址编号
将CPPdll.dll文件置于工作区下的bin文件夹中,将CPPdll.lib文件置于工作区下的lib文件夹中
②“模块定义文件.def”方式创建C++动态库
1)创建新工程:Win32 Dynamic-Link Library
2)添加SourceFiles文件:CPPdll2.def
CPPdll2.def中的内容:
LIBRARY CPPdll2
EXPORTS
CPPdll_add @1
CPPdll_sub @2
3)添加SourceFiles文件:CPPdll2.cpp
CPPdll2.cpp中的内容:
//用"模块定义文件导出.def"方式创建C++动态库
//函数导出
int CPPdll_add(int add1,int add2){
return add1+add2;
}
int CPPdll_sub(int sub1,int sub2){
return sub1-sub2;
}
4)编译、链接
Build后,在当前工程的Debug文件夹下生成CPPdll2.dll和CPPdll2.lib文件
CPPdll2.dll文件保存了函数的实际偏移地址和对应编号,CPPdll2.lib文件并非函数源代码而是存放的动态库dll中的函数名和偏移地址编号
将CPPdll2.dll文件置于工作区下的bin文件夹中,将CPPdll2.lib文件置于工作区下的lib文件夹中
2、调用C++动态库中的函数:
①静态调用(隐式链接)
1)创建工作工程:Win32 Console Application
2)添加SourceFiles文件:UseCPPdll.cpp
UseCPPdll.cpp中的内容:
//用"隐式链接"方式调用C++动态库
//对应的在创建C++动态库时使用的是"声明导出_declspec(dllexport)"方式
#include <stdio.h>
int CPPdll_add(int add1,int add2);
int CPPdll_sub(int sub1,int sub2);
//告诉编译器到哪去抓函数的导出偏移地址编号
#pragma comment(lib,"../lib/CPPdll.lib")
/*****************************************/
//C++编译器调用C语言动态库中的函数
extern "C"int Cdll_add(int add1,int add2);
extern "C"int Cdll_sub(int sub1,int sub2);
#pragma comment(lib,"../lib/Cdll.lib")
/*****************************************/
int main(){
int sum=CPPdll_add(5,6);
int sub=CPPdll_sub(5,6);
printf("sum=%d,sub=%d\n",sum,sub);
/*********************************/
sum=Cdll_add(5,8);
sub=Cdll_sub(5,8);
printf("sum=%d,sub=%d\n",sum,sub);
/********************************/
return 0;
}
3)编译、链接
注意:调用动态库的情况下,须将生成的dll文件(即CPPdll.dll)与执行文件(即UseCPPdll.exe)放在同一目录下,程序才可运行
这里因为之前已将CPPdll.dll文件统一置于工作区下的bin文件夹中,因此在本次调试时,须修改VC6的菜单栏->工程->设置->连接->输出文件名:../bin/UseCPPdll.exe
4)C++编译器在调用动态库中的函数时,须进行函数声明:
#include <stdio.h>
int CPPdll_add(int add1,int add2);
int CPPdll_sub(int sub1,int sub2);
5)C++编译器在调用C语言动态库中的函数时,会对函数名进行换名,须使用extern “C”来抑制C++编译器的换名
extern "C"int Cdll_add(int add1,int add2);
extern "C"int Cdll_sub(int sub1,int sub2);
#pragma comment(lib,"../lib/Cdll.lib")
6)这里C++编译器在调用动态库中的函数时,未见将函数导入,是因为将函数声明放在了调用程序内部,如果单独放在头文件中,则须将函数导入:
//UseCPPdll.h
_declspec(dllimport)int CPPdll_add(int add1,int add2);
_declspec(dllimport)int CPPdll_sub(int sub1,int sub2);
若调用的是C语言动态库(.c生成的.dll、.lib)中的函数,还须在int前加extern "C"
②动态调用(显式链接)
注意:C++程序在动态调用(显式链接)C++动态库中的函数时,会对函数进行换名,故推荐用“模块定义文件.def”的方式导出函数
1)创建工作工程:Win32 Console Application
2)添加SourceFiles文件:UseCPPdll2.cpp
UseCPPdll2.cpp中的内容:
//用"显示连接"方式调用C++动态库
//用此方法调用时,C++编译器会对库中函数的调用进行换名
//因此在创建C++动态库时,推荐使用"模块定义导出.def"方式将函数导出
#include <windows.h>
#include <stdio.h>
typedef int(*DLL_ADD)(int m,int n);
typedef int(*DLL_SUB)(int m,int n);
int main(){
//找到dll文件并使文件中的内容进入内存
HINSTANCE hDll=LoadLibrary("CPPdll2.dll");
printf("hDll:%d\n",hDll);
//获取函数的绝对地址并进行函数调用
DLL_ADD myAdd=(DLL_ADD)GetProcAddress(hDll,"CPPdll_add");
printf("myAdd:%p\n",myAdd);
int sum=myAdd(5,5);
printf("sum=%d\n",sum);
DLL_SUB mySub=(DLL_SUB)GetProcAddress(hDll,"CPPdll_sub");
printf("mySub:%p\n",mySub);
int sub=mySub(5,5);
printf("sub=%d\n",sub);
FreeLibrary(hDll);
return 0;
}
3)编译、链接
注意:调用动态库的情况下,须将生成的dll文件(即CPPdll2.dll)与执行文件(即UseCPPdll2.exe)放在同一目录下,程序才可运行
这里因为之前已将CPPdll2.dll文件统一置于工作区下的bin文件夹中,因此在本次调试时,须修改VC6的菜单栏->工程->设置->连接->输出文件名:../bin/UseCPPdll2.exe
动态加载:
1.定义函数指针类型:typedef ...
2.加载动态库
HMODULE LoadLibrary (
LPCTSTR lpFileName // 动态库文件名(按路径规则搜索)或者用绝对/相对路径(按指定路径加载)
);
成功返回动态库实例句柄(HINSTANCE),失败返回NULL。
3.获取函数地址
FARPROC GetProcAddress (
HMODULE hModule, // 动态库实例句柄
LPCSTR lpProcName, // 函数名(注意C++换名问题)
);
成功返回函数地址,失败返回NULL。
4.卸载动态库
BOOL FreeLibrary (
HMODULE hModule // 动态库实例句柄
);
成功返回TRUE,失败返回FALSE。
5.可执行程序调用LoadLibrary时加载动态库,调用FreeLibrary时卸载动态库
3、在C++动态库中封装类
注意:动态库中类的导出只能用“_declspec(dllexport)”方式,不能使用“模块定义文件.def”方式
1)创建新工程:Win32 Dynamic-Link Library
2)添加HeaderFiles文件:dllClass.h
dllClass.h中的内容:
#ifndef DLLCLASS_H
#define DLLCLASS_H
//宏开关
//定义关于库中类的“导出”(我们编写库时为导出)或“导入”(用户使用库时需导入)的宏定义
#ifdef DLLCLASS_EXPORTS
#define EXT_CLASS _declspec(dllexport)
#else
#define EXT_CLASS _declspec(dllimport)
#endif
class EXT_CLASS CMath{
public:
int Add(int add1,int add2);
int Sub(int sub1,int sub2);
};
#endif
类导出的宏开关:
#ifdef DLLCLASS_EXPORTS
#define EXT_CLASS _declspec(dllexport)
#else
#define EXT_CLASS _declspec(dllimport)
#endif
class EXT_CLASS CMath{
......
}
3)添加SourceFiles文件:dllClass.cpp
dllClass.cpp中的内容:
//在库中封装类
//在.cpp源文件中实现函数的功能,编译连接后生成的.dll文件用户才不可见具体的功能实现过程
//必须在头文件前打开宏开关,设为导出:_declspec(dllexport)
#define DLLCLASS_EXPORTS
#include "dllClass.h"
#include <windows.h>
#include <stdio.h>
//入口函数
BOOL WINAPI DllMain(HINSTANCE hDll,DWORD fdwReason,LPVOID pParam){
switch(fdwReason){
case DLL_PROCESS_ATTACH: //动态库被别的进程加载
//申请资源、初始化工作
printf("Loading...\n");
break;
case DLL_PROCESS_DETACH: //动态库被别的进程卸载
//善后处理
printf("UnLoading...\n");
break;
}
return TRUE;
}
int CMath::Add(int add1,int add2){
return add1+add2;
}
int CMath::Sub(int sub1,int sub2){
return sub1-sub2;
}
4)编译、链接
Build后,在当前工程的Debug文件夹下生成dllClass.dll和dllClass.lib文件
dllClass.dll文件保存了函数的实际偏移地址和对应编号,dllClass.lib文件并非函数源代码而是存放的动态库dll中的函数名和偏移地址编号
将dllClass.dll文件置于工作区下的bin文件夹中,将dllClass.lib文件置于工作区下的lib文件夹中
4、调用C++动态库中的类:
1)创建工作工程:Win32 Console Application
2)添加SourceFiles文件:UsedllClass.cpp
UsedllClass.cpp中的内容:
//调用dllClass.dll库
#include <stdio.h>
#include "../dllClass/dllClass.h"
#pragma comment(lib,"../lib/dllClass.lib")
int main(){
CMath math;
int sum=math.Add(5,2);
int sub=math.Sub(5,2);
printf("sum=%d,sub=%d\n",sum,sub);
return 0;
}
3)编译、链接
在UsedllClass.cpp未见有对宏开关的定义语句“#define DLLCLASS_EXPORTS”,故宏开关自动设为导入:_declspec(dllimport)
注意:调用动态库的情况下,须将生成的dll文件(即dllClass.dll)与执行文件(即UsedllClass.exe)放在同一目录下,程序才可运行
这里因为之前已将dllClass.dll文件统一置于工作区下的bin文件夹中,因此在本次调试时,须修改VC6的菜单栏->工程->设置->连接->输出文件名:../bin/UsedllClass.exe
三、动态库的入口函数:
入口函数不是动态库所必须的,常用于动态库内部初始化或善后处理
BOOL WINAPI DllMain (
HANDLE hDLL, // 动态库实例句柄
DWORD fdwReason, // 被调用的原因
LPVOID lpvReserved // 保留值
);
返回TRUE表示动态库加载成功,FALSE表示失败。
fdwReason取值:
DLL_PROCESS_ATTACH - 进程加载,在主线程中调用LoadLibrary
DLL_PROCESS_DETACH - 进程卸载,在主线程中调用FreeLibrary
DLL_THREAD_ATTACH - 线程加载,在子线程中调用LoadLibrary
DLL_THREAD_DETACH - 线程卸载,在子线程中调用FreeLibrary