您的位置:首页 > 其它

私话编译连接运行过程以及动态库、静态库

2013-11-05 23:24 225 查看
拿程序调用动态库来说:

1 预处理: 宏替换、include展开等

2 编译:每个.cpp分别编译成.obj文件。obj文件中记录各个符号名,包括当前cpp能确定地址的符号名、将来链接时工程中其他cpp文件来确定地址的符号、运行装在时才能确定地址的调用dll中的符号。

编译过程是:编译器先查找函数名、变量名定义或声明是否存在,先查找当前cpp定义、再到include进来的里面找(预处理后include里的函数声明也在此cpp中),所以动态库的导出函数必须在其头文件声明,需要用到dll的程序要include动态库头文件。都存在就翻译成符号。三个地方都找不到的就报编译错误。最后生成.obj.(至于obj中还包含什么信息,没有深入研究,不妨碍理解编译过程大框架)

3 链接:把工程的多个.obj链接成一个exe。链接做的工作:把各个obj中的符号表弄到一个大的符号表中。再次把能确定地址的地址填上,涉及到地址重定位,之前每个obj中符号地址都是相对于自己文件的虚拟地址,现在从定位到整个exe的虚拟地址。存在一个找符号的地址过程,查找其他obj符号表,以及动态库的lib(导出符号表),找到的话,就把符号地址填上,dll此时还是没有地址,它直到加载才有地址,此时lib库的作用就是让编译器直到这个符号在dll中,运行时再确定。找不到的话就报连接错误。

4 运行:双击生成的exe,操作系统的装载器就把exe和dll装载到内存,这时,exe中调用动态库的符号的地址就确定了。动态库的dll中就保存有符号偏移、函数体、数据等信息。

程序调用静态库:

1 预处理和编译过程是相同的,所以使用静态库也需要include静态库头文件。

2 由于静态库的lib保存有符号偏移、函数体、数据等信息(不同于动态库的lib)。所以链接阶段就可以确定所有符号的地址了。链接的时候就是把各个obj和静态库的代码段、数据段等合并在一起,符号表完善地址(此时调用的函数代码区加载了,函数地址就确定了)。

动态库:动态链接库(Dynamic Link Library)是一个可以被其它程序共享的程序模块,其中封装了一些可以被共享的数据、函数和资源。
如果一个可执行程序使用了一个DLL,当可执行程序运行时,操作系统会把DLL加载到内存,并解析可执行程序对该DLL的符号引用,使得可执行程序能够调用DLL中的函数功能。
扩展名一般是dll(so),也有可能是fon、ocx、drv、sys(ko)等。DLL中虽然包含了可执行代码却不能单独执行,而应由其他应用程序直接或间接调用。

动态库:

静态库、动态库的共享问题:动态库所谓的内存共享内存中只加载一份,多个进程(程序)共享。静态库是调用的进程中都有一份,融合在各自的exe中。注意一个程序中每个有调用静态库函数obj都有一份静态库的内容。

老师课件内容:

链接器的主要工作

1 将分散的数据和机器代码收集并合成一个单一的可加载并可执行的文件;
2 符号解析:由多个程序模块(源程序)构建一个可执行程序时,模块之间的相互引用通过符号进行。程序也可以通过符号来引用代码库(lib库)中的功能。符号解析就是将符号引用和符号定义关联起来。
3 地址重定位:编译器产生的各个目标文件(obj文件)中数据和代码的地址一般都是从0开始。因此如果一个程序包含多个目标文件时就会产生地址重叠。重定位就是为每个目标文件重新定义加载地址,并修改相应的代码和数据以反映这种变化。

静态链接方式:在程序执行之前完成所有的组装工作,生成一个可执行的目标文件(如Windows下的EXE文件)。
动态链接方式:在程序已经为了执行被装入内存之后完成链接工作,并且在内存中一般只保留该编译单元的一份拷贝。
将函数名称和函数执行体动态链接(或动态绑定)的过程既可以发生在程序装载时,也可以发生在程序运行时(在需要时才会引入函数的方式)

预编译的函数库:模块化、可重用性强、编译速度快、保护知识产权
静态库(.a后缀)是一系列的目标文件的归档,静态链接时,静态库中的目标函数体会被复制到程序的可执行文件中去
动态库(libname.so[.major.minor.release])不具有“复制”操作,程序运行时根据需要载入内存,且动态地将函数调用与函数体关联到一起。动态库在内存中只有一份拷贝,因此也称为共享库。易于升级。

可以将静态链接库或动态链接库看成是一种仓库,它提供给你一些已经编译成机器代码的可以直接拿来用的数据、函数或类,它们是实现代码共享的一种方式。
静态链接库中的机器代码和数据都被直接包含在最终生成的EXE文件中
动态链接库的内容不必被包含在最终的EXE文件中,EXE文件执行时可以“动态”地引用和卸载这个与EXE独立的DLL文件。

VC6中动态链接库的创建:

1 第一步:通过编译开关(编译参数)将编译器和链接器设置为输出DLL状态;或者在VC6中用向导创建一个“Win32 Dynamic Link Library”工程
2 第二步:将程序中的一些函数设置为导出函数,设置导出函数的方法有两种:
利用.DEF文件
利用VC扩展关键字__declspec(dllexport)

3 VC在生成DLL的同时还会产生一个导入库(import lib),该静态库中列出了DLL输出的所有函数和变量的名称,但不包含任何实现代码。
4 在生成的DLL文件头部,还包含一个输出符号表,其中记录了该DLL输出的所有函数和变量的名称及相对偏移。

动态链接库的两种链接方法:

1 装载时动态链接(Load-time Dynamic Linking):即点击exe运行时,装载器就把dll加载到内存,一般通过程序中:#pragma comment( lib , …)来实现自动加载。
2 运行时动态装载并链接(Run-time Dynamic Loading):exe运行中运行到需要加载动态库的地方才加载。需要在程序中手动调用:void *dlopen(const char *file, int mode);void *dlsym(void *handle, const char *name);int dlclose(void *handle)。windows中是利用Windows API函数LoadLibrary, GetProcAddress和FreeLibrary实现运行动态加载、链接和释放DLL。不需要头文件和导出lib库。呵呵

装载时动态链接(静态加载链接dll)优点:调用程序更简单,易读、运行效率高
缺点:不够灵活,无法选择加载时机,无法更换DLL文件

运行时动态装载并链接(动态加载链接dll)应用实例:

所有的Windows API函数都是以动态链接库导出函数形式提供的。大部分API函数都存放在kernel32.dll、user32.dll和gdi32.dll三个动态库中,相应的导入库为kernel32.lib、user32.lib和gdi32.lib
应用软件的插件技术
每个Windows驱动程序本质上都是动态链接库

例子:基于DLL的消息映射 :将消息映射表存储在配置文件中,将每个消息处理函数都放在一个独立的动态库中。当增加新的消息处理时,只需修改配置文件,增加动态库,根本不需要停止主程序的运行。即消息映射表的“热插拔”版本



Read.cpp中放了一个函数,主要是为了一下回调函数,函数指针。对实验功能来说没有必要,可以全放在下面的MessageMap.cpp中。

Read.cpp:

#include <iostream>
using namespace std;
#include<windows.h>

char dllName[20][100];
//读取配置文件
void ReadConf()
{
for (int i=1;i<=20;i++)
{
char string[25];
itoa(i, string, 10);
//读取配置文件ini的函数
GetPrivateProfileString("MESSAGE", //节名
string, //项名
"NULL", //没找到此项时的返回值
dllName[i], //目标缓冲区地址
sizeof(dllName[i]),  //目标缓冲区长度
"c:\\he\\Confg.ini"); //配置文件的准确路径
}

}


MessageMap.cpp:

#include <iostream>
using namespace std;
#include<windows.h>
#include <cstring>

void SearchMap(int num);
void ReadConf();

extern char dllName[20][100];
char path[500] ;//监控目录

DWORD WINAPI TestThreadProc(LPVOID lpParam )
{
typedef void (*p)();
p pp=(p)lpParam;

HANDLE hChandle=FindFirstChangeNotification("C:\\he",TRUE,FILE_NOTIFY_CHANGE_LAST_WRITE);
//判断是否设置成功
if(hChandle==INVALID_HANDLE_VALUE)
{
printf("Find First Change Notification failed\n");
return -1;
}
//循环等待
while(true)
{
WaitForSingleObject(hChandle,INFINITE);//返回后才执行下面程序
//printf("配置文件改变了\n");
//读取配置文件信息,把消息处理函数dll放入字符数组
pp();//文件修改了,刷新数组。

//         else
//         {
//             //结束监视程序调用FindCloseChangeNotification关闭句柄
//             FindCloseChangeNotification(hChandle);
//             cout<<"退出了"<<endl;
//             return 0;
//             //getch(); //按键退出程序
//         }

//继续监控
FindNextChangeNotification(hChandle);
if(FindNextChangeNotification(hChandle)==FALSE)
{
ExitProcess(GetLastError());

return 0;
}
}

return 0;
}
int main()
{

DWORD  ThreadID;
HANDLE hThread = CreateThread(0,0,TestThreadProc,ReadConf,0,&ThreadID);

ReadConf();//初始读入数组
while (TRUE)
{
int num;
cout<<"输入消息:"<<endl;
cin>>num;
if (num!=10)
{
SearchMap(num);
}
else
return 0;
}
}

//查表函数
void SearchMap(int num)
{
if (strcmp(dllName[num],"NULL")==0)
{
cout<<"消息不存在,输入有误!"<<endl;
return;
}
HINSTANCE hIns=LoadLibrary(dllName[num]);
typedef void (*print_t)();//函数指针
print_t myprint=(print_t)GetProcAddress(hIns,"MyPrint");
cout<<num<<"号消息响应为:";
myprint();
cout<<endl;
FreeLibrary(hIns);

}


配置文件:


几个动态库都是这么写的。函数接口一致。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: