静态链接和动态链接 -- 静态加载(隐式调用)和动态加载(显式调用)
区别
- 静态链接和动态链接
- 静态链接 : 由链接器在链接时将库的内容加入到可执行程序中,这里的库是静态库,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 <stdio.h>
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中有对应的函数
Linux动态链接(静态加载)
- 生成可执行程序。
- gcc main.c -o resd -L ./ -ldmyadd -Wl,-rpath=.
- 这里的rpath是指定动态库的加载路径,如果不指定,会去系统库目录下加载。L指定的是动态库的链接路径,这里要注意区分。不指定rpath,编译时不会报错,但运行时会报找不到动态库。如果不指定L,程序编译阶段就会报错。
- 使用ldd resd命令看下可执行程序的依赖,可以看到,这个时候可执行程序需要依赖动态库。
- 使用nm resd命令查看下符号信息,可以看到可执行文件resd中有对应的函数
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 <stdio.h>
#include <dlfcn.h>
typedef int (*PADD)(int a, int b);
typedef int (*PMINUS)(int a, int b);
int main(){
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);
dlclose(handle);
return 0;
}
- 可以看到,动态加载时不需要包含动态库的头文件
- 编译命令:gcc main.c -o a.out -ldl
- 编译时,也不需要依赖动态库,但要加一个编译参数 -ldl
- 使用ldd a.out命令看下可执行程序的依赖
- 可以看到,不需要依赖对应的库文件。
- 使用nm a.out命令,可以看到可执行程序中也没有动态库文件中对应的符号
制作Windows静态库
- 新建一个Win32项目
- 这里选择静态库
- 建好工程后,分别添加一个头文件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
Windows静态加载
- 再新建一个项目
- 这里选择控制台应用程序
- 创建好工程后,添加一个源文件 main.cpp
-
#include <stdio.h>
#include <stdlib.h>
#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工具查看可执行文件的依赖,都是依赖的系统库。
制作Windows动态库
- 新建一个项目
- 这里选择DLL
- - 建好工程后,分别添加一个头文件myAdd.h和源文件myAdd.cpp
- myAdd.h
-
#pragma once
#ifdef _DLLAPI
#define DLLAPI _declspec(dllexport)
#else
#define DLLAPI _declspec(dllimport)
#endif
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==,这样生成动态库时就是导出符号。
- 点击生成,就会生成一个静态库dynamiclib.lib和动态库dynamiclib.dll
Windows动态链接(静态加载)
- 新建一个项目
- 选择控制台应用程序
- 添加一个源文件main.cpp
-
#include <stdio.h>
#include <stdlib.h>
#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
Windows动态链接(动态加载)
- 新建一个项目
- 选择控制台应用程序
- 建好工程后,添加一个源文件main.cpp
-
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
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
总结
- 静态链接和动态链接
- 通过以上介绍可以看到,静态链接就是在编译阶段,将静态库中的符号加载到可执行程序中。优点是可执行程序不需要依赖库文件,可以把可执行程序直接发给用户就可以执行。缺点是项目一旦复杂,可执行程序就会非常大,并且违反了模块化编程思想。动态链接的优点就是可以进行模块化编程,项目中修改某模块时,只需要替换对应的动态库就可以了。缺点是可执行程序要依赖很多库文件,实际开发中经常会因为动态库的依赖问题让人头疼。
- 静态加载和动态加载
- 静态加载和动态加载都是动态链接,但动态加载更加灵活,可以在程序运行过程中由开发者决定动态库的加载时机和卸载时机,动态库的加载位置也非常灵活,可以由开发者自己指定,且不需要包含对应的头文件。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/657009.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!