在学习导入表、导出表之前,由于导入导出表需要用到静态链接库或者动态链接库,所以先来学习一下
-
我们学过如果一个功能的代码需要被我们自己反复使用,即复用,那么可以定义成函数
-
现在如果要多人实现代码的复用,比如我写的一段代码要给别人用,不可能直接把代码复制一份发给别人,这样如果很多人要使用,就不方便,所以可以通过下面的两种方式实现:
-
静态链接库
-
动态链接库
-
静态链接库
一,创建静态链接库
1、在VC6中创建项目:Win32 Static Library
2、在项目中创建两个文件:xxx.h 和 xxx.cpp
xxx.h文件:
#if !defined(AFX_TEST_H__DB32E837_3E66_4BE7_B873_C079BC621AF0__INCLUDED_)
#define AFX_TEST_H__DB32E837_3E66_4BE7_B873_C079BC621AF0__INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
int Plus(int x, int y);
int Sub(int x, int y);
int Mul(int x, int y);
int Div(int x, int y);
#endif
xxx.cpp文件:
int Plus(int x, int y)
{
return x+y;
}
int Sub(int x, int y)
{
return x-y;
}
int Mul(int x, int y)
{
return x*y;
}
int Div(int x, int y)
{
return x/y;
}
二、使用静态链接库:
方式一: 将xxx.h 和 xxx.lib复制到要使用的项目中 在需要使用的文件中包含:#include "xxx.h" 在需要使用的文件中包含:#pragma comment(lib, "xxx.lib")
方式二:
将xxx.h 和 xxx.lib复制到要使用的项目中 在需要使用的文件中包含:#include "xxx.h"
静态链接库的缺点:
使用静态链接生成的可执行文件体积较大,造成浪费 我们常用的printf、memcpy、strcpy等就来自这种静态库
三,静态链接库的特点
我们发现静态链接库是一种假的模块化,我们前面学过,一个PE中可以包含很多模块(.dll),可以通过OD中的E来查看。但是此时使用静态链接库,虽然一个程序中使用了别的文件中的函数,有点类似于模块化的感觉,但是我们却发现其实这个程序是没有使用其他的模块的。所以静态链接库和模块化是没有什么关系的
-
那么为什么使用静态链接库的程序,就可以使用库中的函数了呢?
我们找一下我们使用的Plus函数在哪里?
可以发现使用的函数所在的内存地址为0x401190,我们前面学过PE文件了,一般一个.exe文件的代码块差不多就是从偏移地址为1000多的地方开始的,所以此时这个函数就属于.exe文件中的一部分了,而不是使用其他模块.dll中的函数
-
总结:静态链接库的特点就是直接将要用到的函数在编译的时候就直接放到.exe文件中代码块中去了
-
这恰恰也是静态链接库的缺点,比如如果我们现在修改了.lib中的函数,那么此时使用此函数的.exe文件必须要重新编译一次,将修改过的函数重写编译到.exe文件中才能使用修改过后的函数
动态链接库
1.创建DLL
1、源文件中:
int __stdcall Plus(int x,int y)
{
return x+y;
}
int __stdcall Sub(int x,int y)
{
return x-y;
}
int __stdcall Mul(int x,int y)
{
return x*y;
}
int __stdcall Div(int x,int y)
{
return x/y;
}
2、头文件中
extern "C" _declspec(dllexport) __stdcall int Plus (int x,int y);
extern "C" _declspec(dllexport) __stdcall int Sub (int x,int y);
extern "C" _declspec(dllexport) __stdcall int Mul (int x,int y);
extern "C" _declspec(dllexport) __stdcall int Div (int x,int y);
、
extern:表示这是一个全局函数,可以供各个其他函数调用
“C”:指的是此函数按照C语言的方式进行编译、链接。为什么要按照C的方式导出呢?因为不指定的话,编译器可能会理解成通过C++的方式导出,但是C++允许函数的重载,而编译器害怕导出后出现相同的名字的函数,但是此时如果有一个C程序要使用,C不支持重载,所以可能会乱套,故编译器会自动在导出.dll的时候把当中定义的所有函数名都改了,改成不同的,这样C程序使用时就不会出现同名的情况
假如现在不加"C",我们看看导出的.dll文件中的函数名是什么样的:
使用VC6自带的工具:depends查看编译后导出的dllTest.dll(可以查看PE文件依赖于哪些动态链接库以及使用了动态链接库中的哪些接口)
假如现在按照C语言的方式导出,就不会改名字,定义的函数是什么名字导出的.dll中就是什么名字
_declspec(dllexport):指告诉编译器此函数为导出函数,可以供别人使用(一定要写的,固定格式)
__stdcall:就是函数的调用约定使用stdcall,我们前面学过,stdcall调用约定的函数会使用内平栈,如果不加VC默认使用cdcall,外平栈。建议如果使用动态链接库最后都使用stdcall的调用约定导出.dll
2.使用DLL
方法一–隐式链接:
1.将xxx.dll ,xxx.lib 放到工程目录下面(前面如果有静态链接库的.lib和.h记得先删掉)
此时.lib中不像静态链接库会存放函数的硬编码,对于动态链接库,.lib只是告诉编译器要使用的函数在哪里;动态链接库,真正存放代码(函数)的地方是.dll中
2.将 #pragma comment(lib,"xxx.lib") 添加到调用文件中(告诉编译器要使用动态链接库的函数)
3.加入函数的声明
extern "C" __declspec(dllimport) __stdcall int Plus (int x,int y);
extern "C" __declspec(dllimport) __stdcall int Sub (int x,int y);
extern "C" __declspec(dllimport) __stdcall int Mul (int x,int y);
extern "C" __declspec(dllimport) __stdcall int Div (int x,int y);
__declspec(dllimport):告诉编译器此函数为导入函数,说明这是使用别人的函数
与上面导出时规定的要一致
方法二–显示链接:
1.定义函数指针
typedef int (__stdcall *lpPlus)(int,int);
typedef int (__stdcall *lpSub)(int,int);
typedef int (__stdcall *lpMul)(int,int);
typedef int (__stdcall *lpDiv)(int,int);
2.声明函数指针变量
lpPlus myPlus;
lpSub mySub;
lpMul myMul;
lpDiv myDiv;
3.动态加载dll到内存中
HINSTANCE hModule = LoadLibrary("xxx.dll");
特别说明:
HINSTANCE:在win32下与HMODULE是相同的东西 Win16 遗留 HMODULE:是代表应用程序载入的模块 HANDLE:是代表系统的内核对象,如文件句柄,线程句柄,进程句柄
这三个本质上就是一种类型—无符号整型;windows之所以设计成三个不一样的名字,是因为
1.可读性更好 2.也可以避免在无意中对这个类型的变量进行运算
4.给函数指针赋值,即指向函数的地址
myPlus = (lpPlus)GetProcAddress(hModule,"_Plus@8");
mySub = (lpSub)GetProcAddress(hModule,"_Sub@8");
myMul = (lpMul)GetProcAddress(hModule,"_Mul@8");
myDiv = (lpDiv)GetProcAddress(hModule,"_Div@8");
5.调用函数
调用函数
int a = myPlus(10,2);
int b = mySub(10,2);
int c = myMul(10,2);
int d = myDiv(10,2);
3.动态链接库的特点
-
动态链接库是真正的模块化,.exe文件中包含了.dll,即模块,所以才能使用.dll中的代码,即我们上面定义的四个函数
-
我们再通过反汇编查看一下这个C程序使用的Plus的函数地址是1000…这个地址明显不属于.exePE文件的某个节,而是.exe文件包含的某个模块中的地址
-
对.dll中的函数进行优化改动,此时就不用再去管.exe,即不用让.exe重新编译,直接在.dll中修改完后,.exe文件中使用的函数就是修改后的。
-
所以==模块化==的好处就是,哪里有问题就改哪个模块。
使用.def导出
1.为什么使用.def导出
我们如果通过上面的方法,将我们写的代码导出,对方可以通过逆向分析得到函数名字,就可以猜到函数的功能,为了不让别人分析我们的.dll,可以通过.def导出的方法,即通过序列号代替函数名的方式,可以达到隐藏的目的
2…def导出方法
在.h头文件中:
int Plus (int x,int y);
int Sub (int x,int y);
int Mul (int x,int y);
int Div (int x,int y);
在.cpp源文件中:
int Plus(int x,int y){
return x+y;
}
int Sub(int x,int y){
return x-y;
}
int Mul(int x,int y){
return x*y;
}
int Div(int x,int y){
return x/y;
}
创建.def文件,内容如下:
EXPORTS
Plus @12
Sub @15 NONAME
Mul @13
Div @16