动态库_动态库编译 - CSDN
精华内容
参与话题
  • 动态链接在企业级开发中无处不在,本课程包含Windows动态链接相关知识点,通过浅显易懂的代码与讲解,让你熟悉掌握动态链接技术!
  • 什么是动态库

    千次阅读 2014-02-20 00:28:31
    一直以来很困惑如何在linux中编译库文件和使用库文件,今天看了一篇文章才终于搞...转:如何用gcc编译生成动态链接库*.so文件 动态库 问:我源文件为main.c, x.c, y.c, z.c,头文件为x.h,y.h,z.h 如何编译成.s

    转自:http://gui323.blog.51cto.com/3168443/798329


    一直以来很困惑如何在linux中编译库文件和使用库文件,今天看了一篇文章才终于搞明白。以下为转载文章,仅供学习,如有侵权请告知。

    转:如何用gcc编译生成动态链接库*.so文件 动态库
    问:我源文件为main.c, x.c, y.c, z.c,头文件为x.h,y.h,z.h
    如何编译成.so动态库?
    编译器用gcc
    最好能给出详细参数解释,谢谢


    答:
    # 声称动代连接库,假设名称为libtest.so
    gcc x.c y.c z.c -fPIC -shared -o libtest.so


    # 将main.c和动态连接库进行连接生成可执行文件
    gcc main.c -L. -ltest -o main


    # 输出LD_LIBRARY_PATH环境变量,一边动态库装载器能够找到需要的动态库
    export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.


    # 测试是否动态连接,如果列出libtest.so,那么应该是连接正常了
    ldd main


    # 执行就不用说了吧 


    -fPIC:表示编译为位置独立的代码,不用此选项的话编译后的代码是位置相关的所以动态载入时是通过代码拷贝的方式来满足不同进程的需要,而不能达到真正代码段共享的目的。


    -L.:表示要连接的库在当前目录中


    -ltest:编译器查找动态连接库时有隐含的命名规则,即在给出的名字前面加上lib,后面加上.so来确定库的名称


    LD_LIBRARY_PATH:这个环境变量指示动态连接器可以装载动态库的路径。
    当然如果有root权限的话,可以修改/etc/ld.so.conf文件,然后调用
    /sbin/ldconfig来达到同样的目的,不过如果没有root权限,那么只能采用输出LD_LIBRARY_PATH的方法了。


    举例:
    动态链接库*.so的编译与使用- -
                                          


    动态库*.so在linux下用c和c++编程时经常会碰到,最近在网站找了几篇文章介绍动态库的编译和链接,总算搞懂了这个之前一直不太了解得东东,这里做个笔记,也为其它正为动态库链接库而苦恼的兄弟们提供一点帮助。
    1、动态库的编译


    下面通过一个例子来介绍如何生成一个动态库。这里有一个头文件:so_test.h,三个.c文件:test_a.c、test_b.c、test_c.c,我们将这几个文件编译成一个动态库:libtest.so。


    so_test.h:


    #include <stdio.h>
    #include <stdlib.h>
    void test_a();
    void test_b();
    void test_c();
    //---------------------------------------
    test_a.c:
    #include "so_test.h"
    void test_a()
    {
        printf("this is in test_a...\n");
    }
    //--------------------------------------
    test_b.c:
    #include "so_test.h"
    void test_b()
    {
        printf("this is in test_b...\n");
    }
    //------------------------------------------
    test_c.c:
    #include "so_test.h"
    void test_c()
    {
        printf("this is in test_c...\n");
    }


    将这几个文件编译成一个动态库:libtest.so
    $ gcc test_a.c test_b.c test_c.c -fPIC -shared -o libtest.so
    2、动态库的链接


    在1、中,我们已经成功生成了一个自己的动态链接库libtest.so,下面我们通过一个程序来调用这个库里的函数。程序的源文件为:test.c。


    test.c:
    #include "so_test.h"
    int main()
    {
        test_a();
        test_b();
        test_c();
        return 0;
    }
    l         将test.c与动态库libtest.so链接生成执行文件test:
    $ gcc test.c -L. -ltest -o test


    l         测试是否动态连接,如果列出libtest.so,那么应该是连接正常了
    $ ldd test
             执行test,可以看到它是如何调用动态库中的函数的。
    3、编译参数解析
    最主要的是GCC命令行的一个选项:
              -shared 该选项指定生成动态连接库(让连接器生成T类型的导出符号表,有时候也生成弱连接W类型的导出符号),不用该标志外部程序无法连接。相当于一个可执行文件


             -fPIC:表示编译为位置独立的代码,不用此选项的话编译后的代码是位置相关的所以动态载入时是通过代码拷贝的方式来满足不同进程的需要,而不能达到真正代码段共享的目的。


             -L.:表示要连接的库在当前目录中


             -ltest:编译器查找动态连接库时有隐含的命名规则,即在给出的名字前面加上lib,后面加上.so来确定库的名称


             LD_LIBRARY_PATH:这个环境变量指示动态连接器可以装载动态库的路径。


             当然如果有root权限的话,可以修改/etc/ld.so.conf文件,然后调用 /sbin/ldconfig来达到同样的目的,不过如果没有root权限,那么只能采用输出LD_LIBRARY_PATH的方法了。
    4、注意


           调用动态库的时候有几个问题会经常碰到,有时,明明已经将库的头文件所在目录 通过 “-I” include进来了,库所在文件通过 “-L”参数引导,并指定了“-l”的库名,但通过ldd命令察看时,就是死活找不到你指定链接的so文件,这时你要作的就是通过修改 LD_LIBRARY_PATH或者/etc/ld.so.conf文件来指定动态库的目录。通常这样做就可以解决库无法链接的问题了。
    展开全文
  • C++动态库的制作和调用

    万次阅读 2018-08-31 20:15:30
    1、dll的有点 代码复用是提高软件开发效率的重要途径。一般而言,只要某部分代码具有通用性,就可将它构造成相对独立的功能模块并在之后的项目中重复使用。比较常见的例子是各种应用程序框架,ATL、MFC等,它们都以...

    1、dll的有点

    代码复用是提高软件开发效率的重要途径。一般而言,只要某部分代码具有通用性,就可将它构造成相对独立的功能模块并在之后的项目中重复使用。比较常见的例子是各种应用程序框架,ATL、MFC等,它们都以源代码的形式发布。由于这种复用是“源码级别”的,源代码完全暴露给了程序员,因而称之为“白盒复用”。“白盒复用”的缺点比较多,总结起来有4点。 
    暴露了源代码;多份拷贝,造成存储浪费; 
    容易与程序员的“普通”代码发生命名冲突; 
    更新功能模块比较困难,不利于问题的模块化实现; 
    实际上,以上4点概括起来就是“暴露的源代码”造成“代码严重耦合”。为了弥补这些不足,就提出了“二进制级别”的代码复用。使用二进制级别的代码复用一定程度上隐藏了源代码,对于缓解代码耦合现象起到了一定的作用。这样的复用被称为“黑盒复用”。 

    说明:实现“黑盒复用”的途径不只dll一种,静态链接库甚至更高级的COM组件都是。

    2、ddl的创建

    2.1、创建及注意事项

    文件------>新建------>项目------>Win32控制台应用程序/Win32项目------>单击下一步------>应用程序类型选择DLL(图1)------>单击完成。

    创建出来原始项目结构:

    在附加选项中,选择空项目,生成的项目结构

    注意:解决方案配置问题,win32平台生成的dll文件,只能被win32平台运行的项目调用:x64平台生成的dll文件,只能被x64平台运行的项目调用。

    2.2、动态库制作方法

    extern "C" _declspec(dllexport)与project2.h中的#ifdef.......endif是将C++函数导出,才会生成lib文件

    2.2.1、方法一

    通过定义C的接口函数对类方法进行封装,及定义全局变量,源码如下(此方法定义的类,还可以进行多项目联合编程):

    Project1.h

    #include "stdafx.h"
    #include<iostream>
    #include<string>
    using namespace std;
    class project1
    {
    public:
    	project1();
    	~project1();
    	void project1_name();
    	void project1_budget(int money);
    	bool project1_run();
    	int project1_numPeople();
    	string project_name;
    };

    Project1.cpp

    #include "stdafx.h"
    #include"Project1.h"
    project1::project1(){}
    project1::~project1(){}
    project1 theApp;//定义一个全局变量,方便被封装函数调用类的方法
    void project1::project1_name()
    {
    	cout << "项目名称为:"<<endl;
    	cout << project_name << endl;
    }
    void project1::project1_budget(int money)
    {
    	cout << money << endl;
    }
    bool project1::project1_run()
    {
    	return true;
    }
    int project1::project1_numPeople()
    {
    	return 10;
    }
    extern "C" _declspec(dllexport) void name()
    {
    	theApp.project1_name();
    }
    extern "C" _declspec(dllexport) void budget(int money)
    {
    	theApp.project1_budget(money);
    }
    extern "C" _declspec(dllexport) bool run()
    {
    	return theApp.project1_run();
    }
    extern "C" _declspec(dllexport) int numPeople()//对numPeople进行封装,需要使用关键字extern "C" _declspec(dllexport),运用关键字后,才会生产lib文件
    {
    	return theApp.project1_numPeople();
    }

    2.2.2、方法二

    将类的成员函数直接封装成C接口,源码如下

    project2.h

    #ifdef TESTDLL_EXPORTS  
    #define TESTDLL_API __declspec(dllexport)   
    #else  
    #define TESTDLL_API __declspec(dllimport)   
    #endif 
    #include<iostream>
    #include<string>
    
    using namespace std;
    class project2
    {
    public:
    
    	project2();
    	~project2();
    	TESTDLL_API void project2_name();
    	TESTDLL_API void project2_budget(int money);//库制作不会报异常,但是传入参数会无效
    //应修改成 static TESTDLL_API void project2_budget(int money);
    	TESTDLL_API bool project2_run();
    	TESTDLL_API int project2_numPeople();
    	string project_name;
    };

    project2.cpp

    project2::project2(){}
    project2::~project2(){}
    void project2::project2_name()
    {
        project_name=“项目2”;//1
    	cout << "项目名称为:" << endl;
    	cout << project_name << endl;//2
        //调用时,应该把1和2注销,原因未理解
    }
    void project2::project2_budget(int money)
    {
    	cout << money << endl;
    }
    bool project2::project2_run()
    {
    	return true;
    }
    int project2::project2_numPeople()
    {
    	return 20;
    }

    2.3、查看动态库生成的接口

    运用的工具:单击Windows图标------>所有程序------>找到相应的Visual Studio文件夹------->选择Visual Studio tool(会打开文件夹)-------->寻找本机工具命令提示。切换到dll文件目录下,运行命令:dumpbin /EXPORTS 库名(例:dumpbin /EXPORTS Project2.dll)

    方法一生成的动态库结构图:

     方法二生成的动态库结构图:

    3、动态库的链接

    3.1、显示链接

    获取dll库的路径,无需配置环境,代码如下:

    #include<iostream>
    #include<Windows.h>
    using namespace std;
    void Display_Call_Project1_DLL()
    {
    	typedef void(*name)();
    	typedef void(*budget)(int money);
    	typedef bool(*run)();
    	typedef int(*numPeople)();
    	HMODULE hDLL = LoadLibrary("..\\..\\Make_Dll\\x64\\Debug\\Project1.dll");//dll的文件路径
    	if (hDLL == NULL)
    	{
    		cout << "动态库未找到" << endl;
    		return;
    	}
    	name n = name(GetProcAddress(hDLL, "name"));//运用函数名
    	n();
    	budget b = budget(GetProcAddress(hDLL, MAKEINTRESOURCE(1)));//运用序号调用,调用的为budget函数
    	b(2000);
    	
    	run r = run(GetProcAddress(hDLL, "run"));
    	cout << r() << endl;
    
    	numPeople np = numPeople(GetProcAddress(hDLL, "numPeople"));
    	cout << np() << endl;
    	FreeLibrary(hDLL);
    }
    //调用方法二生成的动态库
    void Display_Call_Project2_DLL()
    {
    	typedef void(*name)();
    	typedef void(*budget)(int money);
    	typedef bool(*run)();
    	typedef int(*numPeople)();
    	HMODULE hDLL = LoadLibrary("..\\..\\Make_Dll2\\x64\\Debug\\Project2.dll");//dll的文件路径
    	if (hDLL == NULL)
    	{
    		cout << "动态库未找到" << endl;
    		return;
    	}
    	name n = name(GetProcAddress(hDLL, "?project2_name@project2@@QEAAXXZ"));//运用函数名
    	n();
    	budget b = budget(GetProcAddress(hDLL, MAKEINTRESOURCE(1)));//运用序号调用,调用的为budget函数
    	b(1000);
    
    	run r = run(GetProcAddress(hDLL, MAKEINTRESOURCE(4)));
    	cout << r() << endl;
    
    	numPeople np = numPeople(GetProcAddress(hDLL, MAKEINTRESOURCE(3)));
    	cout << np() << endl;
    	FreeLibrary(hDLL);
    }
    int main()
    {
    	Display_Call_Project2_DLL();
    	system("pause");
    	return 0;
    }

    注意:运用方法二,生成的动态库,成员函数必须设置静态成员函数,并且不能调用类的成员。否则入传参无效,并且调用类成员会报错。

    3.2、隐式链接

    必须配置环境:

    项目->属性->配置属性->VC++ 目录-> 在“包含目录”里添加头文件project2.h所在的目录

    项目->属性->配置属性->VC++ 目录-> 在“库目录”里添加头文件project2.lib所在的目录

    项目->属性->配置属性->链接器->输入-> 在“附加依赖项”里添加“project2.lib”(若有多个 lib 则以空格隔开)

    方法一生成的动态库,无法进行隐式链接。

    隐式链接动态库的制作方法,必须在类函数中加上宏定义,源码如下:

    project2.h

    #ifdef TESTDLL_EXPORTS
    
    #define TESTDLL_API __declspec(dllexport)   
    #else  
    #define TESTDLL_API __declspec(dllimport)   
    #endif 
    #include<iostream>
    #include<string>
    
    using namespace std;
    class project2
    {
    public:
    
    	TESTDLL_API project2();
    	TESTDLL_API ~project2();
    	TESTDLL_API void project2_name();
    	TESTDLL_API void project2_budget(int money);
    	TESTDLL_API bool project2_run();
    	TESTDLL_API int project2_numPeople();
    	string project_name;
    };

    project2.cpp

    #define TESTDLL_EXPORTS//不进行宏定义,或提示链接不一致,导致隐式调用失败
    #include"Project2.h"
    project2::project2(){}
    project2::~project2(){}
    void project2::project2_name()
    {
    	//project_name = "项目2";
    	cout << "项目名称为:";
    	//cout << project_name << endl;
    }
    void project2::project2_budget(int money)
    {
    	cout << money << endl;
    }
    bool project2::project2_run()
    {
    	return true;
    }
    int project2::project2_numPeople()
    {
    	return 20;
    }

     调用函数:

    main.cpp

    #include<iostream>
    #include<Windows.h>
    #include"Project1.h"
    #include"project2.h"
    using namespace std;
    void Call_Project2_DLL()
    {
    	project2 p;
    	p.project2_run();
    	p.project2_budget(1000);
    	
    }
    int main()
    {
    
    	Call_Project2_DLL();
    	system("pause");
    	return 0;
    }

    注:提示找不到dll库时,将dll库放在main.cpp同级目录下

    展开全文
  • 动态库

    2020-02-26 16:54:48
    总结一:动态库 前言  我们知道程序编译链接经常使用动态,同时我们可能还知道,动态库时程序运行时加载的。但是动态库到底有什么作用,如何生成、如何加载等,我们却很少关注。接下来,我给大家做一个简单的介绍。...

    总结一:动态库

    前言

    我们知道程序编译链接经常使用动态,同时我们可能还知道,动态库时程序运行时加载的。但是动态库到底有什么作用,如何生成、如何加载等,我们却很少关注。接下来,我给大家做一个简单的介绍。

    1.1 动态库和静态库的区别

    静态库特点(linux):

    • 命名上是以 *.o 结尾
    • 静态库在链接阶段直接就加入到可执行的文件中了,在执行过程中无需该静态库
    • 相对于动态库生成的文件,使用静态库生成的文件连接生成的可执行文件较大

    动态库的特点(linux)

    • 命名上是以 *.so
    • 目标文件在链接阶段只是指明链接的那个动态库,动态库与目标文件保持独立。在执行过程中需要该动态库
    • 使用动态库生成的目标文件较小

    对于工程中比较共通的源码文件,比如多个进程使用同一个模块的源码,我们最好将其制作成动态库,以节省系统空间。同时如果动态库出现bug,只需要重新生成一个动态库并将以前的替换即可。不需要重新编译其他模块。

    1.2 内存中的动态库

    在讲到动态库的装载时我们需要懂一定的背景知识,首先虚拟内存和物理内存,其次还有地址映射,这些知识就不在本文多加讲解,网上资料很多。我们动态库在整个内存空间是有一份,而每个进程都有自己的虚拟空间,虚拟空间会使用匿名映射(mmap使用MAP_PRIVATE方式进行映射),使自己的进程与动态库进行关联。本进程只会保留访问动态库时的一些数据。好了,打的方向就说这么多,这几句话随便抽出一个词来都够将好久。我们只需要对大方向有认识即可。以下就是映射表

    在这里插入图片描述

    此外我们说一些额外的小知识,在linux系统中 /proc 目录下有很多进程文件。在执行的进程都会创建一个文件。随便进入一个文件 /etc/1892。查看maps文件(sudo cat maps),这里面对应了动态库对应虚拟空间的位置,值得注意的是,这个文件最后一列有多少个[stack]就有多少个线程

    00008000-005ba000 r-xp 00000000 b3:01 11822      /usr/local/bin/ecTelematicsApp
    005c1000-005df000 rw-p 005b1000 b3:01 11822      /usr/local/bin/ecTelematicsApp
    005df000-00d7f000 rw-p 00000000 00:00 0          [heap]
    a8c00000-a8cff000 rw-p 00000000 00:00 0 
    a8cff000-a8d00000 ---p 00000000 00:00 0 
    a8e00000-a8e01000 ---p 00000000 00:00 0 
    a8e01000-a9600000 rw-p 00000000 00:00 0          [stack:1369]
    a9600000-a9700000 rw-p 00000000 00:00 0 
    a9700000-a9721000 rw-p 00000000 00:00 0 
    a9721000-a9800000 ---p 00000000 00:00 0 
    .......
    b5c5d000-b645e000 rw-p 00000000 00:00 0          [stack:1961]
    b645e000-b6470000 r-xp 00000000 b3:01 709        /lib/libresolv-2.19.so
    b6470000-b6477000 ---p 00012000 b3:01 709        /lib/libresolv-2.19.so
    b6477000-b6478000 r--p 00011000 b3:01 709        /lib/libresolv-2.19.so
    b6478000-b6479000 rw-p 00012000 b3:01 709        /lib/libresolv-2.19.so
    b6479000-b647b000 rw-p 00000000 00:00 0 
    .......
    bea5d000-bea7e000 rw-p 00000000 00:00 0          [stack]
    ffff0000-ffff1000 r-xp 00000000 00:00 0          [vectors]
    

    同时我们也可以直接使用readelf -m [bin文件名],来读取该bin文件的链接信息。

    1.3 动态库的加载

    关于动态库我当初的直接认识是,程序运行到调用该动态库的接口时,会产生缺页,从而去磁盘加载动态库到内存,然后再执行。但事实并非如此。动态库也分为隐式链接和显示链接,不同的方式其载入内存的时间也是大相径庭。

    显示链接 隐式链接
    语法 不需要申明动态库先关的头文件,在调用时需要加载动态库的名称 只需要添加相应的头文件即可
    加载 执行到相应代码段时加载动态库(可以控制库的加载和卸载) 由系统控制加载时间,一般在程序启动时就加载

    由以上两点我们可以看出显示链接如果控制得当,对内存的消耗将下降许多,大型项目应该使用显示链接。但是缺点也有,就是如果库不存在,隐式链接可以再一开始就发现库不存在,而显示链接会被偶然触发。

    1.4 动态库的制作

    首先我们准备一个源文件 print.c

    #include<stdio.h>
    
    void printInter()
    {
        printf("%s\n", __FUNCTION__);
    }
    
    void printExtern()
    {
        printInter();
        printf("%s\n", __FUNCTION__);
    }
    

    输入指令

    gcc -fPIC -c print.c -o print.o
    gcc -shared print.o -o libprint.so -lstdc++
    

    由此我们生成了 libprint.so动态库。

    然后我们再创建libcurl.so 的接口头文件print.h

    #ifndef __PRINTT_H_
    #define __PRINT_H__
    
    void printExtern();
    
    #endif
    

    1.5 动态库的隐式链接

    我们创建main.c 去使用库

    #include<stdio.h>
    #include<unistd.h>
    #include "print.h"
    
    int main()
    {
    	printf("waite 5 seconds\n");
    	sleep(5);
    	printf("%s\n", __FUNCTION__);
    	printExtern();
    
    	return 0;
    }
    

    输入指令

    gcc -fPIC -c main.c -o main.o
    gcc -o target main.c -L./ -lprint -I./ -lstdc++
    

    生成target

    执行 ./target, 输出如下

    waite 5 seconds
    main
    printInter
    printExtern
    

    我们可以通过 readelf -d target,可以查看target链接了libprint.so

    root@user:/home/cjj/jianxiongs/so2# readelf -d target 
    
    Dynamic section at offset 0xf0c contains 25 entries:
      标记        类型                         名称/值
     0x00000001 (NEEDED)                     共享库:[libprint.so]
     0x00000001 (NEEDED)                     共享库:[libc.so.6]
     0x0000000c (INIT)                       0x80483fc
     0x0000000d (FINI)                       0x8048604
     0x00000019 (INIT_ARRAY)                 0x8049f00
     0x0000001b (INIT_ARRAYSZ)               4 (bytes)
     0x0000001a (FINI_ARRAY)                 0x8049f04
     0x0000001c (FINI_ARRAYSZ)               4 (bytes)
     0x6ffffef5 (GNU_HASH)                   0x80481ac
     0x00000005 (STRTAB)                     0x80482c8
     0x00000006 (SYMTAB)                     0x80481e8
     0x0000000a (STRSZ)                      208 (bytes)
     0x0000000b (SYMENT)                     16 (bytes)
     0x00000015 (DEBUG)                      0x0
     0x00000003 (PLTGOT)                     0x804a000
     0x00000002 (PLTRELSZ)                   32 (bytes)
     0x00000014 (PLTREL)                     REL
     0x00000017 (JMPREL)                     0x80483dc
     0x00000011 (REL)                        0x80483d4
     0x00000012 (RELSZ)                      8 (bytes)
     0x00000013 (RELENT)                     8 (bytes)
     0x6ffffffe (VERNEED)                    0x80483b4
     0x6fffffff (VERNEEDNUM)                 1
     0x6ffffff0 (VERSYM)                     0x8048398
     0x00000000 (NULL)                       0x0
    

    接下来我们删除 libprint.so。 然后再运行target,这时我们发现出现如下错误

    ./target: error while loading shared libraries: libprint.so: cannot open shared object file: No such file or directory
    

    这说明程序在一开始就要加载libprint.so的库,这就是动态库的隐式链接

    1.6 动态库的显式链接

    这时我们需要更改main.c的内容,修改最终如下

    #include<stdio.h>
    #include<unistd.h>
    #include<dlfcn.h>
    #include<stdlib.h>
    //#include "print.h"  删除引用头文件
    
    void test()
    {
        char *libname = NULL;
        char *err = NULL;
    
        //open the lib
        void *handle = dlopen("./libprint.so", RTLD_NOW);
        if(!handle)
        {
            err = dlerror();
            printf("Load libprint.so failed : %s \n", err);
            exit(1);
        }
        //clear error info
        dlerror();
    
        typedef void (*pf_t)(void);
        pf_t print = (pf_t)dlsym(handle, "printExtern");
    
        err = dlerror();
    
        if(err)
        {
            printf("fail to find function : %s \n", err);
            exit(1);
        }
    
        print(); //使用函数指针的方式引用,不能直接引用
    
        //close the lib
        dlclose(handle);
        if(dlerror())
        {
            printf("close libprint.so failed : %s \n", dlerror());
            exit(1);
        }
    
        printf("%s\n", __FUNCTION__);
    
    }
    
    
    int main()
    {
        printf("waite 5 seconds\n");
        sleep(5);
    
        test();
        return 0;
    }
    

    同时我们gcc 中还需要加上 libdl.so库

    gcc -fPIC -c main.c -o main.o
    gcc -o target main.c -L./ -lprint -I./ -lstdc++ -ldl
    

    此时我们删除 libprint.so。然后再执行target 文件。发现程序运行了一段时间,到使用库时才报错

    Load libprint.so failed : ./libprint.so: cannot open shared object file: No such file or directory 
    

    由此我们可以看出动态库的显示连接才能真正实现使用时才去调用。

    附录 : makefile

    在这里我给出这个小工程的makefile

    CXX = g++
    CC = gcc
    FLAGS = -fPIC
    TARGET = target
    CSOURCE = $(wildcard ./*.c)
    COBJS = $(CSOURCE:.c=.o)
    
    target: $(COBJS) libprint 
    	$(CC) $(FLAGS) -o $(TARGET) main.o -L./ -lprint -I./ -ldl
    
    %.c:%.o
    	$(CC) $(FLAGS) -c $< -o $@
    
    libprint:
    	$(CC) $(FLAGS) -shared -o libprint.so print.o
    
    rm:
    	rm print.o
    
    clean:
    	-rm -rf $(COBJS) $(TARGET) libprint.so
    
    test:
    	@echo $(CSOURCE)
    	@echo $(COBJS)
    
    展开全文
  • 动态库编程详解

    千次阅读 2012-08-03 22:07:42
    一、动态库概念与分类 1、什么是动态库 2、动态库分类 4、动态库解决的问题 二、动态库的创建 1、规则动态库 2、声明导出函数的两种方式 2.1__declspec(dllexport)导出 2.2 .def文件导出 3...

     

    目录

    概述

    一、动态库概念与分类

    1、什么是动态库

    2、动态库分类

    4、动态库解决的问题

    二、动态库的创建

    1、规则动态库

    2、声明导出函数的两种方式

    2.1__declspec(dllexport)导出

    2.2 .def文件导出

    3、导出导入类

    三、隐式、显示调用动态库

    1、动态库隐式调用

    2、动态库显示调用

    3.显示、隐式调用的区别

    四、动态库的测试

     

     

     

    概述

          动态库是继静态库发展起来的一种封装重用技术,在灵活性、扩展性、重用性各方面取得了突破,本文只介绍动态库的基础知识,关于动态库的高级编程,推荐参看Jeffrey ReichterChiristophe Nasarre合著的《Windows核心编程》第5版。

          

    一、动态库概念与分类

    1、什么是动态库

       DLL(Dynamic Linkable Library)动态链接库亦简称动态库,它是一块封装好的代码块,包含着一些方法,一般不包括消息循环,也建议不要去包含这些。可把它看成一个仓库,其提供了可直接使用的变量、函数、类等。打个不太生动的比喻,动态库犹如保卫森严的生产基地,但你可以通过正确入口进入,获得你想要的东西,你不用管也管不着这东西是怎么生产的,拿走从出口出来就行,同时生产基地是共享的,大家都可以通过入口获得相应的东西。

          在“库”的发展史上经历了“无库---静态库---动态库”的时代,无论是动态库还是静态库都能解决代码共享的问题。

    动态库是基于二进制级重用的,所以与语言无关、环境无关(前提是你动态库中没有涉及对环境有依赖的东西,如调用一些第三方DLL)的,再一个得遵循DLL接口规范和调用约定,简而言之,用各种语言编写的标准DLL其他语言都可以调用。所以如果想创建一个通用的DLL,那么得严格遵守DLL规范,包括导出、调用约定、形参几方面的内容。

     

    2、动态库分类 

    通过VC++工具编写的动态库分为两类----规则DLL与非规则动态库,Visual C++支持编写三种DLL,它们分别是Non-MFC DLL(非MFC动态库)、MFC Regular DLLMFC规则DLL)、MFC Extension DLLMFC扩展DLL)。非MFC动态库不采用MFC类库结构,其导出函数为标准的C接口,能被非MFCMFC编写的应用程序所调用;MFC规则DLL包含一个继承自CWinApp的类,但其无消息循环;MFC扩展DLL采用MFC的动态链接版本创建,它只能被用MFC类库所编写的应用程序所调用。

    4、动态库解决的问题

          节省资源:如果采用静态链接库,则无论你愿不愿意,lib中的指令都被直接包含在最终生成的EXE文件中了。但是若使用DLL,该DLL不必被包含在最终EXE文件中,EXE文件执行时可以动态地引用和卸载这个与EXE独立的DLL文件。

          节省内存,假如本地有多个进程用到动态库,那么在内存只是只载入一次的,两个进程共用该DLL在内存中的页面。

    灵活性:我认为灵活性才是动态库最值得称道的地方,发行的动态库,只要原有的接口不改变,那么你可以任意地改动,增加动态库里面的内容,同时以新版本替换旧版本,而不影响依赖于此动态库的程序。举个例子吧,例如你的一个软件已经发行了,如果出了什么问题,只要找出出问题的模块(假如它就是个动态库),并处理好,把新版的动态库给用户替换旧版的就OK了,如果全是静态库呢?那只有把整个工程编译一编,发一个大包给人家,人家也许还要把以前的软件卸载,重新装一次新的版本。

    模块化:这对大项目特别有利,利用动态库可以把项目切割成N个小块加以分工,还有利于错误定位等,岂不快哉。

    语言无关性:只要遵循约定的DLL接口规范和调用方式,用各种语言编写的DLL其他语言都可以相互调用。

    特殊用途:windows提供的一些特性只有通过DLL才用使用,如钩子(hook)就需要通过DLL来实现,关于钩子的编程细节,将在后续的博文中详细介绍。

     

    二、动态库的创建

    1、规则动态库

       VC++6.0为开发工具,在VC++中创建Win32 Dynamic-Link Library工程,下图中选择第一项,当然也可以选择其他项。

    在创建的工程中创建一个头文件MyDll.h与一个CPP文件MyDll.cpp,在头文件与源文件中写入以下代码:

    //MyDll.h

    #ifndef MYDLLEIPORT

    #define MYDLLEIPORT extern "C" __declspec(dllimport)

    #endif

    MYDLLEIPORT int add(int a, int b);

    MYDLLEIPORT int     g_nCount;

     

    定义实体:

    #define MYDLLEIPORT extern "C" __declspec(dllexport)

    #include "MyDll.h"

    int g_nCount = 0;

    int add(int a, int b){

          return (g_nCount += a + b);

    }

    代码写完了,现在的工作是怎么把函数导出的问题了,下面分析如何导出函数,与导出的方式

    2、声明导出函数的两种方式

    DLL中导出函数的声明有两种方式:一种是在函数声明中加上__declspec(dllexport)调用DLL时配合.lib文件通过__declsped(dllimport)指令导入函数即可,另外一种方式是采用模块定义(.def)文件声明,.def文件为链接器提供了有关被链接程序的导出、属性及其他方面的信息。

    通过修饰符__declspec(dllexport)导出的函数,可能会面临函数名改编的问题,而通过.def文件导出的,将不会有此问题,要导出编译时函数名不会被改编的函数,还有一种方法,就是在DLL源文件中输入以下代码:

    #pragma comment(linker, “/export:add=_add@8”)

    不过此方法有个麻烦事,你得事先知道函数改篇后的名字是什么,如函数add改编后是_add@8,还有此方法导出的函数名是两个,”add””_add@8”,个人建议使用.def文件导出最为方便快捷,省去非常多的麻烦。

          这两种导出方式都会生成一个与Dll同名的.lib文件,此文件在隐式调用时得用。.lib文件只是包含一些导出函数的属性说明信息。

    2.1__declspec(dllexport)导出

       在头文件声明函数前加上__declspec(dllexport),如下:

    #ifndef MYDLLEIPORT

    #define MYDLLEIPORT extern "C" __declspec(dllimport)

    #endif

    MYDLLEIPORT int add(int a, int b);//导出函数

    MYDLLEIPORT int g_nCount;//导出变量

    其中extern"C"表示函数是安C语言的方式编译,C++对函数名称编译时会改编如add,C++编译后变成_add@8,改名后,C语言或者其他语言调用的时候会出现问题,会找不到,同时不同厂家的编译器编译时生成的函数名也可能不一样; __declspec(dllexport)是导出修饰符,指明导出此函数,不用过分解释,照用就行;下面是对extern "C"的解释:

    extern "C"包含双重含义,从字面上即可得到:首先,被它修饰的目标是“extern”,表示可被外部文件引擎;其次,被它修饰的目标是“C”的,表示按照C语言方式编译和连接。

    编译规(extern "C")必须指定,才能编译出规范的DLL接口。用这个方法导出接口,如果用VC++编译,还是会有函数名改编的问题,编译后,函数名应该是_add@8,调用DLL时,如果用同一厂家(VC++)编译器,是不是会有问题的,用其他厂家的编译器,那就不能保证了,所以这种导出方法并不是很好,可采用下面介绍的通过.def文件导出的方法。

    2.2 .def文件导出

          此方法导出函数,将避免函数名改编的问题。在工程中添加一.def文件---MyDll.def:

    在新加入的MyDll.def文件中输入以下代码:

    ;MyDll.def :导出DLL函数

    LIBRARY DLLDemo

    EXPORTS

    add @ 1

    g_nCount DATA

    .def文件的规则为:
      (1)LIBRARY语句说明.def文件相应的DLL
      (2)EXPORTS语句后列出要导出函数的名称或者变量。可以在.def文件中的导出函数

    名后加@n,表示要导出函数的序号为n,在进行函数调用时,这个序号将发挥其作用,

    可通过序号来调用相应的函数(通过序号取得函数方法:GetProcAddress(hDll,

    MAKEINTRESOURCE(1))),不过此方法不推荐,因为难以维护之类的原因,现在基本

    上不怎么使用;
      (3).def文件中的注释由每个注释行开始处的分号(;)指定,且注释不能与语句共享一行。

          (4)导出全局变量格式:全局变量DATADATA是关键字,例:g_nCount DATA

    其实也可简单地写成如下格式:

    EXPORTS

    add

    g_nCount DATA

    不需要序号,只给个函数名就OK,记住别把函数名弄错了。

     

    3、导出导入类

       最好不要导出类,类的导出会破坏DLL的通用性,C++写的类,C#或者其他语言不一定支持,在这里只是简单的说一下。

    //文件名:point.hpoint类的声明
    #ifndef   POINT_H
    #define  POINT_H
    #ifdef  DLL_FILE
     class _declspec(dllexport) point //导出类point
    #else
     class _declspec(dllimport) point //导入类point
    #endif
    {
     public:
      float y;
      float x;
      point();
      point(float x_coordinate, float y_coordinate);
    };

    #endif

    此处有宏POINT_HDLL_FILE,第一个宏的作用当然是防止头文件重编译,第二个宏则是用来判断当前是用来导出还是导入的,在此头的实现文件(.cpp)中先对DLL_FILE定义,则导出此文件,而当用户用这个动态库时,因为不知道DLL_FILE,所以自然没定义,则会导入此文件。
    //文件名:point.cpppoint类的实现
    #ifndef DLL_FILE
     #define DLL_FILE
    #endif
    #include "point.h"
    //
    point的缺省构造函数
    point::point()
    {
     x = 0.0;
     y = 0.0;
    }

    类的导出导入看似乎不适合动态调用,因为要包含其头文件,在主调应用程序中生成对象。注意类的导出导入格式如下:

    class _declspec(dllexport) 类名 //导出类point
        class _declspec(dllimport) 类名 //导入类point

    三、隐式、显示调用动态库

    1、动态库隐式调用

       隐式调用也有通过指令与通过IDE设置两种方式。隐式调用需要.lib文件,把.dll.lib放同一目录下,然后按以下方法操作

    Ø #pragma comment指令:

          #include “..\MyDll.h”----可以是绝对路径,也以的相对路径

          #pragma comment(lib, “..\\DllDemo.lib”) ----可以是绝对路径,也可以是相对路径

    这种方法似乎有点麻烦,必须把路径设对了,不然可能就找不到。

    Ø 在编译器(VC)中设置:

    1、依次选择tools->options->directories->Show directories for->Library files然后

    添加DllDemo.lib的路径。

    2、依次选择tools->options->directories->Show directories for->Include files然后选择

    MyDll.h的头文件的路径。

          3、依次选择Proctect->Setting->Link然后在Object/library modules中添加上自定义

    .lib库(DllDemo.lib)。

          4、使用自定义库中的函数的时候#include “MyLib.h”就可以了。

    示例代码:

    #include "..\\MyDll.h"

    #pragma comment(lib, "..\\Debug\\DLLDemo.lib")

    int nResutl = add(1, 2);//直接调用

    2、动态库显示调用

       显示调用,不需要头文件,也不需要.lib库文件,随时载入随时释放,比较灵活。

    查看动态库信息的工具:

    VC++的安装目录下的Depends工具,可以看到动态库的接口信息。

    显式调用:

    显示调用首先得知道Dll的路径,然后通过API(LoadLibrary)装载DLL,获得DLL的句柄,通过API(GetProcAddress)配合LoadLibrary返回的句柄再找到想要调用的函数的地址(返回的是指向对应函数地址的指针),通过函数指针就可以调用函数了。就是这么简单,以下是方法:

    //定义函数指针类型,用以调用动态库中函数,以下是定义函数指针类型的格式:

    //typedef [返回类型] (__stdcall *[指针名])(形参);

    typedef int (* PADD)(int, int); //定义指针类型
    int main(int argc, char *argv[])
    {  HINSTANCE hDll; //DLL
    句柄
      PADD addFun; //函数指针

    HINSTANCE hInstance = LoadLibrary(".\\DllDemo.dll");
      if (hInstance != NULL) {     

          addFun = (PADD)GetProcAddress(hInstance, "add");//获得函数地址

          int nResult = pAdd(1, 6);  //通过函数指针调用函数

          int *pCount = (int *)GetProcAddress(hInstance, "g_nCount");//获得全局变量指针

          int nCount = *pCount;

    FreeLibrary(hInstance );                //记得释放哦
     }
     return 0;
    }

    显示调用与隐式调用却有较大差异,下面我们来逐一分析。
      首先,语句typedef int ( * lpAddFun)(int,int)定义了一个与add函数接受参数类型和返回值均相同的函数指针类型。随后,在main函数中定义了lpAddFun的实例addFun

      其次,在函数main中定义了一个DLL HINSTANCE句柄实例hInstance,通过Win32 Api函数LoadLibrary动态加载了DLL模块并将DLL模块句柄赋给了hDll

      再次,在函数main中通过Win32 Api函数GetProcAddress得到了所加载DLL模块中函数add的地址并赋给了addFun。经由函数指针addFun进行了对DLLadd函数的调用;

      最后,应用工程使用完DLL后,在函数main中通过Win32 Api函数FreeLibrary释放了已经加载的DLL模块。

          :如果是用.def文件定义的导出函数,并为函数给定的序号,则可用以下方法调用函数被调用的方法,GetProcAddress(hDll, MAKEINTRESOURCE(1))

     

    3.显示、隐式调用的区别

       从使用层面上讲,隐式调用方便,载入一次(主进程启动的时候载入),就可以到处使用,而显示调用则麻烦些,得载入释放,还有取得相应函数的地址之类。然而显示调用灵活,节省空间,因为使用时才载入,而不用时释放,同时显示调用不需要.lib库之类。

     

    四、动态库的测试

       动态库写好之后,如何测试呢,在这介绍一种比较简单的方法,在动态库的工程中,按F5将出现以下界面:

    意思是动态库是不能单独运行的,你得给它配一个主进程调用它,所以可以写一个简单的程序按上面调用动态库的方法调用动态库中想要测试的函数,编译通过后把exe文件的路径设置在上图的输入框中,点确定之后,再按F5,这时主进程就启动了,你在主进程中触发调用动态库函数的操作,这时将跳进动态库的工程中,动态库的工程处于调试状态,你可以一步一步跟踪,这样就能很好地测试动态库,并找出bug

    配套源码下载:http://download.csdn.net/detail/mingojiang/4475172

     

    转载请注明出自:http://blog.csdn.net/mingojiang

     

    展开全文
  • C++静态库与动态库的区别?

    万次阅读 多人点赞 2018-08-17 20:29:59
    C++静态库与动态库 这次分享的宗旨是——让大家学会创建与使用静态库、动态库,知道静态库与动态库的区别,知道使用的时候如何选择。这里不深入介绍静态库、动态库的底层格式,内存布局等,有兴趣的同学,推荐一本...
  • C++动态库封装及调用

    万次阅读 多人点赞 2017-05-10 11:40:33
    一直对动态库的封装理解不是很透彻,虽然之前写过一个Demo,不过并没有真正的理解。所以写下来,帮助自己理解下。 1、一个程序从源文件编译生成可执行文件的步骤: 预编译 --> 编译 --> 汇编 --> 链接 (1)预编译...
  • 动态库的使用方法

    千次阅读 2016-04-16 15:50:45
    在编写程序时,动态库是常常用到的工具,在vs等编程环境下只需要,完成工程的本地化配置或是直接将dll配置到环境变量即可(不推荐,毕竟小题大做了),而对于像楼主这样刚刚接触Linux操作系统的菜鸟来说,配置动态...
  • 动态库(共享库)的制作和使用

    千次阅读 2019-03-14 01:10:51
    Linux下的动态库为lib*.so格式的二进制文件(目标文件),对应于Windows下的.dll格式的文件。 (1)命名规则 lib+库名+.so (2)动态库的制作 1)生成与位置无关的代码(.o) 2)将.o文件打包成动态库(共享库)...
  • 通俗理解动态库与静态库区别

    万次阅读 多人点赞 2019-06-21 15:27:46
    引:最近做了算法产品化相关的一些工作,其中涉及到算法库封装的相关工作,封装为动态库。总结动态库和静态库区别和对应使用原则。 区别:静态库和动态库最本质的区别就是:该库是否被编译进目标(程序)内部。 ...
  • 动态库的创建与调用

    千次阅读 2019-04-16 00:43:45
    动态库的创建: 先创建一个目录用来后续操作: 然后在Dynamic目录下编辑两个文件,calculate.h和calculate.c,此两个文件和静态库用到的文件一样: 编译: 命令:gcc 源文件 -fPIC –shared –o 目标文件 ...
  • Linux中的动态库和静态库(.a.la.so.o)

    千次阅读 2018-04-21 23:58:33
    Linux中的动态库和静态库(.a/.la/.so/.o) 原文地址:https://www.cnblogs.com/findumars/p/5421910.html 在windows下,一般可以通过文件的后缀名来识别文件的类型。在Linux下大致上也是可以的。但是要明确的一点是...
  • 一、基本概念1.1什么是库在windows平台和linux平台下都大量存在着库。本质上来说库是一种可执行代码的二进制... 1.2库的种类linux下的库有两种:静态库和共享库(动态库)。二者的不同点在于代码被载入的时刻不同。静
  • 本文的目的是测试各种类型库的编译后的使用效果,包括库又链接其他库的编译方法,使用方法,依赖性等。 太长不看版:请跳至文章最后的总结对比表。...④动态库libbb.so依赖动态库libaa.so的测试; ...
  • 原文转自 :http://www.cnblogs.com/ljtknowns/p/5647793.html 文件目录结构如下 1 dynamiclibapp.c 2 Makefile 3 comm/inc/apue.h 4 comm/errorhandle.c 5 dynamiclib/Makefile ...7 d
  • C++静态库与动态库详解与使用

    千次阅读 2016-09-30 09:07:40
    这次分享的宗旨是——让大家学会创建与使用静态库、动态库,知道静态库与动态库的区别,知道使用的时候如何选择。这里不深入介绍静态库、动态库的底层格式,内存布局等,有兴趣的同学,推荐一本书《程序员的自我修养...
  • VS2010 中编写动态库和调用动态库

    千次阅读 2018-11-12 23:05:42
    ... VS2010 中编写动态库和调用动态库 百度查了一下在VS中编写动态库的方法,亲测有效。 (1)启动VS2010》文件》新建》项目,按下图进行选择填写,选择Win32控制台应...
  • 动态库与静态库优缺点比较

    千次阅读 2017-11-14 11:17:22
    动态库与静态库优缺点比较我们在编写一个 C 语言程序的时候,经常会遇到好多重复或常用的部分,如果每次都重新编写固然是可以的,不过那样会大大降低工作效率,并且影响代码的可读性,更不利于后期的代码维护。...
  • VS下静态库与动态库的生成与使用

    千次阅读 2018-02-10 08:36:55
    静态库和动态库的区别与联系 什么是库 什么是静态库 什么是动态库 总结 VS下静态链接库的生成和使用 方法一 静态库和程序在同一目录下2 方法二 直接调用 MY_ADDlib3 方法三 建立自己的库函数推荐 VS下动态链接库...
  • 动态库链接boost静态库

    千次阅读 2018-08-21 20:42:59
    1. boost全部静态链接 2. c++静态链接 1,2点的改变如下,强制链静态的方法为参数下为-l:libXXXX.a; 对于boost log, 需要将宏-DBOOST_LOG_DYN_LINK去掉 LOCAL_STATICLIBS := boost_log boost_log_setup ...
1 2 3 4 5 ... 20
收藏数 810,683
精华内容 324,273
关键字:

动态库