DLL动态库与静态库Lib的区别

之前一直有些疑问,看了这篇文章,思路清晰多了,特转载如下:(打算这个周末2018年5月26日,从头至尾实现并加上自己的感悟,写一篇原创的文章出来)(看来要爽约了,跟老婆昨晚吵了一架,没怎么睡好,这个推出到下周末吧,视情况而定,最近刚去新公司入职,还得学习新公司的旧代码,有点忙啊~~2018-5-26记)

原文链接:点击打开链接

以下以vs2013开发环境做出演示:

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

首先在vs2013中创建一个空的DLL1项目,添加DLL1.h与DLL.cpp 
头文件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

源文件DLL.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;
    }

编译之后Debug目录下会生成两个文件,分别为动态链接库DLL1.dll和动态链接库的引导库DLL1.lib。 
使用Dependency walker分析工具查看.dll文件内容如下:

这里写图片描述

由于C++支持函数重载,对于重载的函数名是一样的,为了方便区别,在编译连接的时候,C++会按照自己的规则修改函数的名字,称为“名字编写”。

接下来在控制台程序中引用该项目,将DLL1.h拷贝到工程目录下,在工程目录下新建名为Bin和Lib的文件夹,Bin文件夹内存放.dll文件,Lib文件夹内存放引导库.lib文件。

在调用的程序直接包含该头文件#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;
}

直接编译会出现如下情况(error LNK 2019):

这里写图片描述

说明没有告诉编译器引导库.lib的位置,添加附加依赖项如下:

这里写图片描述

如果出现如下错误(error LNK 1104):

这里写图片描述

说明编译器没有找到附加依赖项的位置,需要在配置属性->VC++目录->库目录下添加.lib文件所在的文件夹。

这里写图片描述

另外也可以采用#pragma comment(lib,”dll1.lib”)的方式来引用链接库:

#include <stdio.h>
#include "DLL1.h"
#pragma comment(lib,"dll1.lib")  //这行代码的作用相当于在链接器中输入附加依赖项
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;
}

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拷贝到当前项目下,然后进行调用:

#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);
};

名字改编问题:

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
    PORT_DLL int add(int a, int b);
    PORT_DLL int subtract(int a, int b);
    PORT_DLL class  Point
    {
    public:
        int Max(int a, int b);
    };
    #endif  // DLL1_H

这里写图片描述

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中添加代码:

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

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

这里写图片描述

在Dll2.def加入如下的代码(如果想要导出的符号名和源文件中定义的函数名不一样,可以按照下面方法定义,比如要将add函数导出为myadd函数):

LIBRARY DLL2

EXPORTS
myadd=add
subtract

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

二、静态链接库的创建与使用

首先在vs2013中创建一个静态库项目,取消预编译头,新建一个头文件add.h和源文件add.cpp 
头文件

#ifndef ADD_H
#define ADD_H
extern"C" int add(int a, int b);
#endif  // ADD_H

源文件

#include "add.h"
int add(int a, int b) {
    return a + b;
}

然后编译,在Debug目录下生成LIB1.lib

引用静态链接库

方法一:在当前解决方案下再添加一个项目并设置为启动项,调用代码如下:

#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;
}

项目->属性->通用属性->引用->添加新引用:

这里写图片描述

方法二:将add.lib拖放到项目的资源文件下

这里写图片描述

方法三:在属性中设置库目录,添加附加依赖项

方法四:在属性中设置库目录,使用#pragma comment( lib, “add.lib”)


猜你喜欢

转载自blog.csdn.net/ice_ly000/article/details/80428057