精华内容
下载资源
问答
  • LINUX下JNI封装 C++动态链接库

    千次阅读 2016-03-14 20:39:09
    这几天用jni封装了一C++文件,供java调用,在此把具体的步骤给理一下,方便自己温故,也供大家参考。 前提:已有一个动态库文件libTest.so和包含函数声明的头文件test.h。 步骤一:先创建一java项目:JProj...

    这几天用jni封装了一个C++库文件,供java调用,在此把具体的步骤给理一下,方便自己温故,也供大家参考。
    前提:已有一个动态库文件libTest.so和包含函数声明的头文件test.h。

    1. 创建java项目:JProj
    在头文件test.h中包含了类类型和接口函数。如头文件test.h:

    #ifndef TEST_H
    #define TEST_H
    
    typedef struct _ST_1
    {
        int *a;
        long b;
        char c;
    }ST1;
    
    typedef struct _ST_2
    {
        int a[];
        ST1 b; 
    }ST2;
    
    typedef struct _ST_3
    {
        ST1 a[];
        ST2 b;
        int *c;
        string d;
    }ST3;
    void *func1(int a,ST1 b);
    void func2(void *a,ST2 &b);
    int func3(int *a,ST3 *b);
    #endif // TEST_H

    为了能在java程序中调用相关的接口,我们也需要在java中创建相应的类类型和接口函数(可在一个文件或不同文件,在此为一个文件)。
    在java项目中,添加一个包:com,在包下创建两个类:JDefine.java,JTest.java,其中JDefine.java用来类类型和接口函数。JDefine.java应写为:

    public class JDefine
    {
        class clazz1
        {
            int[] a;     //对应于C++中的int *a;
            long b;
            byte c;     //对应于C++中的char c;
        };
    
        class clazz2
        {
            int[] a;
            clazz1 b;        
        };
        class clazz3
        {
            clazz1[] a;
            clazz2 b;
            int[] c;
            string d;
        };
    
        public native long func1(int a,clazz1 b);
        public native void func2(long a,clazz2 b);
        public native int func3(int[] a,clazz3 b);
    };
    • 注意一:java程序想要通过jni调用C++的库,必须在函数声明时使用关键字native,意为此函数是本地函数,即C++库里的函数。
    • 注意二:java没有指针概念,所以可以用一个数组名来代表相同类型的指针,如定义int [] a和int *a,a代表的都是指针。但如果C++中指针为void *handle,则在java可写为long handle,直接用long型的handle表示地址数值。
    • 注意三:java没有struct,所以在C++中的结构可以用java中的class来对应。
    • 注意四:char 类型在C++中为1个字节,但在java中为2个字节,所以C++的char类型的成员映射到java中,应转变为byte。

    2. 编译
    写好JDefine.java好后,对其进行编译:
    1、进入JDefine.java当前目录,命令行:

    javac JDefine.java

    2、进入com的上一级目录,一般为src,在此目录下进行命令行:

    javah -classpath . -d ./com com.JDefine

    可在com目录下得到com_JDefine.h文件,里面包含了用jni格式写的与JDefine.java相对应的函数声明和类对象,如函数名long func1(int a,clazz1 b);变为

    JNIEXPORT jlong JNICALL Java_com_JDefine_func1(JEnv *, jobject , jint ,jobject );

    3. JNI封装
    我们接下来要做的就是将com_JDefine.h头文件中的函数进行实现。
    新建一个C++项目,并把com_JDefine.h、jni.h、jni_md.h添加到当前目录下,或者新建一个include文件夹。
    创建一个cpp工程:jni_test.cpp,对函数进行实现。如java中函数func1要实现C++中的函数GetID(int a ,ST1 st).则必须通过jni中的函数

    JNIEXPORT jlong JNICALL Java_com_JDefine_func1(JEnv *, jobject , jint ,jobject );

    实现,我们只需将C++中的函数GetID()放在Java_com_JDefine_func1()函数下就行了,而现在的难点就是如何将Java_com_JDefine_func1()的参数传入到GetID()中,和返回值为结构体或者指针的时候该如何处理。
    对于一些如jint基本类型,我们可以直接转,如

    jint a = 10;
    int b = a;

    但是类似于jstring/jArray/jobject等类型的就需要经过转换,才能传递到C++接口参数中。一般转换的步骤比较固定,但是由于类型之间可以相互组合,导致可能的情况很多,比如结构体中嵌套结构体(为jobject(jobject)型),字符串数组(为jArray(jstring)型)等,而且参数传递路径又可以分为

    jni--->C++      C++----->jni

    3.1 jni——>C++

    • jstring 转 const char * :
    jstring str = "fsdfasdf";
    const char *c = (char *)pEnv->GetStringUTFChars(str, 0);
    • jfloatArray转float[]
    方法一:
    //需求float[] unknown;
    //joClazz1为类clazz1的实例,jfA为成员a的jfieldID
    jfloatArray arrFloat = (jfloatArray)pEnv->GetObjectField(joClazz1,jfA);
    jsize size = pEnv->GetArrayLength(arrFloat );
    jfloat *pFloat = pEnv->GetFloatArrayElements(arrFloat,NULL);
    for(int i = 0 ; i < size ; ++i)
    {
    ....
    }
    pEnv->ReleaseFloatArrayElements(arrFloat ,pFloat ,0);
    
    方法二:
    jfloatArray arrFloat = (jfloatArray)pEnv->GetObjectField(joClazz1,jfA);
    jsize size = pEnv->GetArrayLength(arrFloat );
    jfloat ref[size ];
    pEnv->GetFloatArrayRegion(arrFloat ,0,size,ref);
    • jobject

    a . 获得jobject类型

    //方法一:已知jobject obj
    jclass jcClazz1 = pEnv->GetObjectClass(obj);
    //方法二:
    jclass jcClazz1 = pEnv->FindClass("com/JDefine$clazz1");

    jclass为类类型,而jobject可以理解为jclass的实例,方法一的提前是已知一个实例obj,从而可以得到这个实例的类型。而方法二的查找路径和格式需要注意:”包名/类名$嵌套类”。

    b.获得每个成员的jfieldID
    格式如:

    jfieldID jfValid = pEnv->GetFieldID(jcObjInfo,"nValid","I");

    函数GetFieldID中第三个参数为成员nValid的类型的签名,其他类型的签名如下:

    int    ----> I
    long   ----> J
    bool   ----> Z
    string ----> Ljava/lang/String;
    object ----> L包名/类名$嵌套类名;
    method ----> (arg1 , arg2, ......)返回值签名,如(int,float)I
    数组    ----> [类型签名,如[I

    例子:
    在JDefine.java文件中:

    class clazz3
    {
        clazz1[] a;
        clazz2 b;
        int[] c;
        string d;
    };

    令obj为clazz3的实例。
    则:

    //注意以下参数格式"Lcom/JDefine$clazz2;"FindClass函数的"com/JDefine$clazz1"的区别。
    jclass jcClass3 = pEnv->GetObjectClass(obj);
    jfieldID jfa = pEnv->GetFieldID(jcClass3 ,"a","[Lcom/JDefine$clazz1;");
    jfieldID jfb = pEnv->GetFieldID(jcClass3 ,"b","Lcom/JDefine$clazz2;");
    jfieldID jfc = pEnv->GetFieldID(jcClass3 ,"c","[I");
    jfieldID jfd = pEnv->GetFieldID(jcClass3 ,"d","Ljava/lang/String;");

    c. 获得每个成员的值

    jobjectArray joaArray = (jobjectArray)pEnv->GetObjectField(obj,jfa);
    jobject job = pEnv->GetObjectField(obj,jfb);
    jintArray cArray = (jintArray)pEnv->GetObjectField(obj,jfc);
    jstring d = (jstring)pEnv->GetObjectField(obj,jfd);

    对于对象数组joaArray和整型数组cArray ,我们可以通过GetObjectArrayElement()和GetIntArrayElements()函数来获得某一个对象和cArray的首地址:

    //获得joaArray 的大小:
    jsize size = pEnv->GetArrayLength(joaArray);
    for(int i = 0; i < size; ++i)
    {
        //提取第i个对象
        jobject joa = pEnv->GetObjectArrayElement(joaArray,i);
        ……
        ……
    }
    //获得cArray 的大小:
    jfloat *pInt = pEnv->GetIntArrayElements(cArray,NULL);
    jsize size = pEnv->GetArrayLength(cArray);
    ......
    
    //释放内存
    pEnv->ReleaseIntArrayElements(cArray,pInt ,0);

    注意:最后需要释放内存,否则容易内存泄露。但如果还有别的指针指向这块内存,就不能释放。

    3.2 C++ ——> jni

    • char* 转 jstring
    char *c = "zsdjfkdsf";
    jstring str = pEnv->NewStringUTF(c)

    当jstring在jobject中时:

    //已知char *c = "zsdjfkdsf";
    //假设对象jobject jo1中有成员jstring str,将str赋值c;
    jclass jcStr = pEnv->GetObjectClass(jo1);
    jfieldID jfStr = pEnv->GetFieldID(jcLogIn,"str","Ljava/lang/String;");
    jstring myStr = pEnv->NewStringUTF(c);
    pEnv->SetObjectField(jo1,jfStr ,myStr );
    • float *转 jfloatArray
    方法一:
    //已知float *pKnown ,求jfloatArray jUnknown;
    //arrLen 为float数组长度
    jfloatArray jUnknown= (jfloatArray)pEnv->NewFloatArray(arrLen);
    jfloat *pData = pEnv->GetFloatArrayElements(jUnknown,NULL);
    memcpy(pData ,pKnown,arrLen);
    
    方法二:
    pEnv->SetFloatArrayRegion(jUnknown,0,arrLen,pKnown);
    展开全文
  • 内含dll/so文件的简单封装及调用的两例子程序,亲测可用。
  • 注:.dll文件是Windows平台下的动态链接库文件,在Linux平台,有响应功能的文件是.so文件,.so文件接口的封装也可以参考此文的思路 目录 一、DLL项目结构介绍 二、DLL项目示例 1.文件 My_Dll_Project.h 2.文件...

    注:.dll文件是Windows平台下的动态链接库文件,在Linux平台,有响应功能的文件是.so文件,.so文件接口的封装也可以参考此文的思路

    目录

     一、DLL项目结构介绍

    二、DLL项目示例

    1.文件 My_Dll_Project.h

    2.文件 My_Dll_Project.cpp

    3.修改后的文件 My_Dll_Project.h

    三、封装方法介绍

    1.再包装法

    1.1 新接口头文件 My_Dll_Project_Interface.h(给用户的头文件)

    1.2 文件 My_Dll_Project_Interface.cpp (给用户头文件的实现,不公开)

    1.3 封装后的原头文件 My_Dll_Project.h (不公开)

    1.4 再包装法面临的难题

    2.抽象类法

    2.1 抽象类接口头文件 My_Dll_Project_Interface_2.h(给用户的头文件)

    2.2 文件 My_Dll_Project_Interface_2.cpp (给用户头文件的实现,不公开)

    1.3 封装后的原头文件 My_Dll_Project.h (不公开)

    3.大小欺骗法(原创,未深度检验)

    3.1 原来给用户的接口头文件(不公开)

    3.2 占位+删减处理后的头文件(公开给用户)


    一、DLL项目结构介绍

    一个C++的DLL项目的基本结构是:

    1. 头文件(.h),写好这个库准备给人家用的函数、类、变量等的声明。
    2. 源文件(.cpp),写上面那个头文件中声明给人家用的函数等的具体实现。
    3. 库文件(.lib),用你DLL的程序员编译他的程序时会用到。
    4. 程序数据库文件(.pdb),保存这个DLL库的调试信息,只有调试这个库时才必须用到。
    5. 动态链接库文件(.dll),用户编写的程序运行时才会用到这个文件。

    其中,源文件(.cpp)里写接口函数的具体实现,编译完成后一般不公开给用户,原因有很多,如,保留产权,或者为了彻底封装,甚至可能只是因为代码写得太烂不好意思公开(手动滑稽)。

    头文件(.h),库文件(.lib),动态链接库文件(.dll)必须给用户,程序数据库文件(.pdb)给不给看心情,如果给了,用户就能在调试的多看几步。

    头文件(.h)就是其他程序员(用户)使用你的库的接口了,也就是说,头文件告诉了用户如何使用你的库,原理参考C++教科书关于声明和定义的章节。


    二、DLL项目示例

    下面是用VS创建项目自动生成的代码,项目名:My_Dll_Project


    1.文件 My_Dll_Project.h

    // 下列 ifdef 块是创建使从 DLL 导出更简单的
    // 宏的标准方法。此 DLL 中的所有文件都是用命令行上定义的 MYDLLPROJECT_EXPORTS
    // 符号编译的。在使用此 DLL 的
    // 任何项目上不应定义此符号。这样,源文件中包含此文件的任何其他项目都会将
    // MYDLLPROJECT_API 函数视为是从 DLL 导入的,而此 DLL 则将用此宏定义的
    // 符号视为是被导出的。
    #ifdef MYDLLPROJECT_EXPORTS
    #define MYDLLPROJECT_API __declspec(dllexport)
    #else
    #define MYDLLPROJECT_API __declspec(dllimport)
    #endif
    
    // 此类是从 dll 导出的
    class MYDLLPROJECT_API CMyDllProject {
    public:
    	CMyDllProject(void);
    	// TODO: 在此处添加方法。
    };
    
    extern MYDLLPROJECT_API int nMyDllProject;
    
    MYDLLPROJECT_API int fnMyDllProject(void);
    

    2.文件 My_Dll_Project.cpp

    // My_Dll_Project.cpp : 定义 DLL 的导出函数。
    //
    
    #include "framework.h"
    #include "My_Dll_Project.h"
    
    
    // 这是导出变量的一个示例
    MYDLLPROJECT_API int nMyDllProject=0;
    
    // 这是导出函数的一个示例。
    MYDLLPROJECT_API int fnMyDllProject(void)
    {
        return 0;
    }
    
    // 这是已导出类的构造函数。
    CMyDllProject::CMyDllProject()
    {
        return;
    }
    

    这样的头文件给别人似乎看不出来什么问题,如果你感觉注释太多,删掉也可以,但是如果接口是下面这样,再直接给用户,就很成问题了。

    3.修改后的文件 My_Dll_Project.h

    #pragma once
    #ifdef MYDLLPROJECT_EXPORTS
    #define MYDLLPROJECT_API __declspec(dllexport)
    #else
    #define MYDLLPROJECT_API __declspec(dllimport)
    #endif
    
    #include <Windows.h>
    
    // 此类是从 dll 导出的
    class MYDLLPROJECT_API CMyDllProject
    {
    public:
    	CMyDllProject(void);
    	void this_dll_user_is_good();
    
    private:
    	int user;
    	char is;
    	void* bad;
    	HANDLE yeah;
    
    private:
    	HANDLE user_is_bad();
    	void user_is_stupid();
    	void user_is_dirty();
    	void user_is_nasty();
    	void user_is_ugly();
    	void user_is_wtf_i_cant_say_more();
    
    };
    
    extern MYDLLPROJECT_API int nMyDllProject;
    
    MYDLLPROJECT_API int fnMyDllProject(void);
    

    当然这里的说用户坏话的函数名只是开个玩笑,认真地说,这个头文件有以下两个问题:

    1. private 标签标识这个这个函数是私有的,也就是说这个类的用户被阻止使用这些函数(原因不解释),也就是说这些函数、成员变量是用户绝对用不到的,不需要暴露给用户。而私有成员部分暴露了类的实现方式,削弱了库的封装性和保密性,用户可以获得内部实现的线索,同时,如果后续修改私有成员,那么用户必须连头文件都一起替换。
    2. 这个库的实现用到了<Windows.h>头文件,但是这个头文件并不是接口所必须的,用户#include这个头文件时,会把<Windows.h>一并包含进来,会污染用户的命名空间。

    函数和变量是独立的,可以分别决定每个函数和变量是否导出,但是类的定义一定是所有成员都写在一起的,这才导致了问题,下面就来介绍常用的类的两种深度隔离的封装方法。

    三、封装方法介绍

    1.再包装法

    就是不导出原本要导出的类,重写一个新类,新类中数据成员只有一根指向原先类的指针,新类的所有普通函数都是通过指针对原先类的调用,就好像在商品外面重新包装了一样,再包装类可以完全隔绝原有类的副影响,而且可以给原先不支持移动语义(C++11)的类附加移动语义支持。


    1.1 新接口头文件 My_Dll_Project_Interface.h(给用户的头文件)

    #pragma once
    #ifdef MYDLLPROJECT_EXPORTS
    #define MYDLLPROJECT_API __declspec(dllexport)
    #else
    #define MYDLLPROJECT_API __declspec(dllimport)
    #endif
    
    class CMyDllProject;	// 这里写声明
    
    class MYDLLPROJECT_API CMyDllProjectInterface
    {
    public:
    	CMyDllProjectInterface(void);
    	CMyDllProjectInterface(CMyDllProjectInterface&& mov) noexcept;	// 添加移动语义
    	~CMyDllProjectInterface();
    	
    public:
    	CMyDllProjectInterface& operator=(CMyDllProjectInterface&& mov) noexcept;	// 添加移动语义
    
    public:
    	void this_dll_user_is_good();
    
    private:
    	CMyDllProject* _self;	// 只暴露一根指针,甚至不暴露,直接声明为void*,然后实现代码使用强制类型转换也可以
    };
    

    1.2 文件 My_Dll_Project_Interface.cpp (给用户头文件的实现,不公开)

    #include "My_Dll_Project_Interface.h"
    #include "My_Dll_Project.h"
    
    CMyDllProjectInterface::CMyDllProjectInterface(void) :
    	_self(new CMyDllProject)
    {}
    
    CMyDllProjectInterface::CMyDllProjectInterface(CMyDllProjectInterface&& mov) noexcept:
    	_self(mov._self)
    {
    	mov._self = nullptr;
    }
    
    CMyDllProjectInterface::~CMyDllProjectInterface()
    {
    	if (_self) delete _self;
    }
    
    CMyDllProjectInterface& CMyDllProjectInterface::operator=(CMyDllProjectInterface&& mov) noexcept
    {
    	this->~CMyDllProjectInterface();
    	_self = mov._self;
    	mov._self = nullptr;
    	return *this;
    }
    
    void CMyDllProjectInterface::this_dll_user_is_good()
    {
    	return _self->this_dll_user_is_good();	// 这个类的函数其实就是对原类的简单调用
    }
    

    1.3 封装后的原头文件 My_Dll_Project.h (不公开)

    #include <Windows.h>	// 这个头文件就不用再暴露给用户了
    
    class CMyDllProject
    {
    public:
    	CMyDllProject(void);
    	void this_dll_user_is_good();
    
    private:
    	int user;
    	char is;
    	void* bad;
    	HANDLE yeah;
    
    private:
    	HANDLE user_is_bad();
    	void user_is_stupid();
    	void user_is_dirty();
    	void user_is_nasty();
    	void user_is_ugly();
    	void user_is_wtf_i_cant_say_more();
    
    };
    

    完美封装+添加移动语义

    这种封装的优点:

    1. 直接封装成类的形式,用户可以直接按类的方式使用
    2. 可以自动管理资源,无需智能指针
    3. 可以添加额外的移动语义,同时对象的移动速度非常快。

    缺点:

    1. 从理论上来说,每次调用原函数都要进栈出栈,可能会显著降低性能(测试结果:Debug模式下,方法一比下面的方法二慢了77%,Release版本下,完全优化,方法一比方法二反而快了2%)
    2. 不容易反映原接口头文件中类的继承关系
    3. 写包装接口类的工作量有点大

    反映类的继承关系就是说下面这样:

    1.4 再包装法面临的难题

    class Interface // 这是要直接公开的抽象类接口
    {
    public:
    	virtual int help() = 0;
    };
    
    class A :public Interface // 这是要包装的导出类
    {
    public:
    	virtual int help();
    	void yes();
    };
    
    class B :public A // 这是要包装的导出类
    {
    public:
    	void yes();
    };

    这三个类想导出,封装类可不好写,读者可以自行试试如何解决这个棘手的问题,先告诉大家这个问题并不是无解。

    2.抽象类法

    这个方法的基本原理就是利用抽象类作接口,给用户提供工厂函数(Factory Method),或者把所有工厂函数打包在一起做个工具箱类,让用户使用工厂函数创建实例。


    2.1 抽象类接口头文件 My_Dll_Project_Interface_2.h(给用户的头文件)

    #ifdef MYDLLPROJECT2_EXPORTS
    #define MYDLLPROJECT2_API __declspec(dllexport)
    #else
    #define MYDLLPROJECT2_API __declspec(dllimport)
    #endif
    
    class MYDLLPROJECT2_API CMyDllProjectInterface
    {
    public:
    	virtual void this_dll_user_is_good() = 0;
    };
    
    MYDLLPROJECT2_API CMyDllProjectInterface* factory_function();	// 导出工厂函数
    

    2.2 文件 My_Dll_Project_Interface_2.cpp (给用户头文件的实现,不公开)

    #include "My_Dll_Project.h"
    #include "My_Dll_Project_Interface_2.h"
    
    CMyDllProjectInterface* factory_function()
    {
    	return new CMyDllProject();
    }
    

    1.3 封装后的原头文件 My_Dll_Project.h (不公开)

    #include <Windows.h>
    #include "My_Dll_Project_Interface_2.h"
    
    class CMyDllProject : public CMyDllProjectInterface
    {
    public:
    	CMyDllProject(void);
    	void this_dll_user_is_good();
    
    private:
    	int user;
    	char is;
    	void* bad;
    	HANDLE yeah;
    
    private:
    	HANDLE user_is_bad();
    	void user_is_stupid();
    	void user_is_dirty();
    	void user_is_nasty();
    	void user_is_ugly();
    	void user_is_wtf_i_cant_say_more();
    
    };
    

    这种封装的优点:

    1. 理论上,直接使用指针可以比方法一更快
    2. 不必编写封装类,工作量少

    缺点:

    1. 要求用户管理资源
    2. 不能直接套用一般类的形式(构造函数、拷贝函数、移动、复制)

    这种方法多适用于接口先于代码确定好的情况


    3.大小欺骗法(原创,未深度检验)

    没有点创新怎好意思投原创,这种方法的原理非常简单,就是原来接口头文件在编译好后、交给用户前时,把类中的私有成员尽管删除,然后用char数组填充类的大小,数组长度为sizeof(类)的大小,说白了就是占位的作用。

    3.1 原来给用户的接口头文件(不公开)

    #pragma once
    #ifdef MYDLLPROJECT_EXPORTS
    #define MYDLLPROJECT_API __declspec(dllexport)
    #else
    #define MYDLLPROJECT_API __declspec(dllimport)
    #endif
    
    #include <Windows.h>
    
    class MYDLLPROJECT_API CMyDllProject
    {
    public:
    	CMyDllProject(void);
    	void this_dll_user_is_good();
    
    private:
    	int user;
    	char is;
    	void* bad;
    	HANDLE yeah;
    
    private:
    	HANDLE user_is_bad();
    	void user_is_stupid();
    	void user_is_dirty();
    	void user_is_nasty();
    	void user_is_ugly();
    	void user_is_wtf_i_cant_say_more();
    
    };
    

    3.2 占位+删减处理后的头文件(公开给用户)

    #pragma once
    #ifdef MYDLLPROJECT_EXPORTS
    #define MYDLLPROJECT_API __declspec(dllexport)
    #else
    #define MYDLLPROJECT_API __declspec(dllimport)
    #endif
    
    // 使用占位可以不包含<Windows.h>了
    
    class MYDLLPROJECT_API CMyDllProject
    {
    public:
    	CMyDllProject(void);
    	void this_dll_user_is_good();
    private:
            // 函数尽管删除,数据成员反应在占位数组的大小里
    	char _place_hoder[20];    // 占位20
    };

    包不包含<Windows.h>的问题:如果类没有返回别的头文件中的数据类型的公共函数,就可以实现不包含,其他头文件中的数据类型的数据成员,可以直接用占位数组占位就行了。

    这种封装的好处:

    1. 简单粗暴,而且不会带来额外的性能开销
    2. 直接反应导出类间的继承等关系
    3. woc,还列个啥,最简单的就是最好的,能直接用了难道不爽吗?

    缺点:

    1. 简直就是野路子方法(没错就是野路子,原创)
    2. 兼容性可能有所欠缺,可能会出错(虽然我自己用了好几次没有出任何错误)
    3. 私有函数其实并没有真正意义上的封装在内部(使用dumpbin仍能在dll中看到导出的私有函数)

    之所以想出这种方法,是基于我的一个假设:类的定义体在编译过程中只起到内存大小指示的作用,内存本身并不划分为不同类型。我认为基于这种方法并不能完全封装,我觉得它的用处可能更多在于团队内部合作用到。

    另外我认为,完全封装并不是不行,只是需要编译器支持,让编译器不导出私有函数就行了。

    展开全文
  • Linux下生成动态链接库

    千次阅读 2018-04-18 17:32:53
    【摘要】动态链接库是在编译器编译之后生成 obj 文件之后,将几链接文件和动态链接库中的文件链接起来,在链接器中将几目标文件组合在一起然后生成可执行文件 exe 文件,而生成动态链接库的这一步骤是由编译器...

    【摘要】动态链接库是在编译器编译之后生成  obj 文件之后,将几个链接文件和动态链接库中的文件链接起来,在链接器中将几个目标文件组合在一起然后生成可执行文件  exe 文件,而生成动态链接库的这一步骤是由编译器自己完成的,它可以调用自己已经处理好的库函数,在需要时直接拿出来使用就好了。而今天,我要分享给大家一个可以自己实现动态链接库生成的小技巧,别说,这种小技巧在面试中还是会被经常问到的哦。
    方法一:

    第一步:

    先编写两个 .c 文件(为了方便,我写了一个最简单的 add.c main.c 。add.c 就是直接做加法运算,main.c 是对 add函数的调用
    第二步:

           这一步很关键,要生成add的库文件 libadd.so (后面的名字是我自己起的,方便理解,你们也可以起其他的名字,但是一定要加上后缀 .so ,这是库文件的后缀)
           输入命令行  ——>   gcc -fpic -shared add.c -o libadd.so
           经过这个步骤,你的文件夹下 用 ls 就可以看到已经生成了 libadd.so 

    第三步:
           生成了动态库之后,我们需要把动态库和 main.c 文件通过系统编译器链接在一起进行编译,生成 a.out 可执行文件

    输入命令行——> gcc libadd.so main.c
    现在你再次 ls 一下,就会发现文件夹下又多了一个 a.out 文件。但是,你现在直接 ./a.out ,系统会报错,大概意思就是找不到你的动态库地址,,这个时候你需要找到你的动态库拷到  /lib/库中,这样才可以被系统检测到

    第四步
    将你自己的动态库拷到/lib/目录下  

    输入命令——>cp libadd.c /lib/
    这个时候就已经大功告成了,你再次使用命令 ./a.out 程序就会顺利执行并且输出结果了。

    方法二:


    方法二的前三步和方法一都是一样的,所以如果你要想在方法一试过之后再尝试方法二,那你就需要删除 a.out 和 libadd.so 这俩文件了。
    第四步:
      现在我们已经知道了,如果系统没有找到我们自己生成的动态库的路径,就无法通过编译,在方法一里面,我们是直接将动态库拷到了 /lib/目录下,相当于充当了一个库文件,这次我们直接 vim 一把 ,将动态库的路径写到一个一个库文件中
    输入命令行——>   vim /etc/ld.so.conf.d/ku.conf
    第五步:
           刷新一下缓冲区
      输入命令行——> ldconfig
      然后你再次输入 ./a.out 也可以相应的输出结果啦,是不是并没有那么难呢。


    但是,切记,你的动态库再使用完之后还是尽快删除吧。尽管这个库文件不是很大,也没有占用多少内存,但是,在以后的实际工作中,如果你要编写的代码量很大时,还是很耗费内存的。为了减少不必要的浪费,记得删除你自己的动态库文件哦。

    展开全文
  • Linux下的动态链接库叫so,即Shared Object,共享对象。一些函数就不说了,网上的是。把我遇到的问题写下来吧提示错误 undefined reference to `dlopen'编译时增加“-ldl”选项即可解决。提示错误 cannot open ...

    我的程序是一个类,在网上找了半天,都是c的例子,c++的类封装成静态库倒容易,可是如何封装成动态库,在其它程序中调用呢?
    Linux下的动态链接库叫so,即Shared Object,共享对象。一些函数就不说了,网上多的是。把我遇到的问题写下来吧

    提示错误 undefined reference to `dlopen'
    编译时增加“-ldl”选项即可解决。

    提示错误 cannot open shared object file: No such file or directory
    将当前目录的绝对路径添加到LD_LIBRARY_PATH即可
    export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/zhj/test_so/

    提示错误 undefined reference to
    检查了一下,原来在makefile里少包含了一个文件。
    这一类错误一般都是缺少相应的类文件所致。

    例子来源:
    http://www.linuxsir.org/bbs/printthread.php?t=266890

    这个扩展名为hpp的文件,我分解了一下,下面就上代码吧

    polygon.h
    //----------
    //polygon.h:
    //----------
    #ifndef POLYGON_H
    #define POLYGON_H
    #include <stdio.h>

    class polygon {
    protected:
    double side_length_;

    public:
    polygon();
    virtual ~polygon();

    void set_side_length(double side_length);

    virtual double area() const;
    };

    // the types of the class factories
    typedef polygon* create_t();
    typedef void destroy_t(polygon*);

    #endif

    polygon.cpp
    //----------
    //polygon.cpp:
    //----------
    #include "polygon.h"


    polygon::polygon()
    {
    //set_side_length(0);
    }

    polygon::~polygon()
    {
    }

    void polygon::set_side_length(double side_length)
    {
    printf("polygon side_length: %f/n/n",side_length);
    side_length_ = side_length;
    printf("polygon side_length_: %f/n/n",side_length_);
    }

    double polygon::area() const
    {
    return 0;
    }

    triangle.cpp
    //----------
    //triangle.cpp:
    //----------
    #include "polygon.h"
    #include <cmath>

    class triangle : public polygon {
    public:
    virtual double area() const {
    printf("triangle side_length_: %f/n/n",side_length_);
    return side_length_ * side_length_ * sqrt(3) / 2;
    }
    };


    // the class factories
    extern "C" polygon* create() {
    return new triangle;
    }

    extern "C" void destroy(polygon* p) {
    delete p;
    }

    main.cpp
    //----------
    //main.cpp:
    //----------
    #include "polygon.h"
    #include <iostream>
    #include <dlfcn.h>

    int main() {
    using std::cout;
    using std::cerr;

    // load the triangle library
    void* triangle = dlopen("./triangle.so", RTLD_LAZY);
    if (!triangle) {
    cerr << "Cannot load library: " << dlerror() << '/n';
    return 1;
    }

    // reset errors
    dlerror();

    // load the symbols
    create_t* create_triangle = (create_t*) dlsym(triangle, "create");
    const char* dlsym_error = dlerror();
    if (dlsym_error) {
    cerr << "Cannot load symbol create: " << dlsym_error << '/n';
    return 1;
    }

    destroy_t* destroy_triangle = (destroy_t*) dlsym(triangle, "destroy");
    dlsym_error = dlerror();
    if (dlsym_error) {
    cerr << "Cannot load symbol destroy: " << dlsym_error << '/n';
    return 1;
    }

    // create an instance of the class
    polygon* poly = create_triangle();

    // use the class
    poly->set_side_length(7);
    cout << "The area is: " << poly->area() << '/n';

    // destroy the class
    destroy_triangle(poly);

    // unload the triangle library
    dlclose(triangle);
    }

    makefile

    objects = main.o polygon.o
    main: $(objects)
    g++ -g -rdynamic -o $@ $(objects) -ldl
    main.o: main.cpp
    g++ -g -c main.cpp
    polygon.o: polygon.cpp polygon.h
    g++ -g -c polygon.cpp

    .PHONY : clean
    clean :
    -rm main $(objects)


    用下面的命令编译,生成libtriangle.so
    g++ -g -fpic -shared -o libtriangle.so triangle.cpp polygon.cpp

    然后make一下,运行main试试吧!

     

    另:

    http://www.cnblogs.com/zhoutian6214/archive/2008/11/11/1331646.html

    展开全文
  • 封装动态库 (1)创建Shared Library 工程 创建工程new-&gt;project-&gt;C Project-&gt;Shared Library-&gt;Empty Project,之后选择Linux GCC ,输入工程名MeLib,最后点击...
  • Windows系统本身提供并使用了大量的库,包括静态链接库(.lib文件)和动态链接库(.dll文件)。类似的,Linux操作系统也使用库。Linux系统中,通常把库文件存放在/usr/lib或/lib目录下。Linux库文
  • 设计软件架构时,为了以后升级考虑和层次间的解耦,会将一些功能封装动态链接库中。当需要对方法升级时,只要保证动态链接库的API定义不变,就可以对局部功能进行升级。 但是由于有些服务要求在更新时也不能停止...
  • 问题是这样的,自己的代码实现了一数据分类的功能,如何把这部分代码完全打包成动态库和头文件,提供API给别人调用呢? 如果没有使用其它的依赖或者其它开源代码,那么可以直接按照gcc编译器生成和使用静态/...
  • linux 静态库动态库封装问题

    千次阅读 2016-05-04 09:09:43
    早上看到有人提问关于linux编译报头文件缺失问题,找到刚工作时候,用到的关于封装的问题,贴出来
  • Linux 下libusb编译与生成动态链接库

    千次阅读 2018-04-13 11:17:57
    二、libusb使用GCC 编译正常来讲,使用 gccc xxx.c -o xxx -I/usr/include -lusb-1.0 就可以了,如下图:但是,当使用Ubuntu 12.04 使用这条命令编译,则会报错,如下图:提示,未找到这`clock_gettime' 这函数...
  •  由于项目中要用到java调用C++的文件,于是需要在linux下编译连接生成动态库,然后根据Recast官方包要先CMake生成编译,然后在包含到自己实现的文件,网上查了半天,坑爹没有全面的,东拼西凑终于才明白原理,...
  • Linux C语言调用C++动态链接库

    千次阅读 2013-09-12 10:53:39
    如果你有一c++做的动态链接库.so文件,而你只有一些相关的声明,那么你如何用c调用呢,别着急,本文通过一小小的例子,让你能够很爽的搞定.    链接库头文件: //head.h class A { public: A(); virtual ~A(); ...
  • 用于加、解密,用C++写的动态库。同时将动态库的调用封装为一个类,方便大家的使用。本已经用于我自己做过的很程序,没有出现问题。
  • C++ 动态链接库和静态链接库

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

    千次阅读 2010-07-13 08:49:00
    1分析Windows和Linux动态库 摘要:动态链接库技术实现和设计程序常用的技术,在Windows和Linux系统中都有动态库的概念,采用动态库可以有效的减少程序大小,节省空间,提高效率,增加程序的可扩展性,便于模块化...
  • Qt: Window, Linux动态链接库的分析对比

    千次阅读 2014-03-23 20:17:41
    摘要:动态链接库技术实现和设计程序常用的技术,在Windows和Linux系统中都有动态库的概念,采用动态库可以有效的减少程序大小,节省空间,提高效率,增加程序的可扩展性,便于模块化管理。但不同操作系统的动态库...
  • 动态与静态两种,动态通常用.so为后缀,静态用.a为后缀。例如:libhello.so libhello.a 为了在同一系统中使用不同版本的,可以在文件名后加上版本号为后缀,例如: libhello.so.1.0,由于程序连接默认以.so为...
  • 本文主要介绍了生成动态库与静态文件的过程、以及封装和使用文件的方法。 二、静态.a与动态库.so的生成与区别 .o文件 :二进制目标文件,可用于打包成文件也可以链接生成可执行文件; c文件...
  • linux动态链接库(.so)的显式调用和隐式调用

    万次阅读 多人点赞 2015-02-04 17:25:31
     我们知道,动态库相比静态的区别是:静态是编译时就加载到可执行文件中的,而动态库是在程序运行时完成加载的,所以使用动态库的程序的体积要比使用静态程序的体积小,并且使用动态库的程序在运行时必须依赖...
  • 这篇笔记我先尝试通过mcc将.m函数编译成动态链接库供c++调用的方式。在另一篇笔记中还尝试了另一种途径:通过matlab引擎来实现。其实,调用自己编写的m函数,只是了一步将自己的matlab函数编译
  • LINUX动态库.so嵌套.so文件,二次封装,完整源码,演示项目齐全
  • JSP/Java调用本地动态链接库,调用第三方动态链接库
  • 静态链接库和动态链接库

    千次阅读 2011-02-23 14:59:00
    库函数根据是否被编译到程序内部而分为静态链接库(static)和动态链接库(dynamic). 简而言之,库是函数的集合,或.o文件的集合.链接器(LD)用于将用户自己的.o文件与库函数链接在一起. 一、静态链接库(静态函数库)...
  • Linux下gcc-编译多个文件为动态库

    千次阅读 2017-04-21 15:55:23
    #ifndef _HEAD_H_ #define _HEAD_H_ void test1(); void test2(); void test3(); ...连接动态库: gcc main.c -L. -ltest -o main 执行: ./main
  • C++动态库封装及调用

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

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 29,203
精华内容 11,681
关键字:

多个类封装动态链接库linux

linux 订阅