精华内容
下载资源
问答
  • 动态链接库

    千次阅读 2016-06-02 20:37:58
    简单介绍动态链接库的使用方式,隐式链接方式,动态加载方式

    动态链接库

    建立动态链接库的方法

    新建项目–>win32控制台应用程序–>Dll
    建立动态链接库

    注意:画蓝色线部分是静态链接库

    动态链接库的使用方法

    隐式链接方式

    隐式连接方法使用动态链接库需要用到动态链接库程序生成的.lib文件(.lib文件说明了导出了哪些函数)。
    在使用动态链接库中的函数之前需要先使用extern 声明函数,将.lib文件和.dll文件放到代码所在目录下,在项目链接器的附加依赖项那里填入lib文件名或者使用pragma comment方法加载lib文件。注意:只有一个.lib文件没有.dll文件是不行的,因为.lib文件没有导出函数的具体实现,具体实现在.dll文件中,同样的只有.dll文件没有.lib文件也是不行的,因为没有导出函数的声明.

    简单的函数

    范例:

    //Dll1.cpp文件
    _declspec(dllexport) int add(int a, int b)
    {
        return a + b;
    }

    调用dumpbin 命令来查看dll1中导出的函数
    dumpbinDll1
    可以看到导出了add函数
    将Dll1.lib和Dll.dll文件都放到测试程序代码目录下

    // 测试程序
    #include <stdio.h>
    #pragma comment(lib,"Dll1.lib")
    int _tmain(int argc, _TCHAR* argv[])
    {   
        extern int add(int a, int b);
        printf("4 + 5 = %d \n", add(4, 5));
        return 0;
    }
    //输出
    4 + 5 = 9
    

    此处要声明extern int add(int a,int b);
    或者使用_declspec(dllimport) int add(int a, int b);来替代上面的一句声明
    那么这两种声明方法的区别是什么的?
    第二种声明方法表明这两个函数是从.lib文件导入的,效率更高,我们在使用的时候应尽量使用这种方法。

    dumpbin 的使用方法:

    直接在Dll的debug目录下执行dumpbin命令的方法
    在VS2013的如下目录下Microsoft Visual Studio 12.0\VC\bin有一个vcvars32.bat文件,将这个文件直接拖放到cmd窗口中执行,然后我们就可以在DLL程序的Debug目录下直接执行dumpbin命令而不必进入VS2013的bin目录下去执行dumpbin命令了。(但是有一个缺点是,当你关闭当前cmd窗口下次再使用时就要重新执行一遍那个vcvars32.bat批处理文件)
    - 查看动态链接库导出了哪些函数的方法:在DLL程序的debug目录下执行 dumpbin -exports 文件名.dll
    - 查看应用程序导入了哪些动态链接库函数的方法:在debug目录下执行命令 dumpbin -imports test.exe
    注意,一定是imports,不能是import

    test.exe是我的使用dll的程序的可执行文件

    改进

    为了更好的使用隐式连接方法调用动态链接库,我们在编写动态链接库的时候一般提供一个头文件,这个头文件中包含了我们所导出的函数的声明注释等。注意:我们的动态链接库头文件是提供给调用者使用的。

    //Dll1.h文件
    _declspec(dllimport) int add(int a, int b);

    我们可以把这个头文件放到要调用该动态链接库的项目下,然后包含这个头文件就可以使用动态链接库中的函数了。而不再需要使用上面的方法来声明函数了。但是.lib文件是必不可少的。

    范例:
    //Dll1.cpp文件
    _declspec(dllexport)  int add(int a, int b)
    {
        return a + b;
    }
    //Test.cpp文件
    #include "stdafx.h"
    #include "Dll1.h"
    #pragma comment(lib,"Dll1.lib")
    int _tmain(int argc, _TCHAR* argv[])
    {
        printf("%d\n", add(5, 6));
        return 0;
    }
    //输出结果为11
    进一步优化,为了使Dll头文件发挥更大的作用,我们可以这样做
    范例:
    //Dll1.h
    #ifdef DLL1_API
    #else
    #define DLL1_API _declspec(dllimport)
    #endif
    DLL3_API int add(int a, int b);
    //Dll1.cpp文件
    #define DLL1_API _declspec(dllexport)
    #include"Dll1.h"
    
    int add(int a, int b)
    {
        return a + b;
    }

    在编译一个程序的时候头文件不参与编译,源文件单独编译。在编译源文件的时候首先我们定义了一个宏DLL1_API,然后我们包含了一个头文件,这个头文件需要展开,展开后进入头文件,头文件首先判断是否定义了DLL1_API这个宏,定义了不做处理,这里我们在cpp文件里面已经定义了DLL1_API这个宏,所以它什么也不做,然后DLL1_API int add(int a, int b);这句代码中的DLL1_API实际上是_declspec(dllexport),在动态链接库使用的时候呢就表明我们要导出add()这个函数。当这个头文件给我们的调用者使用的时候,只要调用者没有定义DLL1_API那么这个时候DLL1_API int add(int a,int b);这段代码就相当于导入函数声明,这样就达到了一个dll的头文件同时为调用者和Dll源文件使用的目的。
    注意:隐式链接方式加载动态链接库时,.dll文件可以放到debug目录或者程序代码目录下,但是.lib文件和.h文件必须放到代码目录下。

    定义类

    范例:
    //Dll3.h
    #ifdef DLL3_API
    #else
    #define DLL3_API _declspec(dllimport)
    #endif
    DLL3_API int add(int a, int b);
    class DLL3_API Rectangle
    {
    public:
        float calcArea(float x, float y);
    };
    //Dll3.cpp
    #define DLL3_API _declspec(dllexport)
    #include"Dll3.h"
    
    int add(int a, int b)  //这里不需要再加上DLL3_API因为头文件里面已经声明了要导出这个函数。
    {
        return a + b;
    }
    float Rectangle::calcArea(float x, float y)
    {
        return x*y;
    }
    //测试程序Test.cpp
    #include "Dll3.h"
    #include <stdio.h>
    #pragma comment(lib,"Dll3.lib")
    int _tmain(int argc, _TCHAR* argv[])
    {
        printf("%d\n", add(5, 6));
        Rectangle rt;
        printf("面积是:%f\n", rt.calcArea(4, 5));
        return 0;
    }
    //输出
    11
    20.000

    动态加载方式

    在了解动态加载方式之前先要了解一下为什么要使用动态加载方式。

    预备知识:函数调用约定

    转载自:作者:星轨(oRbIt)
    E_Mail:inte2000@163.com
    原文地址

    函数调用约定不仅决定了发生函数调用时函数参数的入栈顺序,还决定了是由调用者函数还是被调用函数负责清除栈中的参数,还原堆栈。函数调用约定有很多方式,除了常见的__cdecl,__fastcall和__stdcall之外,C++的编译器还支持thiscall方式,不少C/C++编译器还支持naked call方式。这么多函数调用约定常常令许多程序员很迷惑,到底它们是怎么回事,都是在什么情况下使用呢?下面就分别介绍这几种函数调用约定。
    1. _cdecl约定

    编译器的命令行参数是/Gd。__cdecl方式是C/C++编译器默认的函数调用约定,所有非C++成员函数和那些没有用__stdcall或__fastcall声明的函数都默认是__cdecl方式,它使用C函数调用方式,函数参数按照从右向左的顺序入栈,函数调用者负责清除栈中的参数,由于每次函数调用都要由编译器产生清除(还原)堆栈的代码,所以使用__cdecl方式编译的程序比使用__stdcall方式编译的程序要大很多,但是__cdecl调用方式是由函数调用者负责清除栈中的函数参数,所以这种方式支持可变参数,比如printf和windows的API wsprintf就是__cdecl调用方式。对于C函数,__cdecl方式的名字修饰约定是在函数名称前添加一个下划线;对于C++函数,除非特别使用extern “C”,C++函数使用不同的名字修饰方式。
    2. _stdcall

    编译器的命令行参数是/Gz,__stdcall是Pascal程序的缺省调用方式,大多数Windows的API也是__stdcall调用约定。__stdcall函数调用约定将函数参数从右向左入栈,除非使用指针或引用类型的参数,所有参数采用传值方式传递,由被调用函数负责清除栈中的参数。对于C函数,__stdcall的名称修饰方式是在函数名字前添加下划线,在函数名字后添加@和函数参数的大小,例如:_functionname@number

    1. thiscall

    thiscall只用在C++成员函数的调用,函数参数按照从右向左的顺序入栈,类实例的this指针通过ECX寄存器传递。需要注意的是thiscall不是C++的关键字,不能使用thiscall声明函数,它只能由编译器使用。
    4. _fastcall

    编译器的命令行参数是/Gr。__fastcall函数调用约定在可能的情况下使用寄存器传递参数,通常是前两个 DWORD类型的参数或较小的参数使用ECX和EDX寄存器传递,其余参数按照从右向左的顺序入栈,被调用函数在返回之前负责清除栈中的参数。编译器使用两个@修饰函数名字,后跟十进制数表示的函数参数列表大小,例如:@function_name@number。需要注意的是__fastcall函数调用约定在不同的编译器上可能有不同的实现,比如16位的编译器和32位的编译器,另外,在使用内嵌汇编代码时,还要注意不能和编译器使用的寄存器有冲突。
    5. naked call

    采用前面几种函数调用约定的函数,编译器会在必要的时候自动在函数开始添加保存ESI,EDI,EBX,EBP寄存器的代码,在退出函数时恢复这些寄存器的内容,使用naked call方式声明的函数不会添加这样的代码,这也就是为什么称其为naked的原因吧。naked call不是类型修饰符,故必须和_declspec共同使用。
    VC的编译环境默认是使用__cdecl调用约定,也可以在编译环境的Project Setting…菜单-》C/C++ =》Code Generation项选择设置函数调用约定。也可以直接在函数声明前添加关键字__stdcall、__cdecl或__fastcall等单独确定函数的调用方式。在Windows系统上开发软件常用到WINAPI宏,它可以根据编译设置翻译成适当的函数调用约定,在WIN32中,它被定义为__stdcall。

    预备知识:编译器的改名规则:

    对于C语言编译器:

    对于__stdcall调用约定,编译器和链接器会在输出函数名前加上一个下划线前缀,函数名后面加上一个“@”符号和其参数的字节数,例如_functionname@number。__cdecl调用约定仅在输出函数名前加上一个下划线前缀,例如_functionname。__fastcall调用约定在输出函数名前加上一个“@”符号,后面也是一个“@”符号和其参数的字节数,例如@functionname@number

    对于C++编译器

    函数修饰都是以一个“?”开始,后面紧跟函数的名字,再后面是参数表的开始标识和按照参数类型代号拼出的参数表。对于__stdcall方式,参数表的开始标识是“@@YG”,对于__cdecl方式则是“@@YA”,对于__fastcall方式则是“@@YI”。
    例如:我编写了如下一个程序,显示指明采用_cdecl调用约定

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

    然后我用dumpin去查看编译器导出函数的情况如下
    显式指明C语言调用约定.png
    然后我再改变一下函数调用约定为_stdcall,再次查看

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

    显式指明标准调用约定

    参数表的拼写代号如下所示:
    X–void
    D–char
    E–unsigned char
    F–short
    H–int
    I–unsigned int
    J–long
    K–unsigned long(DWORD)
    M–float
    N–double
    _N–bool
    U–struct
    对于C++的类成员函数(其调用方式是thiscall),函数的名字修饰与非成员的C++函数稍有不同,首先就是在函数名字和参数表之间插入以“@”字符引导的类名;其次是参数表的开始标识不同,公有(public)成员函数的标识是“@@QAE”,保护(protected)成员函数的标识是“@@IAE”,私有(private)成员函数的标识是“@@AAE”,如果函数声明使用了const关键字,则相应的标识应分别为“@@QBE”,“@@IBE”和“@@ABE”。如果参数类型是类实例的引用,则使用“AAV1”,对于const类型的引用,则使用“ABV1”。

    使用动态链接方式加载动态链接库的原因

    首先分析隐式链接方式使用动态链接库的缺点,举个例子:


    //Dll2.h
    #ifdef DLL2_API
    #else
    #define DLL2_API _declspec(dllimport)
    #endif
    DLL2_API int add(int a, int b);
    //Dll2.cpp
    #define DLL2_API _declspec(dllexport)
    #include "Dll2.h"
    int add(int a, int b)
    {
        return a + b;
    }

    使用dumpbin查看导出函数
    Dll2导出函数改名

    可以看到此时dll2导出的add函数名字发生了改编,我们使用测试程序来做测试

    //测试代码
    #include "stdio.h"
    #include "Dll2.h"
    #pragma comment (lib,"Dll2.lib")
    int _tmain(int argc, _TCHAR* argv[])
    {   
        printf("4 + 5 = %d\n", add(4, 5));
        return 0;
    }
    //输出 4 + 5 = 9

    当然这种情况下并没有发生错误,因为我们是在同一个编译器下进行的开发,Dll和测试程序都是使用的默认函数调用方式(我使用的是VS2013,默认的函数调用方式是C语言调用约定),所以不会发生错误,现在我们来做出一些改变,我将Dll2中中的函数调用方式改为标准调用约定,

    //Dll2.h
    #ifdef DLL2_API
    #else
    #define DLL2_API _declspec(dllimport)
    #endif
    DLL2_API int _stdcall add(int a, int b);
    //Dll2.cpp
    #define DLL2_API _declspec(dllexport)
    #include "Dll2.h"
    int _stdcall add(int a, int b)
    {
        return a + b;
    }

    使用dumpbin 查看dll2中的导出函数
    Dll2标准调用约定
    此时,我们去使用测试程序来做测试,结果依然是正确的。因为我们的测试程序包含了Dll2.h头文件,add函数在头文件中已经声明了采用标准调用约定,故不会发生错误,感觉说了半天没说到重点。其实,个人觉得使用动态加载方式的主要目的是为了在防止动态链接库在不同的编译器上能够正确加载,因为不同的编译器对函数的改编规则不同,至于函数调用约定一定要相同,如果你的函数调用约定不同,即使采用动态加载方式那么你也无法正确加载执行动态链接库中的函数。看完下文应该有所收获。现在,回到最开始的地方:

    //Dll1.cpp文件
    _declspec(dllexport) int _stdcall add(int a, int b)
    {
        return a + b;
    }
    > 我改变了add函数的调用约定
    // 测试程序
    #include <stdio.h>
    #pragma comment(lib,"Dll1.lib")
    int _tmain(int argc, _TCHAR* argv[])
    {   
        extern int add(int a, int b);
        printf("4 + 5 = %d \n", add(4, 5));
        return 0;
    }
    > 编译时报错: unresolved external symbol "int __cdecl add(int,int)" (?add@@YAHHH@Z) referenced in function _wmain,程序无法执行,如果我将    extern int add(int a, int b);改为     extern int _stdcall add(int a, int b);程序可以正常执行

    使用动态链接方式加载动态链接库

    这种方式就是为了解决编译器改名带来的问题。主要用到了模块定义文件(.def文件)
    1. 新建一个.def文件,将之加载到DLL工程目录下,并且在项目工程的属性里要进行设置
    设置def
    2. 范例:

    .def文件
    LIBRARY Dll3   //Dll3是动态链接库的名字
    
    EXPORTS         //导出标识
    add                  //导出的函数
    //dll源文件
    int add(int a, int b)
    {
        return a + b;
    }
    //测试程序
    //测试程序
    #include "stdafx.h"
    #include<Windows.h>
    int _tmain(int argc, _TCHAR* argv[])
    {
        HINSTANCE hInst;
        hInst = LoadLibrary(_T("Dll3.dll"));
        typedef int(*ADDPROC)(int a, int b);
        ADDPROC Add = (ADDPROC)GetProcAddress(hInst, "add");
        if (!Add)
        {
            printf("获取函数地址失败\n");
            return 0;
        }
        printf("%d\n",Add(5, 6));
        return 0;
    }
    //输出结果11

    解析:typedef int(*ADDPROC)(int a, int b);
    将(*ADDPROC)(int a, int b)这一部分看做一个整体相当于定义了一个新的整型的类型。现在将这一部分分开看(int a, int b),这一部分相当于一个函数,函数内部有两个int类型的参数,返回值是int类型。(*ADDPROC)这相当于定义了一个函数指针类型,它所表示的函数有两整型个参数,函数的返回类型也是整型。我们定义这个函数指针类型的目的是在需要的时候产生一个函数指针变量。用来接收GetProcAddress的返回值。

    HINSTANCE LoadLibrary( LPCTSTR lpLibFileName // address of filename of executable module);
    作用:映射指定的可执行模块到一个调用地址空间,不光可以加载一个动态链接库还可以加载一个可执行程序,返回的是一个实例句柄(它和HMODULE可以通用)
    FARPROC GetProcAddress(
    HMODULE hModule, // handle to DLL module
    LPCSTR lpProcName // name of function
    );
    获取指定的导出动态链接库的函数地址,第一个参数是动态链接库的模块句柄,第二个是函数名

    解释:为什么使用模块定义文件就可以避免编译器改名带来的问题呢?

    当链接器在链接的时候会分析模块定义文件,当它发现模块定义文件中EXPORTS下面有add符号的名字和我们在源文件中定义的函数名字是一样的,它就会使用我们在模块定义文件中所写的名字导出我们的add函数。使用dumpbin 查看我们使用模块定义文件导出的函数的名字,可以看到编译器并没有对它发生改编
    函数名未改编1

    EXPORTS的用法
    如果我们想自己命名导出的函数的名字,可以这样做,在.def文件中如下写法

    LIBRARY Dll3
    EXPORTS
    add1 = add

    EXPORTS用法

    可以看到导出的函数名字变为了add1,这个时候我们在测试程序中使用该函数时也应该使用add1来调用该函数

    现在我来对动态链接库的函数调用约定进行更改,而不改变测试程序中的函数调用约定看看会发生什么。
    只改变Dll3.cpp文件中的代码

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

    查看Dll3的导出函数
    Dll3的导出函数
    可以看到函数的名字并没有发生改编,所不同的是括号里面的内容发生了变化,仔细观察。再次运行测试程序结果发现不能运行,因为在VS2013中默认的调用约定是C语言调用约定,我使用typedef定义函数指针时没有改变调用约定,导致无法获取函数地址,只需要对测试程序做一些改动即可正常运行
    typedef int(_stdcall *ADDPROC)(int a, int b);//改变调用约定为标准调用约定
    这些问题一定要注意。

    小结:使用模块定义文件的目的主要是为了让我们的动态链接库在其它语言中、在其它的编译器当中能够使用因为这样做能够使动态链接库所输出的函数的符号名不发生改变。

    DllMain

    DllMain是Dll的入口函数,
    BOOL WINAPI DllMain(
    HINSTANCE hinstDLL, // handle to DLL module
    DWORD fdwReason, // reason for calling function
    LPVOID lpvReserved // reserved
    );

    MFC下的动态链接库的三种类型

    1. 常规的动态链接库使用MFC的静态链接
      发布的时候只需要提供动态链接库就行了
    2. 常规的动态链接库使用共享的MFC动态链接库
      发布的时候要确保用户的机器上存在MFC的动态链接库,如果MFC的动态链接库在用户的机器上不存在,那么我们发布的动态链接库就不能被加载。
    3. MFC的扩展动态链接库
      它也是使用的MFC的共享的动态链接库,它和常规的动态链接库的区别就是它可以到处MFC的类,常规动态链接库不能导出MFC的类,只能导出普通的C++的类。
      MFC的动态链接库对MFC提供了很好的支持。
    展开全文
  • C++ 动态链接库和静态链接库

    千次阅读 2019-09-23 15:59:58
    动态静态的区别静态链接库静态链接库的创建方法lib的调用动态链接库动态链接库的创建方法dll的使用隐式链接显式链接 源程序编译链接生成文件格式   首先贴出从源程序生成可执行文件的过程。 源程序(sour...

      今天对C++生成动态链接路和静态链接库非常感兴趣,必须搞定,否则都没有心情干其他事了。Let’s go~


    源程序编译链接生成文件格式

      首先贴出从源程序生成可执行文件的过程。
    源程序(source code)->预处理(preprocessor) ->编译和优化 (compiler) - >生成目标文件(object code) ->链接(Linker)->可执行文件
    生成文件个是对应表

       源程序 预处理 编译和优化 生成目标文件 链接 可执行文件
    文件后缀 .h、.cpp、.c .i .s .o .out .exe
    语言 c\c++ c\c++ 汇编 二进制 二进制 二进制

    下面是实际举例说明:
    1.h

    #pragma once
    
    void print();  //我是1.h的注释
    

    1.cpp

    #include "1.h"
    #include<iostream>
    //我是1.cpp的注释
    void print()
    {
    	std::cout << "print函数运行成功" << std::endl;
    }
    

    main.cpp

    #include <iostream>
    #include "1.h"
    #define AA "this is a macro!"
    
    int main()
    {
    #ifdef AA  //我是main.cpp的注释
    	print();
    	std::cout << AA << std::endl;
    #endif
    
    	int a = 1;
    	int b = 5;
    	int c = a + b;
    	std::cout << c << std::endl;
    
    	system("pause");
    	return 0;
    }
    

    预编译

    预编译阶段先介绍编译器。先介绍几个概念:GNU、GCC、gcc、g++
    按照概念范围大小排序如下:
    GNU:全称“GNU’s Not UNIX”,这样的递归缩写,就是一个强调而已,没有其他信息。GNU是一个操作系统。目前实在没有搞明白,GNU作为一个操作系统,它不就应该和Linux、Windows等操作系统并列吗?为什么和C、C++有关系?开源?通用?而且我们通常用的GSL库就是GNU的科学计算库。或许和Linux的某些内核函数我们也可以使用一样吧,通用,所以可以借用???无论如何,就是很大的概念。
    GCC:GNU Compiler Collection(GUN 编译器集合),它可以编译C、C++、JAV、Fortran、Pascal、Object-C、Ada等语言。
    g++:GCC中的GUN C++ Compiler(C++编译器)
    gcc:GCC中的GUN C Compiler(C 编译器)
      由于编译器是可以更换的,所以gcc不仅仅可以编译C文件,更准确的说法是:gcc调用了C compiler,而g++调用了C++ compiler。
      gcc and g++分别是gnu的c & c++编译器,gcc/g++在执行编译工作的时候,总共需要4步:

    1.预处理,生成.i的文件[预处理器cpp];
    2.将预处理后的文件转换成汇编语言,生成文件.s[编译器egcs];
    3.由汇编变为目标代码(机器代码)生成.o的文件[汇编器as];
    4.连接目标代码,生成可执行程序[链接器ld]。
    

      gcc和g++的主要区别:

    1. 对于 .c和.cpp文件,gcc分别当做c和cpp文件编译(c和cpp的语法强度是不一样的)

    2. 对于 .c和.cpp文件,g++则统一当做cpp文件编译

    3. 使用g++编译文件时,g++会自动链接标准库STL,而gcc不会自动链接STL

    4. gcc在编译C文件时,可使用的预定义宏是比较少的

    5. gcc在编译cpp文件时/g++在编译c文件和cpp文件时(这时候gcc和g++调用的都是cpp文件的编译器),会加入一些额外的宏,这些宏如下:

      #define GXX_WEAK 1
      #define __cplusplus 1
      #define __DEPRECATED 1
      #define GNUG 4
      #define __EXCEPTIONS 1
      #define private_extern extern

    6. 在用gcc编译c++文件时,为了能够使用STL,需要加参数 –lstdc++ ,但这并不代表 gcc –lstdc++ 和 g++等价,它们的区别不仅仅是这个。
      主要参数:
      -g - turn on debugging (so GDB gives morefriendly output)
      -Wall - turns on most warnings
      -O or -O2 - turn on optimizations
      -o - name of the output file
      -c - output an object file (.o)
      -I - specify an includedirectory
      -L - specify a libdirectory
      -l - link with librarylib.a

    使用示例:g++ -ohelloworld -I/homes/me/randomplace/include helloworld.C
    以上参考来源于下面链接。感谢原作者。https://www.cnblogs.com/oxspirt/p/6847438.html


    预处理器主要负责以下的几处:

    1.宏的替换
    2.删除注释
    3.处理预处理指令,如#include,#ifdef
    

    下面进行实例验证。
    现找到:
    在这里插入图片描述
    点击进入你的工程目录对应的cpp位置。
    输入g++ –E main.cpp>main.i进行预编译生成main.i文件。
    在这里插入图片描述
    打开main.i发现里面内容实在很多,前面一大堆不知道啥,大概就是在干#include 和#include "1.h"这两件事。到文件最后:
    在这里插入图片描述
    看看行号,就简简单单的main函数,就那么多行了。重点是:main函数中的头文件被替换了(不管替换成什么了),注释不见了,宏被替换了。
    同理生成1.i文件
    在这里插入图片描述
    也是相同的处理。
    当相同地编译运行1.h文件时,有了如下警告:
    在这里插入图片描述
    单词“pragma”就是编译指示的意思,警告你1.h只在main文件中编译一次,此操作不可行。


    编译和优化

    这个步骤作用如下:

    词法分析 – 识别单词,确认词类;比如int i;知道int是一个类型,i是一个关键字以及判断i的名字是否合法。
    语法分析 – 识别短语和句型的语法属性;
    语义分析 – 确认单词、短语和句型的语义特征;
    代码优化 – 修辞、文本编辑;
    代码生成 – 生成译文。
    内联函数的替换就发生在这一阶段
    

    预编译和编译都是GCC来完成的。

    编译

    按照g++ –E main.cpp>main.i文件的语句写g++ –E main.i>main.s,然后报错了。
    在这里插入图片描述
    然后用g++ –E main.cpp>main.s也是相同的报错。
    后来搜到有人使用g++ -S main.cpp,生成成功。.s文件表示是汇编文件,用编辑器打开就都是汇编指令。

    	.file	"main.cpp"
    .lcomm __ZStL8__ioinit,1,1
    	.def	___main;	.scl	2;	.type	32;	.endef
    	.section .rdata,"dr"
    LC0:
    	.ascii "this is a macro!\0"
    LC1:
    	.ascii "pause\0"
    	.text
    	.globl	_main
    	.def	_main;	.scl	2;	.type	32;	.endef
    _main:
    	leal	4(%esp), %ecx
    	andl	$-16, %esp
    	pushl	-4(%ecx)
    	pushl	%ebp
    	movl	%esp, %ebp
    	pushl	%ecx
    	subl	$36, %esp
    	call	___main
    	call	__Z5printv
    	movl	$LC0, 4(%esp)
    	movl	$__ZSt4cout, (%esp)
    	call	__ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
    	movl	$__ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_, (%esp)
    	movl	%eax, %ecx
    	call	__ZNSolsEPFRSoS_E
    	subl	$4, %esp
    	movl	$1, -12(%ebp)
    	movl	$5, -16(%ebp)
    	movl	-12(%ebp), %edx
    	movl	-16(%ebp), %eax
    	addl	%edx, %eax
    	movl	%eax, -20(%ebp)
    	movl	-20(%ebp), %eax
    	movl	%eax, (%esp)
    	movl	$__ZSt4cout, %ecx
    	call	__ZNSolsEi
    	subl	$4, %esp
    	movl	$__ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_, (%esp)
    	movl	%eax, %ecx
    	call	__ZNSolsEPFRSoS_E
    	subl	$4, %esp
    	movl	$LC1, (%esp)
    	call	_system
    	movl	$0, %eax
    	movl	-4(%ebp), %ecx
    	leave
    	leal	-4(%ecx), %esp
    	ret
    	.def	___tcf_0;	.scl	3;	.type	32;	.endef
    ___tcf_0:
    	pushl	%ebp
    	movl	%esp, %ebp
    	subl	$8, %esp
    	movl	$__ZStL8__ioinit, %ecx
    	call	__ZNSt8ios_base4InitD1Ev
    	leave
    	ret
    	.def	__Z41__static_initialization_and_destruction_0ii;	.scl	3;	.type	32;	.endef
    __Z41__static_initialization_and_destruction_0ii:
    	pushl	%ebp
    	movl	%esp, %ebp
    	subl	$24, %esp
    	cmpl	$1, 8(%ebp)
    	jne	L4
    	cmpl	$65535, 12(%ebp)
    	jne	L4
    	movl	$__ZStL8__ioinit, %ecx
    	call	__ZNSt8ios_base4InitC1Ev
    	movl	$___tcf_0, (%esp)
    	call	_atexit
    L4:
    	leave
    	ret
    	.def	__GLOBAL__sub_I_main;	.scl	3;	.type	32;	.endef
    __GLOBAL__sub_I_main:
    	pushl	%ebp
    	movl	%esp, %ebp
    	subl	$24, %esp
    	movl	$65535, 4(%esp)
    	movl	$1, (%esp)
    	call	__Z41__static_initialization_and_destruction_0ii
    	leave
    	ret
    	.section	.ctors,"w"
    	.align 4
    	.long	__GLOBAL__sub_I_main
    	.ident	"GCC: (i686-posix-sjlj, built by strawberryperl.com project) 4.9.2"
    	.def	__Z5printv;	.scl	2;	.type	32;	.endef
    	.def	__ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc;	.scl	2;	.type	32;	.endef
    	.def	__ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_;	.scl	2;	.type	32;	.endef
    	.def	__ZNSolsEPFRSoS_E;	.scl	2;	.type	32;	.endef
    	.def	__ZNSolsEi;	.scl	2;	.type	32;	.endef
    	.def	_system;	.scl	2;	.type	32;	.endef
    	.def	__ZNSt8ios_base4InitD1Ev;	.scl	2;	.type	32;	.endef
    	.def	__ZNSt8ios_base4InitC1Ev;	.scl	2;	.type	32;	.endef
    	.def	_atexit;	.scl	2;	.type	32;	.endef
    
    

    汇编咱也看不懂,咱也不敢说,只是看明白了1+5,add 了存在1和5的地址。
    同理生成1.s文件。也是看不懂。

    优化

      优化处理是编译系统中一项比较艰深的技术。它涉及到的问题不仅同编译技术本身有关,而且同机器的硬件环境也有很大的关系。优化一部分是对中间代码的优化。这种优化不依赖于具体的计算机。另一种优化则主要针对目标代码的生成而进行的。上图中,我们将优化阶段放在编译程序的后面,这是一种比较笼统的表示。
    对于前一种优化,主要的工作是删除公共表达式、循环优化(代码外提、强度削弱、变换循环控制条件、已知量的合并等)、复写传播,以及无用赋值的删除,等等。
      后一种类型的优化同机器的硬件结构密切相关,最主要的是考虑是如何充分利用机器的各个硬件寄存器存放的有关变量的值,以减少对于内存的访问次数。另外,如何根据机器硬件执行指令的特点(如流水线、RISC、CISC、VLIW等)而对指令进行一些调整使目标代码比较短,执行的效率比较高,也是一个重要的研究课题。
      经过优化得到的汇编代码必须经过汇编程序的汇编转换成相应的机器指令,方可能被机器执行。
      详细可见下面链接,感谢原作者。
    https://blog.csdn.net/qq_30647245/article/details/80558

    生成目标文件

      这一步是从汇编程序生成目标代码(机器代码)。
    GCC语句是g++ -c main.cpp
      功能:.o是GCC生成的目标文件,除非你是做编译器和连接器调试开发的,否则打开这种.o没有任何意义。二进制机器码一般人也读不了,就是下图这样。
    在这里插入图片描述

    链接

      连接各个.o目标代码文件,生成可执行程序。必须已经生成1.o和main.o,这样才能链接需要的.o文件生成.exe。
    g++语句为 g++ 1.o main.o -o Test.exe
    双击生成的Test.exe
    在这里插入图片描述
    这样,完整的从源程序到exe就结束了。其中的g++命令语句参考链接为https://www.jianshu.com/p/e5e9925a6158,感谢原作者。


    下面终于要进入今天的正题链接库是怎么回事?

    什么是库?

      库是写好的现有的,成熟的,可以复用的代码。现实中每个程序都要依赖很多基础的底层库,不可能每个人的代码都从零开始,因此库的存在意义非同寻常。本质上来说库是一种可执行代码的二进制形式,可以被操作系统载入内存执行。库有两种:静态库(.a、.lib)和动态库(.so、.dll)。所谓静态、动态是指链接。

    在这里插入图片描述

    动态静态的区别

      上面刚刚提到了静态链接库和动态链接库,如果采用静态链接库,则无论你愿不愿意,在链接程序时,lib中的指令都被直接(通过拷贝的方式)包含在最终生成的EXE文件中了,这是在第5步链接程序中可以知道的。动态链接库在链接程序时,只是记录了少量必要信息,在实际程序执行时才动态链接库的全部内容将被映射到运行时相应进程的虚地址空间。动态链接程序将根据可执行程序中记录的信息找到相应的函数代码。所以,当单独执行debug文件或者release文件中的exe的时候,静态链接库不需要放在exe的目录下,但是动态链接库必须放在exe的目录下。


    静态链接库

      之所以成为静态库,是因为在链接阶段,会将汇编生成的目标文件.o与引用到的库一起链接打包到可执行文件中。因此对应的链接方式称为静态链接。
      试想一下,静态库与汇编生成的目标文件一起链接为可执行文件,那么静态库必定跟.o文件格式相似。其实一个静态库可以简单看成是一组目标文件(.o/.obj文件)的集合,即很多目标文件经过压缩打包后形成的一个文件。静态库特点总结:

    1	静态库对函数库的链接是放在编译时期完成的。
    2	程序在运行时与函数库再无瓜葛,移植方便。
    3	浪费空间和资源,因为所有相关的目标文件与牵涉到的函数库被链接合成一个可执行文件。
    

    静态链接库的优点和缺点:
    1.代码装载速度快,执行速度略比动态链接库快;
    2.只需保证在开发者的计算机中有正确的.LIB文件,在以二进制形式发布程序时不需考虑在用户的计算机上.LIB文件是否存在及版本问题,可避免DLL地狱等问题。
    3.使用静态链接生成的可执行文件体积较大,包含相同的公共代码,造成浪费;

    lib文件是必须在编译期就连接到应用程序中的,而dll文件是运行期才会被调用的。如果有dll文件,那么对应的lib文件一般是一些索引信息,具体的实现在dll文件中。如果只有lib文件,那么这个lib文件是静态编译出来的,索引和实现都在其中。

    静态链接库的创建方法

      首先创建静态库,要封装的函数只是一系列子函数:

    //main_test.cpp
    #include <iostream>
    #include "1.h"
    #define AA "this is a macro!"
    
    void main_test()
    {
    #ifdef AA  //我是main.cpp的注释
    	print();
    	std::cout << AA << std::endl;
    #endif
    
    	int a = 1;
    	int b = 5;
    	int c = a + b;
    	std::cout << c << std::endl;
    }
    
    void my_func()
    {
    	std::cout << "我是第二个函数" << std::endl;
    }
    
    //main_test.h
     #pragma once
     void main_test();
     void my_func();
    

    在工程属性里更改如下:不生成exe,就生成静态库lib
    在这里插入图片描述
    按F7或者点击生成解决方案之后就可以在Debug里看到生成的lib文件了。

    lib的调用

    将这里的.lib文件和.h文件拷贝,到要引用的目录下,#include “*.h”和#pragma comment (lib,".lib"),在当前工程中就可以直接用里面的函数了。lib文件还有其他方法添加,这里就不赘述了。


    动态链接库

      代码复用是提高软件开发 效率的重要途径。一般而言,只要某部分代码具有通用性,就可将它构造成相对独立的功能模块并在之后的项目中重复使用。比较常见的例子是各种应用程序框架, 如ATL、MFC等,它们都以源代码的形式发布。由于这种复用是“源码级别”的,源代码完全暴露给了程序员,因而称之为“白盒复用”。“白盒复用”的缺点比较多,总结起来有4点:

    1 暴露了源代码;
    2	容易与程序员的“普通”代码发生命名冲突;
    3	多份拷贝,造成存储浪费;
    4	更新功能模块比较困难。
    

      实际上,以上4点概括起来就是“暴露的源代码”造成“代码严重耦合”。为了弥补这些不足,就提出了“二进制级别”的代码复用。使用二进制级别的代码复用一定程度上隐藏了源代码,对于缓解代码耦合现象起到了一定的作用。这样的复用被称为“黑盒复用”。
      另一个问题是静态库对程序的更新、部署和发布页会带来麻烦。如果静态库liba.lib更新了,所以使用它的应用程序都需要重新编译、发布给用户(对于玩家来说,可能是一个很小的改动,却导致整个程序重新下载,全量更新)。

    lib文件是必须在编译期就连接到应用程序中的,而dll文件是运行期才会被调用的。如果有dll文件,那么对应的lib文件一般是一些索引信息,具体的实现在dll文件中。如果只有lib文件,那么这个lib文件是静态编译出来的,索引和实现都在其中。

    动态链接库的创建方法

    创建动态库的方法略有不同。先是将属性里的配置类型改为动态库(dll)
    在头文件中需要加入宏命令。

    #pragma once
    
    #ifdef __DLLEXPORT
    #define __DLL_EXP _declspec(dllexport)    // 导出函数 - 生成dll文件时使用
    #else
    #define __DLL_EXP _declspec(dllimport)    // 导入函数 -使用dll是使用
    #endif // __DLLEXPORT
    
    void main_test();
    void my_func();
    

    如果是生成dll文件,需要在属性->C\C+±>预处理器->预处理器定义中加上宏。
    在这里插入图片描述
      生成解决方案后在Debug中就生成了 .dll和.lib文件。我开始的时候怎么都只有dll,没有lib,后来参考别人的建议,在在工程上右键 -> 添加 -> 新建项 -> 选"模块定义文件(.def)" -> 随便输入个名字 -> 添加,添加的名字任意,可以默认,里面只有LIBRARY这一句,不用管,这时候再生成解决方案,Debug里就有lib和dll了。
      def文件中内容要加上函数名

    //Source.def
    LIBRARY Project2
    
    EXPORTS
    main_test @1
    my_func @2
    

    注意:.def文件中的第一条 LIBRARY 语句不是必须的,但LIBRARY 语句后面的 DLL 的名称必须正确,即与生成的动态链接库的名称必须匹配。
      EXPORTS 语句列出名称,可能的话还会列出 DLL 导出函数的序号值。通过在函数名的后面加上 @ 符和一个数字,给函数分配序号值。当指定序号值时,序号值的范围必须是从 1 到 N,其中 N 是 DLL 导出函数的个数。
    main_test.cpp文件为:

    #include <iostream>
    #include "main_test.h"
    #pragma comment(lib, "Project2.lib")
    
    void main()
    {
    	main_test();
    	my_func();
    	system("pause");
    }
    

    F7生成解决方案就会生成dll、lib文件。

    dll的使用

    调用分为2种。

    隐式链接

    需要用到.dll、.lib和.h文件。
    这里的.lib文件不再是静态链接库的意思,而是导入库的意思。
    将三个文件复制到需要使用的工程目录中,
    将.dll和.lib放在一个文件夹内。
    在这里插入图片描述
    测试工程main.cpp

    #include <iostream>
    #include "main_test.h"
    #pragma comment(lib, "ku\\Project2.lib")
    
    void main()
    {
    	main_test();
    	my_func();
    	system("pause");
    }
    

    运行结果
    在这里插入图片描述

    显式链接

    需要用到.dll和.h文件。
      我暂时不需要用显式调用,所以以后用到再说。


    That’s all. Let’s enjoy it ~

    展开全文
  • 动态链接库编程范例

    2016-04-08 12:37:12
    我们经常在做各种二次开发的时候都会使用卖方提供的SDK进行开发,往往卖方并不愿理提供给我源程序而是给我一个提供一系列函数接口说明和一些封装的动态链接库。   今天我们就要说说静态链接库和动态链接库,...

    我们经常在做各种二次开发的时候都会使用卖方提供的SDK进行开发,往往卖方并不愿理提供给我源程序而是给我一个提供一系列函数接口说明和一些封装的动态链接库。

     

    今天我们就要说说静态链接库和动态链接库,静态链接库往往以lib后缀结尾,然而动态链接库往往以dll后缀结束。是我们不能仅仅以后缀名作为它们二者的区别,比如动态链接库也可能存在lib后缀的文件,然而这种情况下lib文件是保存一些函数符号,正则的提供运行代码的指令部分仍然存在在dll文件中。

     

    其实从本质上来说,动态链接库是我们再运行程序的时候在进行链接,然而静态链接库的代码指令是直接包含在最终的应用序中,所以我们有使用的动态链接库在生成最终的程序里,如果一旦将该动态链接库文件删除,程序将提示确实xxx.dll文件。

    然而使用静态链接库生成的最终程序里,我们直接删除静态链接库文件,程序仍然将正常执行。因为在生成程序的过程中,静态链接库中的代码指令已经完全的嵌入到了应用程序中了。windows提供的大多数服务都是以dll形式提供的。然而静态链接库然可移植性强,但是会造成程序很庞大,所以往往大多数开发接口都是以动态链接库的形式提供的。

     

    下面将展示一个简单的动态链接库的范例:
    1.打开vc++6.0新建一个动态链接库工程,取名为Dlldemo:

     

     

    2.创建一个空的DLL工程:

     

     

    3.我们添加一个头文件为AddDll.h 一个源文件为AddDll.cpp在本范例中我们将要把下面这个函数封装为动态链接库提供给第三方开发:

    int Add(int a,int b)
    {
        return a+b;
    }

     

    4.我们在头文件里添加需要导出的函数的声明:

    1 extern "C" _declspec(dllexport) int Add(int a,int b);

     

     

    5.然后在源文件里面实现这个导出函数:

    1 #include "AddDll.h"
    2 
    3 int Add(int a,int b)
    4 {
    5     return a+b;
    6 }

     

    6.编译工程,此时便在工程目录下生成了我们需要的dll文件,如下图所示:

     

    其中AddDll.h提供动态链接库导出函数的声明,Dlldemo.dll文件提供了实际导出函数的实现代码,不过不再是源代码的形式。Dlldemo.lib提供了导出函数的一些符号列表。

     

    至此动态链接库便成功生成了。

     

     

    下面我们用一个例子测试:

    1.新建一个测试工程为test,在工程中添加一个test.cpp用于测试

     

    2.将上面的三个文件拷贝到test工程目录

     

    3.将Dlldemo.lib用以下两种方式中的一种链接到程序:

        一.通过工程-设置-链接-对象/库模块中 添加Dlldemo.lib

        二.或者在程序通过#pragma comment(lib,"Dlldemo.lib")

     

    4.在test.cpp包含头文件AddDll.h,同时用以下测试代码:

    复制代码
     1 #include "AddDll.h"
     2 #include <iostream>
     3 using namespace std;
     4 #pragma comment(lib,"Dlldemo.lib")
     5 int main()
     6 {
     7     int num1,num2;
     8     cout<<"Please input the two number:"<<endl;
     9     cin>>num1;
    10     cin>>num2;
    11     int AddResult;
    12     AddResult=Add(num1,num2);
    13     cout<<"The Add Result is: "<<AddResult<<endl;
    14     return 0;
    15 }
    复制代码

    运行结果:

     

    测试成功咯!!!


    展开全文
  • Step#1: 新建动态链接库文件 文件->新建->项目->选择Visual C+±>Windows桌面->动态链接库 名称DLL-1。新建成功之后, 平台设置为Debug x64平台。 默认生成以下文件: 在Dll-1.cpp中输入如下...

    参考文献: 孙鑫. VC++深入详解.修订版[M]. 电子工业出版社, 2012. 第19章

    Step#1: 新建动态链接库文件
    文件->新建->项目->选择Visual C+±>Windows桌面->动态链接库
    名称DLL-1。新建成功之后, 平台设置为Debug x64平台。
    在这里插入图片描述
    默认生成以下文件:
    在这里插入图片描述
    在Dll-1.cpp中输入如下代码:
    #include “stdafx.h”
    int add(int a, int b)
    {
    return a + b;
    }
    int subtract(int a, int b)
    {
    return a - b;
    }
    点击: 生成→生成解决方案
    在这里插入图片描述
    显示输出结果: 生成:成功1个;
    在这里插入图片描述
    在E:\1-C++\Dll-1\x64\Debug中可以看到生成的Dll-1.dll文件。
    在这里插入图片描述
    应用程序如果想要访问某个DLL中的函数,那么该函数必须是已经被导出的函数,为了查看一个DLL中有哪些导出函数,可以利用Visual Studio提供的命令行工具:Dumpbin来实现,如何实现可参考文献:https://blog.csdn.net/tpz789/article/details/89635334 。

    利用Dumpbin命令查看Dll-1.dll的信息,发现没有任何与函数有关的信息,这说明Dll-1.dll中没有导出函数。如下图。
    在这里插入图片描述

    为了让DLL导出一些函数,需要在每一个将要被导出的函数前面添加标识符:_declspec(dllexport)。于是修改dll-1.cpp文件中的代码,在add函数和subtract函数的定义前面加上_declspec(dllexport)。
    // Dll-1.cpp : 定义 DLL 应用程序的导出函数。
    #include “stdafx.h”
    _declspec(dllexport) int add(int a, int b)
    {
    return a + b;
    }
    _declspec(dllexport) int subtract(int a, int b)
    {
    return a - b;
    }
    重新生成Dll-1动态链接库,输出窗口中显示如下信息:
    在这里插入图片描述
    可以看到,这时又生成了两个新文件,其中Dll-1.lib文件是引入库文件,在文件中保存的是Dll-1.dll中导出的函数和变量的符号名;Dll.exp文件是一个输出库文件,这里,该文件并不重要。
    然后,再次利用Dumpbin命令查看Dll-1.dll导出函数的信息,结果如下图所示。
    在这里插入图片描述
    可以看到,这时多了一些输出信息,其中有这么一段信息:
    在这里插入图片描述
    这就是导出函数的信息。具体含义参考 《孙鑫. VC++深入详解.修订版[M]. 电子工业出版社, 2012. 第19章》

    Step#2: 测试Dll-1.dll
    新建一个空项目:文件->新建->项目
    名称:Dll-1-test
    在这里插入图片描述
    平台选择Debug x64
    新建一个源文件Dll-1-test.cpp,在其中输入如下代码:
    #include <stdlib.h>
    #include
    using namespace std;
    extern int add(int a, int b); //利用extern声明外部函数;
    extern int subtract(int a, int b); //利用extern声明外部函数;
    void main() {
    int x = 3;
    int y = 6;
    int m = add(x, y);
    int n = subtract(x, y);
    cout << “m:” << m << endl;
    cout << “n:” << n << endl;
    system(“PAUSE”);
    }
    生成→生成解决方案
    在这里插入图片描述
    失败1个,这时会出现三个错误:
    在这里插入图片描述
    在这里插入图片描述
    可以看到,Dll-1-test程序编译成功通过,产生的三个错误是在程序链接时发生的。因为这里调用的add和subtract函数都已经作了声明,所以编译可以通过。当链接时,链接器需要知道这两个函数到底是在哪个地方实现的,它要找到这两个函数的实现。正因为没有找到该信息,所以链接时就出错了。
    为了解决这个问题,就需要利用动态链接库的引入库文件了。在Dll-1.dll文件所在目录下,复制Dll-1.lib文件到Dll-1-test程序所在目录下,这个文件中就包含了Dll-1.dll中导出函数的符号名。然后在Dll-1程序中进行链接器的属性配置。在Debug|64文件夹下,双击Microsoft.Cpp.x64.user,在链接器→常规→附加库目录中输入Dll-1.lib的路径:E:\1-C++\Dll-1-test\Dll-1-test,在链接器→输入→附加依赖项中输入Dll-1.lib
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    重新生成解决方案:
    在这里插入图片描述
    这时会成功生成Dll-1.exe文件。也就是说,当应用程序需要调用某个动态链接库提供的函数时,在程序链接时只需要包含该动态链接库提供的输入库文件就可以了。
    在这里插入图片描述
    此时也可以利用Dumpbin命令查看可执行程序的输入信息,参考文献:https://blog.csdn.net/tpz789/article/details/89635968

    回到E:\1-C++\Dll-1-test\x64\Debug,可以看到生成的Dll-1.exe文件,
    在这里插入图片描述
    双击运行。
    在这里插入图片描述
    弹出错误信息,提示在指定的路径下无法找到动态链接库Dll-1.dll文件。
    动态链接库的搜索顺序为:
    ①程序的执行目录:
    本例子的执行目录为:E:\1-C++\Dll-1-test\x64\Debug
    ②当前目录
    例如将Dll-1-test.exe拷到一个新创建的文件夹中,这个新文件夹就是当前目录。
    ③系统目录
    依次是:C:\Windows\System32、C:\Windows\SysWOW64
    ④path环境变量中所列出的路径

    本例中将Dll-1.dll文件复制到执行目录,双击Dll-1-test.exe,执行成功。
    在这里插入图片描述
    Step#3: 利用_declspec(dllimport)声明外部函数
    除了使用extern关键字表明函数时外部定义的之外,还可以使用标识符:_declspec(dllimport)来表明函数是从动态链接库中引入的。在Dll-1-test程序中,将extern的两个函数注释起来,然后在其后添加如下代码:

    _declspec(dllimport) int add(int a, int b);//表明函数是从动态链接库中引入的
    _declspec(dllimport) int subtract(int a, int b);//表明函数是从动态链接库中引入的

    重新Build,最后发现程序运行时一样的。
    如果调用的函数来自于动态链接库,则与使用extern关键字相比,使用_declspec(dllimport)标识符声明外部函数时,编译器可以生产运行效率更高的代码。

    Step#4: 完善Dll-1,利用头文件
    对上述Dll-1-test的例子来说,因为该程序使用的动态链接库Dll-1.dll是我们自己编写的,所以我们清楚该Dll-1.dll中的导出函数。如果DLL程序的实现者和使用者不是同一个人,那么后者只能通过一些工具来查看该DLL提供的导出函数,并猜测这些函数的原型。这种做法对DLL的调用不是很方便。通常在编写动态链接库时,都会提供一个头文件,在此文件中提供DLL导出函数原型的声明,以及函数的有关注释文档。
    接下来,我们就为Dll-1工程添加一个头文件:Dll-1.h,并在其中添加如下的代码:
    _declspec(dllimport) int add(int a, int b);
    _declspec(dllimport) int subtract(int a, int b)
    下面,对Dll-1.h进行改造,使其不仅能够为调用动态链接库的客户端程序服务,同时也能够由动态链接库程序本身来使用。改造后的Dll-1.h文件内容如下所示:

    #ifdef Dll_1_API
    #else
    #define Dll_1_API _declspec(dllimport)
    #endif

    Dll_1_API int add(int a, int b);
    Dll_1_API int subtract(int a, int b);

    在该文件中,首先使用条件编译指令判断是否定义了DLL-1-API符号,如果已经定义了该符号,则不作任何处理;否则定义该符号,将其定义为:_declspec(dllimport)。然后使用所定义的DLL-1-API宏来代替add函数和subtract函数声明前面的_declspec(dllimport)标识符。

    接下来,在动态链接库的源程序:Dll-1.cpp文件中,首先利用#define指令定义DLL-1-API宏,然后利用#include指令包含Dll-1.h头文件。之后,在定义add和subtract函数时不再需要指定declspec(dllexport)标识符了,所以将其删除,这时的Dll-1.h.cpp文件内容如下所示:

    #include “stdafx.h”
    #define Dll_1_API _declspec(dllexport)
    #include “Dll-1.h”
    int add(int a, int b)
    {
    return a + b;
    }
    int subtract(int a, int b)
    {
    return a - b;
    }

    在程序编译时,头文件不参与编译,源文件单独编译。因此,在编译Dll-1.cpp文件时,首先定义DLL-1-API宏,将其定义为:_declspec(dllexport)。然后包含Dll-1.h这一头文件,这将展开该头文件。展开之后,首先判断DLL-1-API是否已经定义了。因为这时已经定义了这个宏,所以不再定义该宏,直接编译其后的add和subtract函数的声明。因为在声明这两个函数时,都使用了DLL-1-API宏,而且这时该宏的定义是:_declspec(dllexport),表明这两个函数是动态链接库的导出函数。
    之后,将这个DLL交由其他程序使用时,只要后者没有定义DLL-1-API宏,那么该宏的定义就是:_declspec(dllimport),即add函数和subtract函数是导入函数。通过上述方法,Dll-1.h这个头文件既可以由实现DLL的程序使用,也可以由调用该DLL的客户端程序使用。

    重新生成Dll-1.lib和Dll-1.dll文件,将Dll-1.dll复制到E:\1-C++\Dll-1-test\x64\Debug文件夹下,将Dll-1.lib和Dll-1.h复制到工程根目录下E:\1-C++\Dll-1-test\Dll-1-test,另外再将通用属性配置一下,即附加库目录和附加依赖项。
    修改Dll-1-test.cpp,代码如下:

    #include <stdlib.h>
    #include
    #include “Dll-1.h”
    using namespace std;

    void main() {
    int x = 3;
    int y = 6;
    int m = add(x, y);
    int n = subtract(x, y);
    cout << “m:” << m << endl;
    cout << “n:” << n << endl;
    system(“PAUSE”);
    }

    运行成功,结果也是正确的。

    Step#5: 从DLL中导出C++类
    在一个动态链接库中还可以导出一个C++类。为了实现这样的功能,仍以Dll-1为例,首先打开Dll-1工程,然后在Dll-1.h文件中添加如下代码:
    #ifdef Dll_1_API
    #else
    #define Dll_1_API _declspec(dllimport)
    #endif

    Dll_1_API int add(int a, int b);
    Dll_1_API int subtract(int a, int b);

    class Dll_1_API Point {
    public:
    void output(int x, int y);
    };

    在Dll-1.cpp中添加如下代码:
    #include “stdafx.h”
    #include
    #define Dll_1_API _declspec(dllexport)
    #include “Dll-1.h”
    int add(int a, int b)
    {
    return a + b;
    }
    int subtract(int a, int b)
    {
    return a - b;
    }

    void Point::output(int x, int y) {
    std::cout << x << y << std::endl;
    }

    重新生成解决方案,成功。然后将Dll-1.dll复制到E:\1-C++\Dll-1-test\x64\Debug中,
    将Dll-1.lib、Dll-1.h复制到E:\1-C++\Dll-1-test\Dll-1-test中,然后配置属性,再将Dll-1.h添加到Dll-1-test项目中(也可以不添加)。

    修改Dll-1-test.cpp的代码为:

    #include <stdlib.h>
    #include
    #include “Dll-1.h”
    using namespace std;

    void main() {
    int x = 3;
    int y = 6;
    int m = add(x, y);
    int n = subtract(x, y);
    cout << “m:” << m << endl;
    cout << “n:” << n << endl;
    Point pt;
    pt.output(5, 3);
    system(“PAUSE”);
    }

    运行成功。
    在这里插入图片描述

    此外,也可以从类中导出部分函数,例如:在Dll-1.h中将Point类再添加一个成员函数:test
    class Point {
    public:
    void Dll_1_API output(int x, int y);
    void test();
    };
    在Dll-1.cpp中添加test函数的实现,该函数不作任何处理。
    void Point::test()
    {
    }
    然后生成Dll-1.dll,利用Dumpbin命令的exports选项查看最新的Dll-1.dll的导出信息。
    在这里插入图片描述
    可以看到,对Point类来说,Dll-1.dll仅导出了它的Output成员函数。
    重新编译运行,最后的结果是一样的。

    展开全文
  • 动态链接库DLL

    2013-10-04 17:20:40
    动态链接库是一个包含可由多个程序同时使用的代码和数据的库,使进程可以调用不是属于其可执行代码的函数,多个进程调用同一个DLL只需向内存加载一份该DLL。静态链接库lib 指令都直接包含在最终生成的exe文件中,而...
  • codeblocks 下 添加动态链接库

    千次阅读 2014-11-06 19:22:40
    codeblocks 下 添加动态链接库
  • Linux下生成动态链接库

    千次阅读 2018-04-18 17:32:53
    obj 文件之后,将几个链接文件和动态链接库中的文件链接起来,在链接器中将几个目标文件组合在一起然后生成可执行文件 exe 文件,而生成动态链接库的这一步骤是由编译器自己完成的,它可以调用自己已经处理好的...
  • VC动态链接库基础

    2014-08-18 20:07:19
    在实际编程时,可以把完成某种功能的函数放在一个动态链接库中,然后提供给其他函数调用。 Windows API中所有的函数都包含在DLL中,其中有3个最重要的DLL: Kernel32.dll:包含管理内存,进程,线程的函数; User...
  • 本文意在讲解静态链接库与动态链接库的创建与使用,在此之前先来对二者的概念、区别及优缺点进行简要的阐述。其中大多内容参考相关网络资料,由于本人能力有限,不能确保完全准确无误,若有偏差之处请不吝指出。文中...
  • Qt生成调用动态链接库dll

    千次阅读 2019-01-12 22:36:41
    这个文件就是链接库,又可以分为静态链接库和动态链接库。 1. 静态链接库 链接程序从库中寻找需要的符号(函数和变量的名字),查找到就将其放入可执行文件,未查找到就报错。 使用静态库链接的程序: (1)可...
  • (2)隐式加载动态链接库,需要的文件:*.h(非必须文件,如果不使用*.h文件,那么调用程序所需要的函数或是类就需要自己声明,所以说建议直接使用动态链接库的*.h文件。如果使用别人的动态链接库没有*.h文件那只有...
  • 一、Linux下生成动态链接库 //func.c # include <stdio.h> int add(int a , int b) { return a+b; } void func() { printf("hello world......\n"); } 命令行:gcc -shared -fpic -lm -ldl -o libfunc.so...
  • 静态是在链接阶段被链接的(好像是废话,但事实就是这样),所以生成的可执行文件就不受的影响了,即使删除了,程序依然可以成功运行。 有别于静态动态库链接是在程序执行的时候被链接的。所以,即使...
  • 但是动态链接库,对头文件和库都要进行相应的处理。 创建一个项目,有几种方案,其中,在应用程序类型中选择dll,在附加项中选择导出符号,选择导出符号这样做比较方便,很多东西都帮你做好了。 生成一个dll.h dll....
  • 动态链接库有两种加载方式:隐式链接加载动态链接库和动态加载动态链接库。  1、隐式链接加载动态链接库   如果有XXX.h ,XXX.lib ,XXX.dll,这样就可以隐式链接加载了。  把这三个文件复制到工程目录下面,当发布...
  • 动态链接库的建立与调用

    千次阅读 2020-05-25 10:33:03
    动态链接库(Dynamic Link Library DLL)是一个可执行模块,它包含的函数可以由Windows应用程序调用以提供所需功能,为应用程序提供服务。 1.动态链接库基础知识 大型的应用程序都是由多个模块组成的,这些模块彼此...
  • Linux下动态链接库的创建和使用

    千次阅读 2016-04-06 13:40:41
    Linux链接库的创建和使用 链接库 动态链接库的创建和使用 静态链接库的创建和使用
  • 一、动态链接库预加载型rootkit概述 二、动态链接库预加载型rootkit所用技术 2.1linux动态链接库预加载机制 2.2全局符号介入 2.3 rootkit利用的技术点 三、动态链接库预加载型rootkit 3.1利用LD_PRELOAD加载...
  • Linux下的静态链接库和动态链接库编程 参考: Linux下的静态链接库和动态链接库编程 - Histring - 博客园 Mac下静态库和动态库的创建和使用_C/C++_vincent2610的专栏-CSDN博客 一. 链接库概述 Linux下的库有动态...
  • 下面介绍一下用VS如何创建静态链接库和动态链接库,并测试创建的库。 1.静态链接库 打开VS2010,新建一个项目,选择win32项目,点击确定,选择静态库这个选项,预编译头文件可选可不选。 在这个空项目中,...
  • DLL 动态链接库

    2010-04-30 14:30:00
    一、动态链接库的概念动态链接库(Dynamic Link Library,缩写为DLL)是一个可以被其它应用程序共享的程序模块,其中封装了一些可以被共享的例程和资源。动态链接库文件的扩展名一般是dll, 也有可能是drv、sys和fon...
  • 静态、动态链接库编译及Makefile编写 重要的几个概念 利用gcc生成可执行文件,可以有三种“模式”: 多文件编译静态库链接动态库链接 其中多文件编译和静态库链接两种方式有些相似 静态库文件通常以.a结尾,静态...
  • 显然一个是静态链接库(.a),一个是动态链接库(.so) 联系和区别 相同点:链接库本身不是最终的执行程序文件,而是为其他执行文件提供服务的程序。如果把最终的执行程序文件比作一个汽车生产厂家,那么链接库就可以...
  • 无法定位程序输入点于动态链接库

    万次阅读 2018-06-19 15:22:12
    最近在项目研发调用动态链接库.dll文件时发现一个问题“无法定位程序输入点于...网上百度未果,经调试发现调用新动态链接库.dll文件未将DEBUG文件夹下旧动态链接库.dll文件删除删除动态链接库后运行成功。 ...
  • 1、链接库概述 2、静态链接库 2.1、编辑测试文件 2.2、将.c 编译生成 .o文件 2.3、由 .o 文件创建.a静态库 2.4、在程序中使用静态库 2.5、生成目标程序main,然后运行 3、动态库(隐式链接) 3.1、由 .o创建...
  •  动态链接库(Dynamic Link Library,缩写为DLL)是一个可以被其它应用程序共享的程序模块,其中封装了一些可以被共享的例程和资源。  动态链接库文件的扩展名一般是dll,也有可能是drv、sys和fon,它和可执行...
  • vc加载动态链接库

    千次阅读 2014-06-26 11:22:04
    在仓库的发展史上经历了“无库-静态链接库-动态链接库”的时代。  静态链接库与动态链接库都是共享代码的方式,如果采用静态链接库,则无论你愿不愿意,lib中的指令都被直接包含在最终生成的EXE文件中了。但是若...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 155,066
精华内容 62,026
关键字:

动态链接库可以删除吗