静态链接和动态链接 -- 静态加载(隐式调用)和动态加载(显式调用)

区别

  • 静态链接和动态链接
    • 静态链接 : 由链接器在链接时将库的内容加入到可执行程序中,这里的库是静态库,Windows下是*.lib后缀,Linux下是*.a后缀。
    • 动态链接 : 可执行程序加载时(静态加载) 或者 运行时(动态加载),将库文件中的内容加入到可执行程序中,这里的库是动态库,Windows下是*.dll后缀,Linux下是*.so后缀。
  • 静态加载和动态加载
    • 首先,静态加载和动态加载都是动态链接,跟静态链接没有关系。静态加载和动态加载指的都是动态链接的方式。也称为显式调用和隐式调用。
    • 静态加载(隐式调用) : 由编译器完成对动态库的加载和卸载工作。编译阶段需要添加头文件,编译器根据动态库路径取查找动态库。程序运行时,如果找不到动态库就会报错。
    • 动态加载(显式调用) : 是由运行的程序自行决定什么时候加载或卸载动态库的,编译的时候无需添加头文件等。程序运行时,即使找不到动态库,也能正常执行。
  • 下面就分别介绍下Linux平台和Windows平台的库的静态链接和动态链接,以及动态库的静态加载和动态加载。

Linux文件内容

  • 库文件内容
    • myadd.h
    •   #ifndef __MYADD_H__
        #define __MYADD_H__
      
        int myAdd(int a, int b);
        int myMinus(int a, int b);
        
        #endif
      
    • myadd.c
    •   #include "myadd.h"
      
        int myAdd(int a, int b){
                return a + b;
        }
      
        int myMinus(int a, int b){
                return a - b;
        }
      
  • 源文件内容
    • main.c
    •   #include "myadd.h"
        #include 
      
        int main(){
            int data1 = myAdd(10, 20);
            printf("data1 = %d\n", data1);
            int data2 = myMinus(10, 20);
            printf("data2 = %d\n", data2);
            return 0;
        }
      

Linux库文件制作

  • 制作静态库
    • gcc -c myadd.c -o myadd.o
    • ar rcs libsmyadd.a myadd.o
  • 制作动态库
    • gcc -c myadd.c -o myadd.o -fPIC
    • gcc -shared -o libdmyadd.so myadd.o
  • 这里分别制作一个静态库 libsmyadd.a 和一个动态库 libdmyadd.so

Linux静态链接

  • 静态链接
    • gcc main.c -o ress -L ./ -lsmyadd
  • 可以使用ldd ress命令查看可执行文件的依赖库,可以看到依赖库都是系统库。
    在这里插入图片描述
  • 使用nm ress命令查看下符号信息,可以看到可执行文件ress中有对应的函数
    静态链接和动态链接 -- 静态加载(隐式调用)和动态加载(显式调用)_第1张图片

Linux动态链接(静态加载)

  • 生成可执行程序。
    • gcc main.c -o resd -L ./ -ldmyadd -Wl,-rpath=.
  • 这里的rpath是指定动态库的加载路径,如果不指定,会去系统库目录下加载。L指定的是动态库的链接路径,这里要注意区分。不指定rpath,编译时不会报错,但运行时会报找不到动态库。如果不指定L,程序编译阶段就会报错。
  • 使用ldd resd命令看下可执行程序的依赖,可以看到,这个时候可执行程序需要依赖动态库。
    静态链接和动态链接 -- 静态加载(隐式调用)和动态加载(显式调用)_第2张图片
  • 使用nm resd命令查看下符号信息,可以看到可执行文件resd中有对应的函数
    静态链接和动态链接 -- 静态加载(隐式调用)和动态加载(显式调用)_第3张图片

Linux动态链接(动态加载)相关函数介绍

  • void *dlopen(const char *filename, int flag);
    • 函数功能:打开或者加载一个动态链接库
    • 参数
      • filename : 动态库文件名
      • flag : 动态库加载方式
    • 返回值 : 失败返回NULL
  • void *dlsym(void *handle, const char *symbol);
    • 函数功能 : 从动态链接库中获取符号地址
    • 参数
      • handle : dlopen打开的动态库的句柄
      • symbol : 符号,也就是动态库中的函数名
    • 返回值:失败返回NULL
  • int dlclose(void *handle);
    • 函数功能 : 关闭或者卸载一个动态链接库
  • char *dlerror(void);
    • 函数功能 : 获取错误信息

Linux动态链接(动态加载)

  • 动态加载时,需要对源文件main.c作以下修改
  •   #include 
      #include 
      
      //定义函数指针
      typedef int (*PADD)(int a, int b);
      typedef int (*PMINUS)(int a, int b);
      
      int main(){
      
              //动态加载so库
              void *handle = dlopen("./libdmyadd.so", RTLD_NOW);
              if(handle == NULL){
                      printf("load libdmyadd.so failed, errmsg is %s\n", dlerror());
                      return -1;
              }
      
              PADD pAdd = (PADD)dlsym(handle, "myAdd");
              if(pAdd == NULL){
                      printf("load myAdd func failed, errmsg is %s\n", dlerror());
                      return -1;
              }
      
              PMINUS pMinus = (PMINUS)dlsym(handle, "myMinus");
              if(pMinus == NULL){
                      printf("load myMinus func failed, errmsg is %s\n", dlerror());
                      return -1;
              }
      
      
              int data1 = pAdd(10, 20);
              printf("data1 = %d\n", data1);
              int data2 = pMinus(10, 20);
              printf("data2 = %d\n", data2);
      
              //动态卸载so库
              dlclose(handle);
      
              return 0;
      }
    
  • 可以看到,动态加载时不需要包含动态库的头文件
  • 编译命令:gcc main.c -o a.out -ldl
  • 编译时,也不需要依赖动态库,但要加一个编译参数 -ldl
  • 使用ldd a.out命令看下可执行程序的依赖
    静态链接和动态链接 -- 静态加载(隐式调用)和动态加载(显式调用)_第4张图片
  • 可以看到,不需要依赖对应的库文件。
  • 使用nm a.out命令,可以看到可执行程序中也没有动态库文件中对应的符号
    静态链接和动态链接 -- 静态加载(隐式调用)和动态加载(显式调用)_第5张图片

制作Windows静态库

  • 新建一个Win32项目
    静态链接和动态链接 -- 静态加载(隐式调用)和动态加载(显式调用)_第6张图片
  • 这里选择静态库
    静态链接和动态链接 -- 静态加载(隐式调用)和动态加载(显式调用)_第7张图片
  • 建好工程后,分别添加一个头文件myAdd.h和源文件myAdd.cpp
  • myAdd.h
  •   #pragma once
    
      int myAdd(int a, int b);
    
      int myMinus(int a, int b);
    
  • myAdd.cpp
  •   #include "myAdd.h"
    
      int myAdd(int a, int b) {
      	return a + b;
      }
    
      int myMinus(int a, int b) {
      	return a - b;
      }
    
  • 然后点击生成,就会生成一个静态库文件 staticlib.lib
    静态链接和动态链接 -- 静态加载(隐式调用)和动态加载(显式调用)_第8张图片

Windows静态加载

  • 再新建一个项目
    静态链接和动态链接 -- 静态加载(隐式调用)和动态加载(显式调用)_第9张图片
  • 这里选择控制台应用程序
    静态链接和动态链接 -- 静态加载(隐式调用)和动态加载(显式调用)_第10张图片
  • 创建好工程后,添加一个源文件 main.cpp
  •   #include 
      #include 
      #include "myAdd.h"
      
      //加载静态库
      #pragma comment(lib, "staticlib.lib")
      
      int main() {
      	int sum1 = myAdd(10, 22);
      	printf("sum1 = %d\n", sum1);
      	
      	int sum2 = myMinus(10, 20);
      	printf("sum2 = %d\n", sum2);
      	
      	system("pause");
      	return 0;
      }
    
  • 然后需要把刚才制作的静态库staticlib.lib以及对应的头文件myAdd.h拷贝到当前工程目录下,然后编译即可运行。
  • 可以使用dependency工具查看可执行文件的依赖,都是依赖的系统库。
    静态链接和动态链接 -- 静态加载(隐式调用)和动态加载(显式调用)_第11张图片

制作Windows动态库

  • 新建一个项目
    静态链接和动态链接 -- 静态加载(隐式调用)和动态加载(显式调用)_第12张图片
  • 这里选择DLL
    静态链接和动态链接 -- 静态加载(隐式调用)和动态加载(显式调用)_第13张图片-
  • 建好工程后,分别添加一个头文件myAdd.h和源文件myAdd.cpp
  • myAdd.h
  •   #pragma once
      
      /* 特别说明
      *  导出函数时(也就是生成动态库时),要用_declspec(dllexport)声明函数
      *  导入函数时(也就是使用动态库时),要用_declspec(dllimport)声明函数
      *  但由于我们生成和使用动态库时,使用的是同一个头文件,所以这里声明函数时要做宏控制进行条件编译
      */
      
      #ifdef _DLLAPI
      #define DLLAPI _declspec(dllexport) 
      #else
      #define DLLAPI _declspec(dllimport)
      #endif
      
      //注意这里要使用extern "C"去声明,用c编译器去编译
      //因为我们知道,c++有多态,c++编译器编译函数时,不仅会把名字编译进去,把参数个数和类型也会编译进去
      //因此,如果用c++编译器编译,除了函数名还有其他一些特殊符号。
      extern "C" DLLAPI int myAdd(int a, int b);
      
      extern "C" DLLAPI int myMinus(int a, int b);
    
  • myAdd.cpp
  •   #include "myAdd.h"
    
      int myAdd(int a, int b)
      {
      	return a + b;
      }
      
      int myMinus(int a, int b)
      {
      	return a - b;
      }
    
  • 编译前要添加一个宏定义_DLLAPI,这样生成动态库时就是导出符号。
    静态链接和动态链接 -- 静态加载(隐式调用)和动态加载(显式调用)_第14张图片
  • 点击生成,就会生成一个静态库dynamiclib.lib和动态库dynamiclib.dll
    静态链接和动态链接 -- 静态加载(隐式调用)和动态加载(显式调用)_第15张图片

Windows动态链接(静态加载)

  • 新建一个项目
    静态链接和动态链接 -- 静态加载(隐式调用)和动态加载(显式调用)_第16张图片
  • 选择控制台应用程序
    静态链接和动态链接 -- 静态加载(隐式调用)和动态加载(显式调用)_第17张图片
  • 添加一个源文件main.cpp
  •     #include 
        #include 
        #include "myAdd.h"
        
        //加载静态库
        #pragma comment(lib, "dynamiclib.lib")
        
        int main() {
        	int sum1 = myAdd(10, 22);
        	printf("sum1 = %d\n", sum1);
        	
        	int sum2 = myMinus(10, 20);
        	printf("sum2 = %d\n", sum2);
        	
        	system("pause");
        	return 0;
        }
    
  • 然后我们需要将静态库dynamiclib.lib和动态库dynamiclib.dll,以及头文件myAdd.h一起拷贝到当前工程目录下,然后再进行编译。
  • 可以看下可执行程序的依赖,需要依赖动态库dynamiclib.dll
    静态链接和动态链接 -- 静态加载(隐式调用)和动态加载(显式调用)_第18张图片

Windows动态链接(动态加载)

  • 新建一个项目
    静态链接和动态链接 -- 静态加载(隐式调用)和动态加载(显式调用)_第19张图片
  • 选择控制台应用程序
    静态链接和动态链接 -- 静态加载(隐式调用)和动态加载(显式调用)_第20张图片
  • 建好工程后,添加一个源文件main.cpp
  •   #include 
      #include 
      #include 
      
      //定义函数指针
      typedef int(*PADD)(int a, int b);
      typedef int(*PMINUS)(int a, int b);
      
      int main() {
      	//加载动态库
      	HMODULE hDll = LoadLibrary(L"dynamiclib.dll");
      	if (hDll == NULL) {
      		printf("加载testdll.dll失败\n");
      		return -1;
      	}
      
      	//获取动态库中函数地址
      	PADD pAdd = (PADD)GetProcAddress(hDll, "myAdd");
      	if (pAdd == NULL) {
      		printf("获取myAdd地址失败\n");
      		return -1;
      	}
      	
      	PMINUS pMinus = (PMINUS)GetProcAddress(hDll, "myMinus");
      	if (pMinus == NULL) {
      		printf("获取myMinus地址失败\n");
      		return -1;
      	}
      
      	int sum1 = pAdd(10, 20);
      	printf("sum1 = %d\n", sum1);
      	
      	int sum2 = pMinus(10, 20);
      	printf("sum2 = %d\n", sum2);
      	
      	//卸载动态库
      	FreeLibrary(hDll);	
      	
      	system("pause");
      	return 0;
      }
    
  • 然后只需要将动态库dynamiclib.dll拷贝到当前工程下,就可以运行了。
  • 可以看下可执行程序的依赖,并不需要依赖动态库dynamiclib.dll
    静态链接和动态链接 -- 静态加载(隐式调用)和动态加载(显式调用)_第21张图片

总结

  • 静态链接和动态链接
    • 通过以上介绍可以看到,静态链接就是在编译阶段,将静态库中的符号加载到可执行程序中。优点是可执行程序不需要依赖库文件,可以把可执行程序直接发给用户就可以执行。缺点是项目一旦复杂,可执行程序就会非常大,并且违反了模块化编程思想。动态链接的优点就是可以进行模块化编程,项目中修改某模块时,只需要替换对应的动态库就可以了。缺点是可执行程序要依赖很多库文件,实际开发中经常会因为动态库的依赖问题让人头疼。
  • 静态加载和动态加载
    • 静态加载和动态加载都是动态链接,但动态加载更加灵活,可以在程序运行过程中由开发者决定动态库的加载时机和卸载时机,动态库的加载位置也非常灵活,可以由开发者自己指定,且不需要包含对应的头文件。

你可能感兴趣的:(开发语言,#,C/C++开发,c++,c语言)