windows 環境的vc的話,可以方便的指定__declspec(dllexport) 關鍵字來控制是否把dll中的函數導出。
我也來測試一下linux下面是如何做的:
先看gcc 和ld的相關選項
======================================
gcc 選項
-shared
Produce a shared object which can then be linked with other objects
to form an executable. Not all systems support this option. For
predictable results, you must also specify the same set of options
that were used to generate code (-fpic, -fPIC, or model suboptions)
when you specify this option.[1]
-fpic
Generate position-independent code (PIC) suitable for use in a
shared library, if supported for the target machine. Such code
accesses all constant addresses through a global offset table
(GOT). The dynamic loader resolves the GOT entries when the
program starts (the dynamic loader is not part of GCC; it is part
of the operating system). If the GOT size for the linked
executable exceeds a machine-specific maximum size, you get an
error message from the linker indicating that -fpic does not work;
in that case, recompile with -fPIC instead. (These maximums are 8k
on the SPARC and 32k on the m68k and RS/6000. The 386 has no such
limit.)
Position-independent code requires special support, and therefore
works only on certain machines. For the 386, GCC supports PIC for
System V but not for the Sun 386i. Code generated for the IBM
RS/6000 is always position-independent.
When this flag is set, the macros "__pic__" and "__PIC__" are
defined to 1.
-fPIC
If supported for the target machine, emit position-independent
code, suitable for dynamic linking and avoiding any limit on the
size of the global offset table. This option makes a difference on
the m68k, PowerPC and SPARC.
Position-independent code requires special support, and therefore
works only on certain machines.
When this flag is set, the macros "__pic__" and "__PIC__" are
defined to 2.
-rdynamic
Pass the flag -export-dynamic to the ELF linker, on targets that
support it. This instructs the linker to add all symbols, not only
used ones, to the dynamic symbol table. This option is needed for
some uses of "dlopen" or to allow obtaining backtraces from within
a program.
-fvisibility=default|internal|hidden|protected
Set the default ELF image symbol visibility to the specified
option---all symbols will be marked with this unless overridden
within the code. Using this feature can very substantially improve
linking and load times of shared object libraries, produce more
optimized code, provide near-perfect API export and prevent symbol
clashes. It is strongly recommended that you use this in any
shared objects you distribute.
Despite the nomenclature, "default" always means public ie;
available to be linked against from outside the shared object.
"protected" and "internal" are pretty useless in real-world usage
so the only other commonly used option will be "hidden". The
default if -fvisibility isn't specified is "default", i.e., make
every symbol public---this causes the same behavior as previous
versions of GCC.
A good explanation of the benefits offered by ensuring ELF symbols
have the correct visibility is given by "How To Write Shared
Libraries" by Ulrich Drepper (which can be found at
<http://people.redhat.com/~drepper/>)---however a superior solution
made possible by this option to marking things hidden when the
default is public is to make the default hidden and mark things
public. This is the norm with DLL's on Windows and with
-fvisibility=hidden and "__attribute__ ((visibility("default")))"
instead of "__declspec(dllexport)" you get almost identical
semantics with identical syntax. This is a great boon to those
working with cross-platform projects.
For those adding visibility support to existing code, you may find
#pragma GCC visibility of use. This works by you enclosing the
declarations you wish to set visibility for with (for example)
#pragma GCC visibility push(hidden) and #pragma GCC visibility pop.
Bear in mind that symbol visibility should be viewed as part of the
API interface contract and thus all new code should always specify
visibility when it is not the default ie; declarations only for use
within the local DSO should always be marked explicitly as hidden
as so to avoid PLT indirection overheads---making this abundantly
clear also aids readability and self-documentation of the code.
Note that due to ISO C++ specification requirements, operator new
and operator delete must always be of default visibility.
Be aware that headers from outside your project, in particular
system headers and headers from any other library you use, may not
be expecting to be compiled with visibility other than the default.
You may need to explicitly say #pragma GCC visibility push(default)
before including any such headers.
extern declarations are not affected by -fvisibility, so a lot of
code can be recompiled with -fvisibility=hidden with no
modifications. However, this means that calls to extern functions
with no explicit visibility will use the PLT, so it is more
effective to use __attribute ((visibility)) and/or #pragma GCC
visibility to tell the compiler which extern declarations should be
treated as hidden.
Note that -fvisibility does affect C++ vague linkage entities. This
means that, for instance, an exception class that will be thrown
between DSOs must be explicitly marked with default visibility so
that the type_info nodes will be unified between the DSOs.
An overview of these techniques, their benefits and how to use them
is at <http://gcc.gnu.org/wiki/Visibility>.
===================================
ld命令選項
-E
--export-dynamic
--no-export-dynamic
When creating a dynamically linked executable, using the -E option
or the --export-dynamic option causes the linker to add all symbols
to the dynamic symbol table. The dynamic symbol table is the set
of symbols which are visible from dynamic objects at run time.
If you do not use either of these options (or use the
--no-export-dynamic option to restore the default behavior), the
dynamic symbol table will normally contain only those symbols which
are referenced by some dynamic object mentioned in the link.
If you use "dlopen" to load a dynamic object which needs to refer
back to the symbols defined by the program, rather than some other
dynamic object, then you will probably need to use this option when
linking the program itself.
You can also use the dynamic list to control what symbols should be
added to the dynamic symbol table if the output format supports it.
See the description of --dynamic-list.
Note that this option is specific to ELF targeted ports. PE
targets support a similar function to export all symbols from a DLL
or EXE; see the description of --export-all-symbols below.
--dynamic-list=dynamic-list-file
Specify the name of a dynamic list file to the linker. This is
typically used when creating shared libraries to specify a list of
global symbols whose references shouldn't be bound to the
definition within the shared library, or creating dynamically
linked executables to specify a list of symbols which should be
added to the symbol table in the executable. This option is only
meaningful on ELF platforms which support shared libraries.
The format of the dynamic list is the same as the version node
without scope and node name. See VERSION for more information.
--dynamic-list-data
Include all global data symbols to the dynamic list.
--version-script=version-scriptfile
Specify the name of a version script to the linker. This is typically used when creating shared libraries to
specify additional information about the version hierarchy for the library being created. This option is only
fully supported on ELF platforms which support shared libraries; see VERSION. It is partially supported on PE
platforms, which can use version scripts to filter symbol visibility in auto-export mode: any symbols marked
local in the version script will not be exported.
#VERSION 文件格式可以參考這里 http://sourceware.org/binutils/docs-2.20/ld/VERSION.html#VERSION
=========================================
測試看看:
-----------------------main.c -----------------------------------
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<dlfcn.h>
int test (int i)
{
printf("i=%d\n" ,i);
}
int main()
{
int (*test2)(int);
int (*test3)(int);
int *handler;
handler=(int*)dlopen("test.so", RTLD_LAZY);
if (!handler) {
printf( "加載模塊錯誤 %s\n", dlerror() );
return;
}
test3 = dlsym(handler, "test3");
if (test3) test3(3);
test2 = dlsym(handler, "test2");
if (test2) test2(2);
dlclose(handler);
exit(0);
}
------------------------------------------------------------------
$ gcc -rdynamic main.c -o main -ldl
$ readelf -s main
Symbol table '.dynsym' contains 25 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 00000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
2: 00000000 0 NOTYPE WEAK DEFAULT UND _Jv_RegisterClasses
3: 00000000 0 FUNC GLOBAL DEFAULT UND (2)
4: 00000000 0 FUNC GLOBAL DEFAULT UND (3)
5: 00000000 0 FUNC GLOBAL DEFAULT UND (2)
6: 00000000 0 FUNC GLOBAL DEFAULT UND (4)
7: 00000000 0 FUNC GLOBAL DEFAULT UND (3)
8: 00000000 0 FUNC GLOBAL DEFAULT UND (2)
9: 00000000 0 FUNC GLOBAL DEFAULT UND (3)
10: 0804a020 0 NOTYPE GLOBAL DEFAULT 24 __data_start
11: 0804a028 22 OBJECT GLOBAL DEFAULT 24 str
12: 0804a048 0 NOTYPE GLOBAL DEFAULT ABS _end
13: 0804a040 0 NOTYPE GLOBAL DEFAULT ABS _edata
14: 0804a020 0 NOTYPE WEAK DEFAULT 24 data_start
15: 080486b0 0 FUNC GLOBAL DEFAULT 14 _start
16: 080488f8 4 OBJECT GLOBAL DEFAULT 16 _fp_hw
17: 080488fc 4 OBJECT GLOBAL DEFAULT 16 _IO_stdin_used
18: 08048850 90 FUNC GLOBAL DEFAULT 14 __libc_csu_init
19: 0804a040 0 NOTYPE GLOBAL DEFAULT ABS __bss_start
20: 08048764 28 FUNC GLOBAL DEFAULT 14 test ///test 在這里導出了
21: 08048780 178 FUNC GLOBAL DEFAULT 14 main
22: 080485ec 0 FUNC GLOBAL DEFAULT 12 _init
23: 08048840 5 FUNC GLOBAL DEFAULT 14 __libc_csu_fini
24: 080488dc 0 FUNC GLOBAL DEFAULT 15 _fini
----------------------------------------------------------------------------------
$ gcc main.c -o main -ldl
$ readelf -s main
Symbol table '.dynsym' contains 11 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 00000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
2: 00000000 0 NOTYPE WEAK DEFAULT UND _Jv_RegisterClasses
3: 00000000 0 FUNC GLOBAL DEFAULT UND (2)
4: 00000000 0 FUNC GLOBAL DEFAULT UND (3)
5: 00000000 0 FUNC GLOBAL DEFAULT UND (2)
6: 00000000 0 FUNC GLOBAL DEFAULT UND (4)
7: 00000000 0 FUNC GLOBAL DEFAULT UND (3)
8: 00000000 0 FUNC GLOBAL DEFAULT UND (2) ///沒有把 test函數導出
9: 00000000 0 FUNC GLOBAL DEFAULT UND (3)
10: 080486fc 4 OBJECT GLOBAL DEFAULT 16 _IO_stdin_used
====================================================================
$ gcc -rdynamic -fPIC main.c -o main -ldl
$ readelf -s main
可以看到有 '.dynsym' 節有把函數導出
=======================================================================
------test.c---------------------
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
extern int test (int i);
int test2 (int i)
{
test(i);
printf("this is test2\n");
}
int test3 (int i)
{
printf("this is test 3\n");
}
-----------------------------------------------
$ gcc -shared -fPIC test.c -o test.so
$ readelf -s test.so
'.dynsym' 節里把所有函數導出
====================================================================
$ ./main
加載模塊錯誤 test.so: cannot open shared object file: No such file or directory
找不到 test.so文件,他不會自動在當前目錄下搜索啊。
那就設置好這個LD_LIBRARY_PATH這個環境變量吧,我是不想復制到系統目錄去了
$ echo $LD_LIBRARY_PATH
$ export LD_LIBRARY_PATH=`pwd`
$ echo $LD_LIBRARY_PATH
/home/widebright/桌面/測試用例
--------------------------
gcc -rdynamic -fPIC main.c -o main -ldl
gcc -shared -fPIC test.c -o test.so
使用上面編譯選線,結果
$ ./main
this is test 3
i=2
this is test2
--------------------------------
gcc -fPIC main.c -o main -ldl
gcc -shared -fPIC test.c -o test.so
使用上面編譯選線,結果test.so就找不到 main中export出來的test函數了
$ ./main
this is test 3
./main: symbol lookup error: /home/widebright/桌面/測試用例/test.so: undefined symbol: test
--------------------------------
gcc -rdynamic main.c -o main -ldl
gcc -shared -fPIC test.c -o test.so
使用上面編譯選線,結果還是可以執行,說明 -fPIC對程序來說好像用處不大。
$ ./main
this is test 3
i=2
this is test2
------------------------------------------------
gcc -rdynamic main.c -o main -ldl
gcc -shared test.c -o test.so
使用上面編譯選線,也還能正確運行 -fPIC 有什么作用啊,不明顯
$ ./main
this is test 3
i=2
this is test2
---------------------------------------------
----visibility.txt------------------
{
global:
test3;
local: *;
};
-------------------
gcc -rdynamic main.c -o main -ldl
gcc -shared test.c -o test.so -Wl,--version-script=visibility.txt
$ gcc -shared test.c -o test.so -Wl,--version-script=visibility.txt
$ ./main
this is test 3
可以看到 使用--version-script=visibility.txt 參數控制之后,只有test3才被導出了
:~/桌面/測試readelf -s test.so
Symbol table '.dynsym' contains 7 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 00000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
2: 00000000 0 NOTYPE WEAK DEFAULT UND _Jv_RegisterClasses
3: 00000000 0 NOTYPE GLOBAL DEFAULT UND test
4: 00000000 0 FUNC GLOBAL DEFAULT UND (2)
5: 00000000 0 FUNC WEAK DEFAULT UND (3)
6: 0000041b 20 FUNC GLOBAL DEFAULT 12 test3 //visibility.txt文件里面指定了 test3才導出了, test2不再導出了
======================================
gcc -shared test.c -o test.so -fvisibility=hidden
----當 -fvisibility=hidden 設置為hidden之后,所有的都函數不再導出了。
readelf -s test.so
Symbol table '.dynsym' contains 11 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 00000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
2: 00000000 0 NOTYPE WEAK DEFAULT UND _Jv_RegisterClasses
3: 00000000 0 NOTYPE GLOBAL DEFAULT UND test ///這個還是會在這里,知道是外部函數來的
4: 00000000 0 FUNC GLOBAL DEFAULT UND (2)
5: 00000000 0 FUNC WEAK DEFAULT UND (3)
6: 0000202c 0 NOTYPE GLOBAL DEFAULT ABS _end
7: 00002024 0 NOTYPE GLOBAL DEFAULT ABS _edata
8: 00002024 0 NOTYPE GLOBAL DEFAULT ABS __bss_start
9: 0000035c 0 FUNC GLOBAL DEFAULT 10 _init
10: 000004e8 0 FUNC GLOBAL DEFAULT 13 _fini
==============================================
我們給要導出到函數加上 屬性控制,改成這樣,
----------test.c-----------------
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
extern int test (int i);
__attribute ((visibility)) int test2 (int i)
{
test(i);
printf("this is test2\n");
}
__attribute ((visibility)) int test3 (int i)
{
printf("this is test 3\n");
}
-------------------------------
$ gcc -shared test.c -o test.so -fvisibility=hidden
$
$
$ readelf -s test.so
Symbol table '.dynsym' contains 13 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 00000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
2: 00000000 0 NOTYPE WEAK DEFAULT UND _Jv_RegisterClasses
3: 00000000 0 NOTYPE GLOBAL DEFAULT UND test
4: 00000000 0 FUNC GLOBAL DEFAULT UND (2)
5: 00000000 0 FUNC WEAK DEFAULT UND (3)
6: 0000202c 0 NOTYPE GLOBAL DEFAULT ABS _end
7: 00002024 0 NOTYPE GLOBAL DEFAULT ABS _edata
8: 000004bc 31 FUNC GLOBAL DEFAULT 12 test2 //這次就會導出了
9: 00002024 0 NOTYPE GLOBAL DEFAULT ABS __bss_start
10: 000003a0 0 FUNC GLOBAL DEFAULT 10 _init
11: 00000528 0 FUNC GLOBAL DEFAULT 13 _fini
12: 000004db 20 FUNC GLOBAL DEFAULT 12 test3
$ ./main
this is test 3
i=2
this is test2
-----------------------------------------------------
總結:
通過gcc命令的-fvisibility=hidden 選項和 "__attribute__ ((visibility("default")))" 語法擴展 可以得到 vc中
__declspec(dllexport)"的效果。
采用ld的 --version-script=version-scriptfile 參數 類似vc中到 *.DEF 文件,也可以用來統一鏈接庫到輸出與否。
測完才發現,gcc幫助文檔提到的 “How To Write Shared Libraries” 這篇文章上面解釋的更完整一些,
一個pdf文檔,在google搜索一下就可以找到了。
其他的辦法還包括:
1. static 定義函數
2、libtool的-export-symbols參數
$ libtool --mode=link gcc -o libfoo.la \
foo.lo -export-symbols=foo.sym
3.使用alias屬性來隱藏具體的函數名
int next (void) {
return ++last_int;
}
extern __typeof (next) next_int
__attribute ((alias ("next"),
visibility ("hidden")));