静态链接库、动态链接库

我的梦境 提交于 2019-12-02 10:50:07
1.代码的重用
一般在程序中,代码重用可以用函数来实现;
给其它程序重用代码的实现有两种方式:
    1】静态链接库
    2】动态链接库
也就是可以用来实现模块化;
 
1.静态链接库
1)创建静态链接库
vc6中创建:File    ->new    ->Projects    ->Win32 Static Library
 
头文件:
#if !defined(AFX_HELLO_H__0A93BBBF_3714_47C0_A953_6BC530AAD15A__INCLUDED_)
#define AFX_HELLO_H__0A93BBBF_3714_47C0_A953_6BC530AAD15A__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 // !defined(AFX_HELLO_H__0A93BBBF_3714_47C0_A953_6BC530AAD15A__INCLUDED_)
 
cpp文件:
#include "Hello.h"
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;
}
 
然后编译,会生成一个.lib文件;
创建完成;
 
2)使用静态链接库
 
1】方式一:                            
    将xxx.h 和 xxx.lib复制到要使用的项目中                        
    在需要使用的文件中包含:#include "xxx.h"                        
    在需要使用的文件中包含:#pragma comment(lib, "xxx.lib")    
 
将编译生成的.dll文件和头文件复制到目标工程根目录;
代码:
#include "stdafx.h"
 
//引入静态链接库中的头文件
#include "Hello.h"
 
//告诉编译器去哪里找lib文件
#pragma comment(lib, "StaticHello.lib")
 
int main(int argc, char* argv[])
{
    int x = 1;
    int y = 2;
    //使用静态链接库中的方法
    printf("相加的值为:%d\n", Plus(1,2));
    getchar();
    return 0;
}
 
方式二:                    
    将xxx.h 和 xxx.lib复制到要使用的项目中                
    在需要使用的文件中包含:#include "xxx.h"       
         
  
3)静态链接库的缺点
使用静态链接生成的可执行文件体积较大,造成浪费                    
我们常用的printf、memcpy、strcpy等就来自这种静态库          
 
用od的“E”可以查看程序包含了哪些模块;
静态链接库实际上是假的模块化;
静态链接库的代码实际上在编译时被编译到exe中了;  
 
2.动态链接库
1)创建
vc6中创建:File    ->new    ->Projects    ->Win32 Dynamic-Link Library      
 
头文件:
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);        
 
源文件:
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;        
}            
  
说明:                    
    1、extern 表示这是个全局函数,可以供各个其他的函数调用;   
    2、"C" 按照C语言的方式进行编译、链接      
        因为c不支持重载,而c++支持;如果不按照c导出,编译器可能会因为防止重载而改函数名;              
    __declspec(dllexport)告诉编译器此函数为导出函数;   
 
2)使用
1】方式一:隐式连接                        
    步骤1:将 *.dll  *.lib 放到工程目录下面     
        动态链接库代码放在.dll中,.lib中放的是去哪里找代码信息;                   
    步骤2:将 #pragma comment(lib,"DLL名.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);       
        注意函数导入和导出的调用约定要一致;
        dll的导入一般使用__stdcall内平栈;
 
说明:                        
__declspec(dllimport)告诉编译器此函数为导入函数;                        
                    
2】方式二:显示链接  
显示连接就是编译器不知道什么时候调用dll也不知道dll在哪,而是自己调用;                      
步骤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("DllDemo.dll");      
 
步骤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");       
因为是 __stdcall,编译器会将函数名改为“_函数名@8”,如果是默认的__cdecl则不会改名;
 
步骤5:    //调用函数                    
    int a = myPlus(10,2);                    
    int b = mySub(10,2);                    
    int c = myMul(10,2);                    
    int d = myDiv(10,2);           
 
特别说明:                                                
    Handle 是代表系统的内核对象,如文件句柄,线程句柄,进程句柄。                                                
    HMODULE 是代表应用程序载入的模块                                                
    HINSTANCE 在win32下与HMODULE是相同的东西 Win16 遗留                                                
    HWND 是窗口句柄    
其实就是一个无符号整型,Windows之所以这样设计有2个目的:                                                
1、可读性更好    ->这几个宏定义都是无符号整型,但用不同的名字可以知道具体是干什么用的,比如看到HWAND就知道这是一个窗口;                                               
2、避免在无意中进行运算    ->提醒用户,比如看到HWAND就知道这不是普通的数,不是用来做计算的;                        
 
 
3)总结
dll中的代码在编译后不会整合到exe中;
而是在自己独立的模块中;
比如:发现dll中某个函数有问题时,只需要修改dll就可以了,并不需要exe重新编译;
而静态链接库必须重新生成exe;
dll的这一特性可以实现模块化;
 
3.使用.def导出
很多时候,知道了一个函数名大概就能猜到这个函数是干什么用的;
为了安全,有时需要将重要的函数的名字隐藏,比如验证密码的函数;
使用.def导出dll时,可以让函数名字隐藏;
使用.def导出的项目首先要是一个动态链接库的项目;也就是说这是一种dll导出的方式
 
*.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
例如:Plus是函数名,@12就是说函数Plus的导出序号是12,这个序号可以随便写;
加了NOME的函数在导出时会看不见函数名;
 
使用序号导出的好处:                
    名字是一段程序就精华的注释,通过名字可以直接猜测到函数的功能                
    通过使用序号,可以达到隐藏的目的.                
 
 
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!