引言
重點講述linux上使用gcc編譯動態庫的一些操作.並且對其深入的案例分析.
最後介紹一下動態庫插件技術, 讓代碼向後兼容.關於linux上使用gcc基礎編譯,
預編譯,編譯,生成機械碼最後鏈接輸出可執行文件流程參照下面.
gcc編譯流程 http://www.jb51.net/article/46407.htm
而本文重點是分析動態庫相關的知識點. 首先看需要用到的測試素材
heoo.h
#ifndef _H_HEOO #define _H_HEOO /* * 測試接口,得到key內容 * : 返回key的字符串 */ extern const char* getkey(void); /* * 測試接口,得到value內容 * arg : 傳入的參數 * : 返回得到的結果 */ extern void* getvalue(void* arg); #endif // !_H_HEOO
heoo-getkey.c
#include "heoo.h"
/*
* 測試接口,得到key內容
* : 返回key的字符串
*/
const char*
getkey(void) {
return "heoo-getkey.c getkey";
}
heoo-getvalue.c
#include "heoo.h"
#include <stdio.h>
/*
* 測試接口,得到value內容
* arg : 傳入的參數
* : 返回得到的結果
*/
const void*
getvalue(void* arg) {
const char* key = "heoo-getvalue.c getvalue";
printf("%s - %s\n", key, (void*)arg);
return key;
}
heoo.c
#include "heoo.h"
#include <stdio.h>
/*
* 測試接口,得到key內容
* : 返回key的字符串
*/
const char*
getkey(void) {
return "heoo.c getkey";
}
/*
* 測試接口,得到value內容
* arg : 傳入的參數
* : 返回得到的結果
*/
const void*
getvalue(void* arg) {
const char* key = "heoo.c getvalue";
printf("%s - %s\n", key, (char*)arg);
return key;
}
main.c
#include <stdio.h>
#include "heoo.h"
// 測試邏輯主函數
int main(int argc, char* argv[]) {
// 簡單的打印數據
printf("getkey => %s\n", getkey());
getvalue(NULL);
return 0;
}
到這裡也許感覺有點臃腫, 但是理解為什麼是必要的. 會讓你對於動態庫高度高上0.01毫米的.哈哈.
先讓上面代碼跑起來.
gcc -g -Wall -o main.out main.c heoo.c
測試結果如下

測試完成,那就開始靜態庫到動態庫擴展之旅.
前言
從靜態庫說起來
首先參照下面編譯語句
gcc -c -o heoo-getkey.o heoo-getkey.c gcc -c -o heoo-getvalue.o heoo-getvalue.c
對於靜態庫創建本質就是打包. 所以用linux上一個 ar創建靜態庫壓縮命令.詳細用法可以看
ar詳細用法參照 http://blog.163.com/xychenbaihu@yeah/blog/static/132229655201121093917552/
那麼我們開始制作靜態庫
ar rcs libheoo.a heoo-getvalue.o heoo-getkey.o
那麼我們采用靜態庫執行編譯上面main.c 函數
gcc -g -Wall -o main.out main.c -L. -lheoo
運行的截圖如下

運行一切正常. 對於靜態庫編譯 簡單說明一下. ar 後面的 rcs表示 替換創建和添加索引. 具體的看上面的網址.
後面gcc中 -L表示查找庫的目錄, -l表示搜索的 libheoo庫. 還有其它的-I表示查找頭文件地址, -D表示添加全局宏.......
對於上面靜態庫編譯還有一種方式如下
gcc -g -Wall -o main.out main.c libheoo.a
執行結果也是一樣的.可以將 *.a 理解成多個 *.o合體.
好到這裡前言就說完了.那我們開始說正題動態庫了.
正文
動態庫的構建和使用
動態庫構建命名如下,仍然以heoo.c heoo.h 為例
gcc -shared -fPIC -o libheoo.so heoo.c
開始編譯代碼 先介紹一種最簡單的有點類似上面靜態庫最後一種方式.
gcc -g -Wall -o main.out main.c ./libheoo.so
這裡是顯式編譯. 結果如下

對於 上面編譯 動態庫的時候如果 直接使用 libheoo.so. 例如
gcc -g -Wall -o main.out main.c libheoo.so
如果沒有配置動態庫路徑, 查找動態庫路徑會出問題. 這裡就不復現了(因為我把環境調好了). 會面會給出解決辦法.
下面說libheoo.so 標准的使用方式
gcc -g -Wall -o main.out main.c -L. -lheoo
運行結果如下

上面是個常見錯誤, 系統找不見動態庫在那. 需要配置一下, 再編譯參照如下
export LD_LIBRARY_PATH="$LD_LIBRARY_PATH;./" gcc -g -Wall -o main.out main.c -L. -lheoo
上面第一句話是在當前會話層. 添加庫查找路徑,包含當前文件目錄.這個會話層關閉了就失效了. Linux上shell確實很重要. 現在執行結果

到這裡動態庫的也都完畢了. 一切正常.
一個奇巧淫技
問: gcc -l 鏈接一個庫的時候,但是庫中存在同名的靜態庫和動態庫. 會鏈接到那個庫?
通過上面的那麼多測試應該知道是動態庫吧,因為使用動態庫會報錯.使用靜態庫沒有事.
那麼問題來了, 我想使用靜態庫怎麼辦.
-static
上面gcc 選項可以幫助我們強制鏈接靜態庫!
動態庫的顯示使用
到這裡基本上是重頭戲了. 扯一點,這些知識點在window也一樣知識環境變了,設置變了.鏈接編譯顯式加載都有的. 下面是重新操作的代碼.
heooso.c
#include <stdio.h>
#include <dlfcn.h>
#define _STR_PATH "./libheoo.so"
// 顯示調用動態庫, 需要 -ldl 鏈接程序庫
int main(int argc, char* argv[]) {
const char* (*getkey)(void);
const void* (*getvalue)(void* arg);
/*
* 對於dlopen 函數第二個參數
* RTLD_NOW:將共享庫中的所有函數加載到內存
* RTLD_LAZY:會推後共享庫中的函數的加載操作,直到調用dlsym()時方加載某函數
*/
void* handle = dlopen(_STR_PATH, RTLD_LAZY);
// 下面得到錯誤信息,是一種小心的變成方式,每次都檢測一下錯誤是否存在
const char* err = dlerror();
if(!handle || err) {
fprintf(stderr, "dlopen " _STR_PATH " no open! err = %s\n", err);
return -1;
}
getkey = dlsym(handle, "getkey");
if((err = dlerror())){
fprintf(stderr, "getkey err = %s\n", err);
dlclose(handle);
return -2;
}
puts(getkey());
//這種顯式調用dll代碼,很不安全代碼注入太簡單了
getvalue = dlsym(handle, "getvalue");
if((err = dlerror())){
fprintf(stderr, "getvalue err = %s\n", err);
dlclose(handle);
return -3;
}
puts(getvalue(NULL));
dlclose(handle);
return 0;
}
編譯代碼
gcc -g -Wall -o heooso.out heooso.c -ldl
測試結果截圖如下

運行一切正常. 功能是實現了.但是大家千萬別這麼用.否則還是比較危險的.也是一種編程思路吧.後面
後記會寫一個向後兼容的插件機制. 大家可以觀摩一下. 方便更深入的了解Linux系統開發.算是一個簡易的
Linux運用插件技術的小項目吧.
後記
錯誤是難免的,歡迎吐槽. 最後獻上一個linux上如何通過動態庫運行時加載插件的案例.麻雀雖小,五髒俱全.
Makefile
CC = gcc
DEBUG = -g -Wall
LIB = -ldl
RUNSO = $(CC) -fPIC -shared -o $@ $^
RUN = $(CC) $(DEBUG) -o $@ $^
#總的任務
all:libheoo.so libheootwo.so libheoothree.so main.out
#簡單lib%.so生成
libheoo.so:heoo.c
$(RUNSO)
libheootwo.so:heootwo.c
$(RUNSO)
libheoothree.so:heoothree.c
$(RUNSO)
#生成的主要內容
main.out:main.c
$(RUN) $(LIB)
# 簡單的清除操作 make clean
.PHONY:clean
clean:
rm -rf *.so *.s *.i *.o *.out *~ ; ls -hl
heoo.h
#ifndef _H_HEOO #define _H_HEOO /* * 測試接口,得到key內容 * : 返回key的字符串 */ extern const char* getkey(void); /* * 測試接口,得到value內容 * arg : 傳入的參數 * : 返回得到的結果 */ extern const void* getvalue(void* arg); #endif // !_H_HEOO
heootwo.c
#include "heoo.h"
#include <stdio.h>
/*
* 測試接口,得到key內容
* : 返回key的字符串
*/
const char*
getkey(void) {
return "heootwo.c getkey";
}
/*
* 測試接口,得到value內容
* arg : 傳入的參數
* : 返回得到的結果
*/
const void*
getvalue(void* arg) {
const char* key = "heootwo.c getvalue";
printf("%s - %s\n", key, (char*)arg);
return key;
}
heoothree.c
#include "heoo.h"
#include <stdio.h>
/*
* 測試接口,得到key內容
* : 返回key的字符串
*/
const char*
getkey(void) {
return "heoothree.c getkey";
}
/*
* 測試接口,得到value內容
* arg : 傳入的參數
* : 返回得到的結果
*/
const void*
getvalue(void* arg) {
const char* key = "heoothree.c getvalue";
printf("%s - %s\n", key, (char*)arg);
return key;
}
main.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <errno.h>
#include <unistd.h>
#include <dirent.h>
#include <dlfcn.h>
//塞入的句柄數
#define _INT_HND (3)
// 最多支持108個插件
#define _INT_LEN (108)
// 文件路徑最大長度
#define _INT_BUF (512)
// 處理dll,並且將返回的數據保存在a[_INT_HND]中, 這個數組長度必須是
bool dll_add(void* a[], const char* dllpath);
// 處理指定目錄得到結果塞入a中, nowpath為NULL表示當前目錄
int dll_new(void* a[][_INT_HND], int len, const char* nowpath);
// 釋放資源
void dll_del(void* a[][_INT_HND], int len);
/*
* 動態加載機制
*/
int main(int argc, char* argv[]) {
int idx, len, i;
void* a[_INT_LEN][_INT_HND];
// 當前目錄下,處理結果
len = dll_new(a, _INT_LEN, NULL);
if(len == 0){
fprintf(stderr, "感謝使用,沒有發現合法插件內容!\n");
exit(1);
}
//數據展示
puts("------------------------------ 歡迎使用main插件 ----------------------------------");
for(i=0; i<len; ++i){
const char* (*getkey)(void) = a[i][1];
printf(" %d => %s\n", i, getkey());
}
printf(" 請輸入 待執行的 索引[0, %d)\n", len);
if(scanf("%d", &idx)!=1 || idx<0 || idx >= len){
puts(" fake 錯誤的命令,程序退出中!");
goto __exit;
}
puts(" 執行結果如下:");
const void* (*getvalue)(void* arg) = a[idx][2];
puts(getvalue(NULL));
__exit:
puts("------------------------------ 謝謝使用main插件 ----------------------------------");
dll_del(a, len);
return 0;
}
// 處理dll,並且將返回的數據保存在a[_INT_HND]中, 這個數組長度必須是
bool
dll_add(void* a[], const char* dllpath) {
const char* (*getkey)(void);
const void* (*getvalue)(void* arg);
void* handle = dlopen(dllpath, RTLD_LAZY);
// 下面得到錯誤信息,是一種小心的變成方式,每次都檢測一下錯誤是否存在
const char* err = dlerror();
if(!handle || err) return false;
getkey = dlsym(handle, "getkey");
if((err = dlerror())){
dlclose(handle);
return false;
}
//這種顯式調用dll代碼,很不安全代碼注入太簡單了
getvalue = dlsym(handle, "getvalue");
if((err = dlerror())){
dlclose(handle);
return false;
}
// 句柄, key, value, 協議訂的
a[0] = handle;
a[1] = getkey;
a[2] = getvalue;
return true;
}
// 處理指定目錄得到結果塞入a中, nowpath為NULL表示當前目錄
int
dll_new(void* a[][_INT_HND], int len, const char* nowpath){
int j = 0, rt;
DIR* dir;
struct dirent* ptr;
char path[_INT_BUF];
// 設置默認目錄
if(!nowpath || !*nowpath) nowpath = ".";
// 打開目錄信息
if((dir = opendir(nowpath)) == NULL) {
fprintf(stderr, "opendir open %s, error:%s\n", nowpath, strerror(errno));
exit(-1);
}
//挨個讀取文件
while(j<len && (ptr=readdir(dir))){
//只處理文件,包含未知文件
if(DT_BLK == ptr->d_type || DT_UNKNOWN == ptr->d_type){
rt = snprintf(path, _INT_BUF, "%s/%s", nowpath, ptr->d_name);// 只有確實是 *.so 文件才去出去運行
if(rt>3&&rt < _INT_BUF&&path[rt-1]=='o'&&path[rt-2]=='s'&&path[rt-3]=='.') {
// 添加數據 dao數組 a中
if(dll_add(a[j], path))
++j;
}
}
}
closedir(dir);
return j;
}
// 釋放資源
void
dll_del(void* a[][_INT_HND], int len) {
int i=-1;
while(++i < len)
dlclose(a[i][0]);
}
最後運行截圖

到這裡一個小demo就完工了. 關於Linux gcc上動態庫插件開發,剖析完畢.O(∩_∩)O哈哈~