静态链接库(Lib)和动态链接库(DLL)

article/2025/9/29 8:14:30

            序言:本文主要讲解静态链接库和动态链接库的区别,以及怎么样编译和引用两种库,怎么样从DLL中导出函数和导出C++类。

一、静态链接库和动态链接库

         1.静态链接库(.LIB):函数和数据被编译进一个二进制文件。发布时,只需要发布这个可执行文件,并不需要发布被使用的静态库。

         2.动态库(.DLL):在使用动态库的时候,往往提供两个文件:一个引入库(.lib)文件和一个DLL(.dll)。虽然引入库和静态库的后缀名相同,但是差别很大。对于一个DLL来说,其引入库文件包含该DLL导出的函数和变量的符号名,而.dll文件包含DLL的实际的函数和数据。在使用动态链接库的情况下,在编译链接可执行文件时,只需要DLL的引入库文件,而在运行可执行程序时,需要加载所需要的DLL,发布产品时,需要发布调用的动态链接库。

二、静态链接库的创建和引用

            a.创建静态链接库

        打开VS2008,创建一个Win32控制台应用程序,选择静态链接库选项,不加预编译头文件。如下:


    

    新建一个头文件add.h和源文件add.cpp

    add.h

#ifndef ADD_H
#define ADD_H
int add(int a, int b);
#endif  // ADD_H
add.cpp

#include "add.h"
int add(int a, int b) {
return a + b;
}
然后编译,在Debug目录下生成LIB1.lib

        b.引用静态链接库

        首先创建一个测试程序test,调用代码如下:

#include "../LIB1/add.h" 
#include <stdio.h>
extern int add(int a, int b);
int main() {
int a = 2;
int b = 1;
printf("a=%d, b=%d\n", a,b);
printf("add: %d\n", add(a,b));
getchar();
return 0;
}
如果直接编译,会出现如下的错误,主要原因是找不到要引用的lib文件,下面讲解引用的方法


 引用方法一:当有该lib项目的时候使用。右键单击测试项目,选择引用,弹出下面的框


 

单击确定后即可。

 引用方法二:拖放的方法,该方法只针对第三方库。将要引用的lib文件复制到该项目的debug目录下,用鼠标将该lib文件拖放到资源文件夹下。


引用方法三:配置方法。对于带有配置文件的第三方库。将lib文件复制到debug目录下,设置项目的属性,找到链接的输入选项,在附加依赖项中添加要引用的库,这里为:.\debug\LIB1.lib



三、动态链接库的创建和引用

 

     a.动态链接库的创建

              导出函数

          首先在VS2008中创建一个空的DLL1项目,编写两个函数,分别为加法和减法。

 

int add(int a,int b)
{
return a+b;
}
int subtract(int a,int b)
{
return a-b;
}

对上面的代码进行编译,在该项目的debug目录下生成DLL1.dll文件。

     这时候外部的程序还不能对该DLL进行调用,主要是因为该DLL没有导出函数。这里使用Dumpbin命令来查看一个DLL有哪些导出函数。在命令行下输入 dumpbin命令,如下:

该命令在VS安装目录的bin文件夹下面

          注明:如果无法执行该命令,则在该界面下执行VCVARS32.bat文件。每次启动新的命令需要运行该文件。

    查看该DLL提供的导出函数:dumpbin -exports DLL1.dll

上面显示没有导出函数。从DLL导出函数需要在每一个将要被导出的函数前面添加标志符:_desclspec(dllexport)

修改后的函数:

_declspec(dllexport)int add(int a,int b)
{
return a+b;
}
_declspec(dllexport)int subtract(int a,int b)
{
return a-b;
}

编译之后,在debug目录下会生成两个文件,引入库DLL1.lib和DLL1.dll文件。然后重新查看该DLL的导出函数。


从上图可以看到多出了一些信息,ordinal表示导出函数的序号,hint表示提示码,RVA列出的地址值是导出函数在DLL模块中的位置,即通过该地址可以在DLL中找到它们;name列出了导出函数的名字。由于C++支持函数重载,对于重载的函数名是一样的,为了方便区别,在编译连接的时候,C++会按照自己的规则修改函数的名字,称为“名字编写”。

 

引用该DLL

首先为了让编译器知道这两个函数,需要用extern 声明,如下:

#include <stdio.h>
extern int add(int a, int b);
extern  int subtract(int a,int b);
int main() {
int a = 2;
int b = 1;
printf("a=%d, b=%d\n", a,b);
printf("add: %d\n", add(a,b));
printf("subtract: %d\n", subtract(a,b));
getchar();
return 0;
}

编译出现如下错误:

这里产生三个错误是由于程序链接的时候产生的。为了解决这个问题,需要将DLL的引入库文件DLL1.lib文件复制到test工程目录下。并且设置附加依赖项.\debug\DLL1.lib。

重新编译,发现没有错误。然后运行该测试程序,出现如下错误:

主要原因是该测试程序找不到要加载的DLL1.dll文件,应该将该文件拷贝到test项目所在的目录下,再运行就不会发生错误。

注明:采用dumpbin -imports test.exe 可以查看该程序的输入信息以及其要加载的DLL信息。

           采用Depends工具可以查看一个DLL或exe所依赖的动态链接库。

 

利用_declspec(dllimport)声明外部函数:上面采用的是extern来声明,如果调用的函数来自动态链接库,建议采用_declspec(dllimport),可以生成效率更高的代码。
 _declspec(dllimport) int add(int a, int b);
_declspec(dllimport) int subtract(int a,int b);

 

 如何让用户知道该DLL导出了哪些函数呢?在写DLL的时候,应该使用头文件,DLL1.h

#ifndef DLL1_H
#define DLL1_H
#ifdef BUILD_DLL
#define PORT_DLL __declspec(dllexport)
#else
#define PORT_DLL __declspec(dllimport)
#endif
int PORT_DLL add(int a, int b); int PORT_DLL subtract(int a,int b);
#endif  // DLL1_H

源文件DLL1.CPP修改如下:

#define BUILD_DLL
#include"DLL1.h"
PORT_DLL int add(int a,int b)
{
return a+b;
}
PORT_DLL int subtract(int a,int b)
{
return a-b;
}


在调用的程序直接包含该头文件#include"DLL1.h"即可。

#include <stdio.h>
#include"DLL1.h"
int main() {
int a = 2;
int b = 1;
printf("a=%d, b=%d\n", a,b);
printf("add: %d\n", add(a,b));
printf("subtract: %d\n", subtract(a,b));
getchar();
return 0;
}


 DLL中导出C++类:

首先在DLL1.h的头文件中定义一个类:

 

#ifndef DLL1_H
#define DLL1_H
#ifdef BUILD_DLL
#define PORT_DLL _declspec(dllexport)
#else
#define PORT_DLL _declspec(dllimport)
#endif
int PORT_DLL add(int a, int b); 
int PORT_DLL subtract(int a,int b);
class PORT_DLL Point
{
public:
int Max(int a,int b);
};
#endif  // DLL1_H


 在DLL1.CPP文件中定义函数:

#define BUILD_DLL
#include"DLL1.h"
PORT_DLL int add(int a,int b)
{
return a+b;
}
PORT_DLL int subtract(int a,int b)
{
return a-b;
}
int Point::Max(int a, int b)
{
return a>=b?a:b;
}


编译之后,将DLL1.lib,DLL1.dll和Dll1.h拷贝到test项目下,然后进行调用:

#include <stdio.h>
#include "DLL1.h"
int main() {
int a = 2;
int b = 1;
printf("a=%d, b=%d\n", a,b);
printf("add: %d\n", add(a,b));
printf("subtract: %d\n", subtract(a,b));
Point pt;
printf("Point: %d\n",pt.Max(a,b));
getchar();
return 0;
}

导出一个类的某个函数:

class  /*PORT_DLL*/ Point
{
public:
PORT_DLL int Max(int a,int b);
};


用dumpbin -exports DLL1.dll查看该DLLL导出的函数:


从上图可以看到导出了一个类Point, 以及该类中的Max函数,还有两个函数,但是 他们的函数名字已经发生改变。

     名字改编问题:

    C++编译器在生成DLL时,会对导出的函数进行名字修改,并且不同的编译器使用的改编规则不同。如果利用不同的编译器分别生成DLL和访问该DLL的客户端程序的话,则访问DLL的导出函数就会出现问题。如果用C++语言编写了一个DLL,那么用C语言编写的客户端访问该DLL的函数就会出现问题。因为后者使用函数原始名称来调用DLL中的函数,而C++编译器已经对该名称进行了改编,所以C语言编写的客户端程序就找不到所需DLL中的函数。

对于C++ 与C中的兼容,我们希望动态链接库在编译的时候,导出函数的名称不要发生变化,可以在定义导出函数时候,加上extern "C",C大写。如下:

 

#ifndef DLL1_H
#define DLL1_H
#ifdef BUILD_DLL
#define PORT_DLL extern"C" _declspec(dllexport)
#else
#define PORT_DLL  extern"C" _declspec(dllimport)
#endif
int PORT_DLL add(int a, int b); 
int PORT_DLL subtract(int a,int b);
class PORT_DLL Point
{
public:
int Max(int a,int b);
};
#endif  // DLL1_H

编译,用dumpbin查看,发现名字没有发生改变。


extern "C"可以解决C++与C语言之间的相互调用的函数命名问题。但是有一个缺陷,就是不能导出一个类的成员函数。

如果导出函数的调用约定发生改变,即使使用了extern ”C“,该函数的名字仍然会发生改变。

下面采用标准调用约定,即在声明这些函数时添加_stdcall关键字;

#ifndef DLL1_H
#define DLL1_H
#ifdef BUILD_DLL
#define  PORT_DLL  extern"C" _declspec(dllexport)
#else
#define PORT_DLL  extern "C" _declspec(dllimport)
#endif
PORT_DLL int _stdcall  add(int a, int b); 
PORT_DLL  int  _stdcall subtract(int a,int b);
/*class PORT_DLL Point
{
public:
int Max(int a,int b);
};*/
#endif  // DLL1_H




源文件:

#define BUILD_DLL
#include"DLL1.h"
PORT_DLL int _stdcall add(int a,int b)
{
return a+b;
}
PORT_DLL int _stdcall subtract(int a,int b)
{
return a-b;
}
/*int Point::Max(int a, int b)
{
return a>=b?a:b;
}*/


编译然后查看:

从上图可以看到,add函数的名字变为_add@8,在add前面有一个下划线,后面跟一个@,接着是数字8,该数字表示add函数的参数所占的字节数,因为有两个int 参数。

通过上图可以发现,如果函数的调用约定发生了变化,即使在声明这些导出函数时使用了extern "C",它的名字仍然会发生变化。C语言和Delphi语言的调用约定不一样,后者使用pascal调用约定,即标准调用约定_stdcall.解决这种问题,可以定义一个模块定义文件(DEF)来解决名字改编问题

  

      使用DEF文件导出函数。首先创建一个新项目DLL2,在 DLL2.cpp中添加代码:

 int  add(int a,int b)
{
return a+b;
}
int  subtract(int a,int b)
{
return a-b;
}

在该工程目录下,新建一个Dll2.DEF空文件,后缀名为def,然后通过下面方式加入到工程:项目——属性——连接器——输入——模块定义文件 中输入你所定义的def文件名

在Dll2.def加入如下的代码:

LIBRARY DLL2

EXPORTS 表明DLL将要导出的函数
add
subtract

如果想要导出的符号名和源文件中定义的函数名不一样,可以按照下面方法定义,比如要将add函数导出为myadd函数:

LIBRARY DLL2
EXPORTS
myadd=add
subtract

 

然后编译,用dumpbin工具查看导出函数,发现函数名字不变。



使用DEF文件来导出函数不需要使用_stdcall 和 _declspec(dllexport)


http://chatgpt.dhexx.cn/article/xAZPCQp9.shtml

相关文章

C++静态链接库与动态链接库

C静态链接库与动态链接库 什么是库程序编译成可执行程序的步骤静态链接库与动态链接库的区别&#xff08;简易版&#xff09;&#xff1a;静态链接库调用实现动态链接库调用实现g&#xff08;gcc&#xff09;编译选项 什么是库 库是写好的、现有的、可复用的代码。库是一种可执…

「C/C++」C/C++静态链接库与动态链接库

博客主页&#xff1a;何曾参静谧的博客 文章专栏&#xff1a;「C/C」C/C学习 目录 相关术语案例环境&#xff1a;Win10VS2019一、链接库介绍二、静态链接库&#xff08;Static Library&#xff09;2.1、静态库优缺点2.2、静态库的创建2.2.1、创建静态库项目2.2.2、添加.h头文件…

C++封装静态链接库及使用

一、为什么要把程序封装成库 有时我们需要把自己的程序交给第三方调用&#xff0c;但是又不想被别人看到自己的具体实现代码&#xff0c;就封装成库给别人使用。库有动态链接库和静态链接库&#xff0c;区别是动态链接库可以在程序运行时动态链接&#xff0c;而静态链接库相当于…

C++ 创建静态链接库和动态链接库

上篇文章演示了如何使用C 编译的静态链接库和动态链接库&#xff0c;本篇文章主要介绍如何创建静态链接库和动态链接库&#xff0c;本文使用的工具是visual studio 2019 企业版&#xff0c;需要安装对应的Csdk&#xff0c;可以参考网上其他文章&#xff0c;本问不在赘述。 一、…

动态链接库和静态链接库

转载自&#xff1a;https://www.cnblogs.com/king-lps/p/7757919.html &#xff08;有删减&#xff09; 1. 库的介绍 库是写好的现有的&#xff0c;成熟的&#xff0c;可以复用的代码。现实中每个程序都要依赖很多基础的底层库&#xff0c;不可能每个人的代码都从零开始&…

静态链接库与动态链接库----C/C++

平时我们写程序都必须include很多头文件&#xff0c;因为可以避免重复造轮子&#xff0c;软件大厦可不是单靠一个人就能完成的。但是你是否知道引用的那些头文件中的函数是怎么被执行的呢&#xff1f;这就要牵扯到链接库了&#xff01;&#xff01;&#xff01; 库有两种&#…

【四、静态库与动态库(共享库)】揭开链接库的神秘面纱:手把手教你制作静态链接库与动态链接库

前言 不管是在 Windows 下开发&#xff0c;还是在 Linux 下开发&#xff0c;我们都会经常性的使用一些库文件&#xff0c;这些库文件的特点就是&#xff0c;我们可以看到接口的原型并通过这些接口来调用这个函数的功能&#xff0c;但是我们无法查看这个功能的实现。这就是库文…

静态链接库(.lib)和动态链接库(.dll)的使用

静态链接库(.lib)和动态链接库(.dll)的使用 文章目录 静态链接库(.lib)和动态链接库(.dll)的使用一、静态链接库1. 静态链接库概述2. 创建静态链接库3. 调用静态链接库 二、动态链接库(dynamic linking library)1. 动态链接库概述2. 创建动态链接库并导出函数导出函数两种方式1…

静态链接库

库 库的存在&#xff0c;大大方便了我们进行编程。因为有了库&#xff0c;我们不必再从0开始&#xff0c;例如我们大多数人C语言写的第一个程序Hello World!都是用了库函数。以printf为例&#xff0c;我们只需要在程序源代码中包含<stdio.h>这个头文件之后&#xff0c;就…

史上最全Java类型转换

讲类型转换之前&#xff0c;让我来贴张表 基本数据类型的表示范围 类型 长度 表示范围默认值byte8b-128&#xff5e;1270short16b-32768&#xff5e;327670int32b-21147483648&#xff5e;21474836470long64b-9223372036 854 775 808&#xff5e;9223372036 8547758070Lfloat3…

Java:类型转换

public class Demo04 {public static void main(String[] args) {//低--------------------------> 高// byte,short,char->int->long->float->doubleint i 128;byte b (byte) i ;//内存溢出//强制转换 &#xff08;类型&#xff09;变量名 高--低//自动转换…

java强转规则_java类型转换及其规则介绍

一、自动类型转换 整型、实型(常量)、字符型数据可以混合运算。运算中&#xff0c;不同类型的数据先转化为同一类型&#xff0c;然后进行运算。 数据类型转换必须满足如下规则&#xff1a; 1、不能对boolean类型进行类型转换。 2、不能把对象类型转换成不相关类的对象。 3、在把…

Sql-Java类型转换

Mysql-Java字段类型转换 mysql类型名大小用途对应Java类char0-255 bytes定长字符串 &#xff08;姓名、性别、学号&#xff09;Stringvarchar0-65535 bytes变长字符串&#xff08;比上面更长一点的那种&#xff09;Stringtinytext0-255 bytes比较短的那种文本数据&#xff08;…

JAVA类型转换及变量详解

类型转换 由于java是强类型语言&#xff0c;所以要进行有些运算的时候&#xff0c;需要用到类型转换。 byte&#xff08;1个字节&#xff09;,short&#xff08;2个字节&#xff09;,char&#xff08;2个字节&#xff09;----->int&#xff08;4个字节&#xff09;---->…

Java类型转换(自动类型转换+强制类型转换)

一、 自动类型转换(隐式类型转换) 整型、实型(常量)、字符型数据可以混合运算。运算中,不同类型的数据先转化为同一类型,然后进行运算。自动转换从低级到高级。 自动转换有以下规律: 1、小的类型自动转化为大的类型 2、整数类型可以自动转化为浮点类型,可能会产生舍入误…

Java的类型转换

需要类型转换的原因:因为Java是强类型的语言&#xff0c;所以有时可能要进行跨类型的运算&#xff0c;这就需要先进行类型转换&#xff0c;再进行运算。 类型转换分为&#xff1a; 强制类型转换(由高-->低时使用)&#xff0c;自动类型转换/ 隐式类型转换(由低-->高时使用…

java中的类型转换

java的基本数据类型 1.数值型&#xff1a;byte&#xff0c;short&#xff0c;int&#xff0c;long&#xff0c;float&#xff0c;double 2.字符型&#xff1a;char 3.布尔型&#xff1a;boolean 数据类型占据字节数byte1个字节short2个字节int4个字节long8个字节float4个字节…

Java中各种类型的转化

目录 一.Integer和int之间的类型转化 1.自动装箱 2.构造器方法 3.Integer.valueOf(int i) 二.Integer和String之间的类型转换 1.String转换为Integer 1.Integer.parseInt(String s) ​编辑 2.Integer.valueOf(String s) 2.Integer转换为String 1.Integer.toString() …

【JAVA】Java中的类型转换

目录 1.自动类型转换&#xff08;隐式转换&#xff0c;小类型转换为大类型&#xff09; 2.强制类型转换&#xff08;显示转换&#xff0c;大类型转换为小类型&#xff09; 3.小于4字节的类型转换问题 3.1 byte<->int 3.2 char<->int 3.3 String<->int …

Java 类型转换

一、自动类型转换(隐式类型转换) 整型、实型(常量)、字符型数据可以混合运算。不同类型的数据先转化为同类型再进行运算 自动转换按从低级到高级顺序: char ↓ Byte→short→int→long---›float→double 自动转换有以下规律&#xff1a; 小的类型自动转化为大的类型 整数…