在Linux下动态库(.so)中,通过GCC的C++ visibility属性可以控制共享文件导出符号。在GCC 4.0及以上版本中,有个visibility属性,可见属性可以应用到函数、变量、模板以及C++类。
限制符号可见性的原因:从动态库中尽可能少地输出符号是一个好的实践经验。输出一个受限制的符号会提高程序的模块性,并隐藏实现的细节。动态库装载和识别的符号越少,程序启动和运行的速度就越快。导出所有符号会减慢程序速度,并耗用大量内存。
“default”:用它定义的符号将被导出,动态库中的函数默认是可见的。”hidden”:用它定义的符号将不被导出,并且不能从其它对象进行使用,动态库中的函数是被隐藏的。default意味着该方法对其它模块是可见的。而hidden表示该方法符号不会被放到动态符号表里,所以其它模块(可执行文件或者动态库)不可以通过符号表访问该方法。
要定义GNU属性,需要包含__attribute__和用括号括住的内容。可以将符号的可见性指定为visibility(“hidden”),这将不允许它们在库中被导出,但是可以在源文件之间共享。实际上,隐藏的符号将不会出现在动态符号表中,但是还被留在符号表中用于静态链接。
导出列表由编译器在创建共享库的时候自动生成,也可以由开发人员手工编写。导出列表的原理是显式地告诉编译器可以通过外部文件从对象文件导出的符号是哪些。GNU用户将此类外部文件称作为”导出映射”。
以下是测试代码,各个文件的内容如下:
Dynamic_Library/library.hpp:
#ifndef FBC_LIBRARY_LIBRARY_HPP_
#define FBC_LIBRARY_LIBRARY_HPP_
// reference: https://gcc.gnu.org/wiki/Visibility
// https://developer.apple.com/library/content/documentation/DeveloperTools/Conceptual/CppRuntimeEnv/Articles/SymbolVisibility.html
#ifdef __GNUC__ >= 4 // it means the compiler is GCC version 4.0 or later
#ifdef FBC_EXPORT
#warning "===== dynamic library ====="
#define FBC_API_PUBLIC __attribute__((visibility ("default")))
#define FBC_API_LOCAL __attribute__((visibility("hidden")))
#else
#warning "===== static library ====="
#define FBC_API_PUBLIC
#define FBC_API_LOCAL
#endif
#else
#error "##### requires gcc version >= 4.0 #####"
#endif
#ifdef __cplusplus
extern "C" {
#endif
FBC_API_PUBLIC int library_add(int a, int b);
FBC_API_LOCAL void print_log();
#ifdef FBC_EXPORT
FBC_API_PUBLIC int value;
#endif
#ifdef __cplusplus
}
#endif
template
class FBC_API_PUBLIC Simple {
public:
Simple() = default;
void Init(T a, T b);
T Add() const;
private:
T a, b;
};
#endif // FBC_LIBRARY_LIBRARY_HPP_
Dynamic_Library/library.cpp:
#include "library.hpp"
#include
#include
FBC_API_PUBLIC int library_add(int a, int b)
{
#ifdef FBC_EXPORT
value = 11;
#endif
fprintf(stdout, "File: %s, Function: %s, Line: %d\n", __FILE__, __FUNCTION__, __LINE__);
return (a+b);
}
FBC_API_LOCAL void print_log()
{
fprintf(stderr, "print_log function is hidden, in dynamic library: %s, %d\n", __FUNCTION__, __LINE__);
}
template
void Simple::Init(T a, T b)
{
this->a = a;
this->b = b;
}
template
T Simple::Add() const
{
fprintf(stdout, "File: %s, Function: %s, Line: %d\n", __FILE__, __FUNCTION__, __LINE__);
return (a + b);
}
template class Simple;
template class Simple;
Test_Dynamic_Library/test_library.hpp:
#ifndef FBC_CPPBASE_TEST_TEST_LIBRARY_HPP_
#define FBC_CPPBASE_TEST_TEST_LIBRARY_HPP_
namespace test_library_ {
#ifdef __cplusplus
extern "C" {
#endif
int test_library_1();
int test_library_2();
int test_library_3();
#ifdef __cplusplus
}
#endif
} // namespace test_library_
#endif // FBC_CPPBASE_TEST_TEST_LIBRARY_HPP_
Test_Dynamic_Library/test_library.cpp:
#include "test_library.hpp"
#include
#include
#include
namespace test_library_ {
int test_library_1()
{
int a{ 4 }, b{ 5 }, c{ 0 };
c = library_add(a, b);
fprintf(stdout, "%d + %d = %d\n", a, b, c);
#ifdef FBC_EXPORT
fprintf(stdout, "dynamic library: value: %d\n", value);
#endif
return 0;
}
int test_library_2()
{
Simple simple1;
int a{ 4 }, b{ 5 }, c{ 0 };
simple1.Init(a, b);
c = simple1.Add();
fprintf(stdout, "%d + %d = %d\n", a, b, c);
Simple simple2;
std::string str1{ "csdn blog: " }, str2{ "http://blog.csdn.net/fengbingchun" }, str3;
simple2.Init(str1, str2);
str3 = simple2.Add();
fprintf(stdout, "contents: %s\n", str3.c_str());
return 0;
}
int test_library_3()
{
#ifdef FBC_EXPORT
fprintf(stdout, "dynamic library cann't run print_log function\n");
#else
print_log();
#endif
return 0;
}
} // namespace test_library_
Test_Dynamic_Library/main.cpp:
#include
#include "test_library.hpp"
int main()
{
test_library_::test_library_1();
test_library_::test_library_2();
test_library_::test_library_3();
return 0;
}
CMakeLists.txt:
# CMake file for Samples_Dynamic_Library
# 设定依赖的CMake版本
CMAKE_MINIMUM_REQUIRED(VERSION 3.2)
# 指定项目名称
PROJECT(Test_Dynamic_Library)
# 打印相关信息, CMAKE_CURRENT_SOURCE_DIR指的是当前处理的CMakeLists.txt所在的路径
MESSAGE(STATUS "current path: ${CMAKE_CURRENT_SOURCE_DIR}")
# 使CMake支持C++11特性
SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=gnu++0x")
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++0x")
# 定义用户自定义变量
SET(PATH_DYNAMIC_LIBRARY_CPP_FILES ${CMAKE_CURRENT_SOURCE_DIR}/Dynamic_Library)
SET(PATH_TEST_DYNAMIC_LIBRARY_CPP_FILES ${CMAKE_CURRENT_SOURCE_DIR}/Test_Dynamic_Library)
SET(PATH_DYNAMIC_LIBRARY_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/Dynamic_Library)
#MESSAGE(STATUS "Dynamic Library cpp files path: ${PATH_DYNAMIC_LIBRARY_CPP_FILES}")
#MESSAGE(STATUS "Test Dynamic Library cpp files path: ${PATH_TEST_DYNAMIC_LIBRARY_CPP_FILES}")
# 递归查询所有匹配的文件:*.cpp
FILE(GLOB_RECURSE DYNAMIC_LIBRARY_CPP_LIST ${PATH_DYNAMIC_LIBRARY_CPP_FILES}/*.cpp)
FILE(GLOB_RECURSE TEST_DYNAMIC_LIBRARY_CPP_LIST ${PATH_TEST_DYNAMIC_LIBRARY_CPP_FILES}/*.cpp)
#MESSAGE(STATUS "Dynamic Library cpp list: ${DYNAMIC_LIBRARY_CPP_LIST}")
#MESSAGE(STATUS "Test Dynamic Library cpp list: ${TEST_DYNAMIC_LIBRARY_CPP_LIST}")
IF (BUILD_DYNAMIC_LIBRARY)
# 添加编译参数,添加-D预编译宏定义,可以一次添加多个
ADD_DEFINITIONS(-DFBC_EXPORT)
# 生成动态库
ADD_LIBRARY(Dynamic_Library SHARED ${DYNAMIC_LIBRARY_CPP_LIST})
ELSE()
ADD_LIBRARY(Static_Library STATIC ${DYNAMIC_LIBRARY_CPP_LIST})
ENDIF()
# 指定需要包含的头文件
INCLUDE_DIRECTORIES(${PATH_DYNAMIC_LIBRARY_INCLUDE_DIR})
# 生成可执行文件Test_Dynamic_Library
ADD_EXECUTABLE(Test_Dynamic_Library ${TEST_DYNAMIC_LIBRARY_CPP_LIST})
IF (BUILD_DYNAMIC_LIBRARY)
# 用来为target添加需要链接的共享库,指定工程所用的依赖库,包括动态库和静态库
TARGET_LINK_LIBRARIES(Test_Dynamic_Library Dynamic_Library)
ELSE()
TARGET_LINK_LIBRARIES(Test_Dynamic_Library Static_Library)
ENDIF()
编译方法(ReadMe.txt):
在Linux下通过CMake编译Samples_Dynamic_Library中的测试代码步骤:
将终端定位到Linux_Code_Test/Samples_Dynamic_Library,依次执行如下命令:
$ mkdir build
$ cd build
// Note:-DBUILD_DYNAMIC_LIBRARY=1,编译生成动态库; -DBUILD_DYNAMIC_LIBRARY=0, 编译生成静态库
$ cmake -DBUILD_DYNAMIC_LIBRARY=1 ..
$ make (生成动态库和执行文件)
$ ./Test_Dynamic_Library
GitHub:https://github.com/fengbingchun/Linux_Code_Test