Linux下C++程序编译链接的学习例子

Linux下C++程序编译链接的学习例子

一、项目例子的整体结构

  1. 本项目旨在简单学习C++编译连接的过程以及Makefile和Cmake的简单语法,项目结构如下:

    .
    ├── appC
    │   ├── include
    │   ├── lib
    │   └── src
    │       ├── CMakeLists.txt
    │       ├── main.cpp
    │       └── Makefile
    ├── cleanAll.sh
    ├── CMakeLists.txt
    ├── libA
    │   ├── output
    │   │   ├── include
    │   │   └── lib
    │   └── src
    │       ├── A.cpp
    │       ├── ADll.h
    │       ├── A.h
    │       ├── CMakeLists.txt
    │       └── Makefile
    ├── libB
    │   ├── include
    │   ├── lib
    │   ├── output
    │   │   ├── include
    │   │   └── lib
    │   └── src
    │       ├── B.cpp
    │       ├── BDll.h
    │       ├── B.h
    │       ├── CMakeLists.txt
    │       └── Makefile
    └── readme.md
    
    
  2. 依赖关系为:main->B->A,cleanAll.sh用来删除生成的*.o *.a *.so文件,以便学习不同的链接方式。

二、代码结构

  1. main代码如下:

    #include "B.h"
    #include <iostream>
    
    using namespace std;
    
    int main (int argc, char *argv[])
    {
        B objB;
        int result = objB.func(1, 2);
        
        cout << "result = " << result << endl;
    
    }
    
  2. libA代码如下:

    /**
    *********A.cpp************
    */
    #include "A.h"
    #include <iostream>
    
    using namespace std;
    
    A::A()
    {
        cout << "A ctor" << endl;
    }
    
    A::~A()
    {
        cout << "A dtor" << endl;
    }
    
    int A::func(int x, int y)
    {
        return (x + y) * 2;
    }
     /**
     *********A.h************
     */
     #ifndef _A_H_
    #define _A_H_
    
    #include "ADll.h"
    
    class LIBA_API A {
    public:
        A();
        ~A();
        int func(int x, int y);
    
    };
    
    #endif
    
  3. libB代码如下:

     /**
    *******************B.cpp********************
     */
    #include "B.h"
    #include "A.h"
    #include <iostream>
    using namespace std;
    
    class BImpl {
    public:
        BImpl() {};
        ~BImpl() {};
    
        int func(int x, int y){
            return objA.func(x, y);
        }
    
    private:
        A objA;
    };
    
    B::B() : _impl(new BImpl)
    {
        cout << "B ctor" << endl;
    }
    
    B::~B()
    {
      cout << "B dtor" << endl;
      delete _impl;  
    }
    
    int B::func(int x, int y){
        return _impl->func(x, y);
    }
    
    
     /**
    ************B.h*******************
     */
    #ifndef _B_H_
    #define _B_H_
    
    #include "BDll.h"
    
    class BImpl;
    
    class LIBB_API B {
    public:
        B();
        ~B();
        int func(int x, int y);
    
    private:
        BImpl *_impl;
    };
    
    #endif
    
  4. 以libA下的makefile为例学习makefile:

    CXX			= g++ #编译器
    AR			= ar   # 对.o文件进行归档,生成库
    
    CXXFLAGS = -shared -g -Wall -fPIC -std=c++11 -I./      #g++参数:-shared 调用动态库  -g 可执行程序包含调试信息 -Wall 编译后显示所有警告     																											-fPIC 真正的共享.so   -std=c++11 支持c++11标准; -I 后跟头文件地址
    LDFLAGS  = -L./  # 库文件地址      ### -Wl,-rpath=your_lib_dir 只有-L选项时有可能编译通过但是执行时not found 库,因为-L只有编																							译时有效,可以通过加上此命令记住链接库的位置;
    
    SRCS = $(wildcard *.cpp)  # wildcard 函数,展开所有的.cpp文件 
    OBJS = $(patsubst %.cpp, %.o, $(SRCS))  patsubst函数  将.cpp全部替换成.o并返回,等于objs等于.cpp编译的.o文件
    $(info SRCS = $(SRCS))
    $(info OBJS = $(OBJS))  # info make的打印函数
    
    TARGET	= libA.so libA.a   # 编译目标
    OUTPUT	= ../output #输出位置
    
    all: clean $(TARGET) install copy2B     # 让所有的操作顺次执行,
    .PHONY: clean install copy2B   #因为make的规则是 目标 :依赖 ,当 main : main.c 时,为了从依赖获取目标,就会执行之后的规则;
    				      #但是当clean: 后没有依赖时,make会认为依旧获取到目标,因此不再执行clean后的规则,因此
                                           #引用.PHONY关键字,意为欺骗make,clean目标还未达成,即会执行clean之后的规则;
    						#  目标  : 依赖
    						#        规则
    
    
    .cpp.o:  #自动将.cpp识别为源文件后缀,.o识别为目标文件后缀;   g++ -c 将.cpp编译成.o ,而不是可执行
    	@echo "\ncompile..."
    	$(CXX) -c $< -o $@ -DLIBA_API_EXPORTS $(CXXFLAGS)     # $@目标文件   $^所有的依赖文件  $<第一个依赖文件  ¥% 仅当目标时函数库文																																		件,表示规则中的目标成员名;
    
    $(TARGET): $(OBJS)   # .o到.so .a
    	$(CXX) $^ -o $@ $(CXXFLAGS) $(LDFLAGS)  #g++生成动态库 gcc -fPIC -shard -o 目标  源文件   gcc -c -fPIC  *.c     gcc  -shared -o *.so *.o 
    	$(AR) -crsv libA.a $^     # ar  创建静态库 -c 禁止再创建库的文件时产生的正常信息 -r 如果已存在,则替换 -s 无论是否修改了库的内容,强制生成库符号表 其余命令可查找学习
    
    install:
    	@echo "\ninstall to output..."
    	cp A.h ADll.h $(OUTPUT)/include   # cp 移动
    	cp -a $(TARGET) ${OUTPUT}/lib
    
    clean:
    	@echo "\nclean..."
    	rm -rf $(TARGET) *.o  #清除中间文件
    
    copy2B:
    	@echo "\ncopy to libB..."
    	cp ../output/include/*	../../libB/include
    	cp ../output/lib*	../../libB/lib
    
    
  5. 现在已经学会了简单的makefile文件编写,接下来就可以进行实践尝试了;

三、动态库的链接

  1. 将A编译成动态库,B依赖A编译成动态库,main依赖B生成可执行文件,依赖A,B的动态库,

    可以通过修改makefile来达到目的。

    问题1:

    ​ A,B的编译都顺利通过,编译main时依旧顺利通过,但当运行./main时报错:

    ./main: error while loading shared libraries: libA.so: cannot open shared object file: No such file or directory
    
    
    # 检查makefile里的配置
    LDFLAGS  = -lA -lB -L../lib/linux -Wl,-rpath=../lib/linux
    

    通过ldd ./main可以查看动态库链接信息,可以发现成功链接到libB.so,但是libA.so not found

    $ ldd ./main
    	linux-vdso.so.1 (0x00007ffe1d7ff000)
    	libB.so => ../lib/linux/libB.so (0x00007f173646a000)
    	libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f17360e1000)
    	libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f1735ec9000)
    	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f1735ad8000)
    	libA.so => not found
    	libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f173573a000)
    	/lib64/ld-linux-x86-64.so.2 (0x00007f1736870000)
    
    

    解决方法:由于在Linux上,可执行程序会去系统默认下的路径去寻找共享库(*.so),因此动态库放在自己的目录下时,编译时会通过,但是执行时却会提示找不到。

    1. linux系统默认的动态库路径有 /lib/ /usr/lib 因此在编译动态库时可以直接在makefile里将动态库cp到系统的默认路径下,即可解决问题。

    2. 如果想自己创建一个用来学习测试的库目录,可以将自己的目录添加到系统的配置文件中,也可以达到目的,方法如下:

      sudo vim /etc/ld.so.conf
      

      通过vim或gedit打开配置文件,添加自己的目录。

      sudo  /sbin/ldconfig
      

      添加完成之后,保存并通过以上命令加载配置即可。

      至于为什么可以找到B却不能找到A,笔者猜测是由于可执行文件先加载B,此时搜索完成指定目录,而B又依赖于A,此时去搜索A的时候,之前的搜索已经完成,因此直接跳过指定目录,转而去搜索系统默认的其他路径。以上只是随意猜测,实际具体的原因笔者并未深入了解;

  2. 如果我们自己生成的B库依赖于第三方的A库,但是不希望主程序知道A库,该怎么办呢?

    即将A编译成静态库,在编译B时直接将A完成链接,此时只需要提供B的动态库给主程序即可。我们可以通过修改makefile文件来测试。

  3. 如果A,B都编译为静态库,同样需要将A,B都交给main。

四、CMake学习

1、以B和main的CmakeLists.txt文件为例子,

#工程的CMakeList.txt

CMAKE_MINIMUM_REQUIRED(VERSION 3.0)  #最低版本要求
PROJECT(test VERSION 1.0)       #项目名称

#编译器
set(CMAKE_C_COMPILER  "bin/gcc")   #这里没用到交叉编译,可以直接写编译器地址
set(CMAKE_CXX_COMPILER  "bin/g++")
#编译选项
set(CMAKE_CXX_FLAGS "-g -Wall -fPIC std=c++11 -02")  #编译参数

#libA
ADD_SUBDIRECTORY(libA/src)   #添加子目录

#libB
ADD_SUBDIRECTORY(libB/src)

#appC
ADD_SUBDIRECTORY(appC/src)
#libB下的CMakeList.txt

#源文件
AUX_SOURCE_DIRECTORY(. SRC)   # 此函数查找目录下所有源文件 = src

#编译动态库
add_definitions("-DLIBB_API_EXPORTS")

#编译静态库,因为B.cpp中include “A.h”,所以要定义LIBA_STATIC
#add_definitions("-DLIBA_STATIC")
#add_definitions("-DLIBB_STATIC")

#头文件目录
INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/../include)  # CMAKE_CURRENT_SOURCE_DIR  当前cmakelists所在的目录
  
#库文件目录
LINK_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/../lib)


#生成动态库
ADD_LIBRARY(libb_shared SHARED ${SRC})   #用法 ADD_LIBRARY(name  [STATIC | SHARED |MODULE] source)

SET_TARGET_PROPERTIES(libb_shared PROPERTIES OUTPUT_NAME "B")  #设置目标的一些属性,此处设置了名字
TARGET_LINK_LIBRARIES(libb_shared A)  #链接依赖库

#生成静态库
ADD_LIBRARY(libb_static STATIC ${SRC}) 
SET_TARGET_PROPERTIES(libb_static PROPERTIES OUTPUT_NAME "B")
TARGET_LINK_LIBRARIES(libb_shared A)

set(OUTPUT ${PROJECT_SOURCE_DIR}/libB/output)
INSTALL(FILES B.h BDll.h DESTINATION ${OUTPUT}/include)

INSTALL(TARGETS libb_shared libb_static 
        LIBRARY DESTINATION ${OUTPUT}/lib)#动态库安装路径
        ARCHIVE DESTINATION ${OUTPUT}/lib}#静态库安装路径
        ## 没用到的命令
        ##RUNTIME 可执行文件
        ##public_HEADER  头文件


#main

#源文件
AUX_SOURCE_DIRECTORY(. SRC)

#使用静态库,因为main.cpp中 include"B.h",所以要定义LIBB_STATIC
add_definitions("-DLIBB_STATIC") 

#头文件目录
INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/../include)

#库文件目录
LINK_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/../lib)

#生成可执行文件
ADD_EXECUTABLE(main ${SRC})

TARGET_LINUX_LIBRARIES(main 
                        libB.so
                        libA.so)

注意在新建build文件夹来进行编译,每一个CMakeLists生成的makefile需要单独进行make编译。

posted on 2020-12-05 21:19  初学者5467852  阅读(136)  评论(0)    收藏  举报