動態庫後續補充, 本身內容有點多, 這裡簡單分享一下. 希望有魚漁 : ) ...
引言 - 也許是修行
很久以前寫過關於動態庫科普文章, 廢話反正是說了好多. 核心就是在 linux 上面玩了一下 dlopen : )
linux動態庫編譯和使用詳細剖析 - https://www.cnblogs.com/life2refuel/p/5332358.html
本文是上面文章的補充部分. 因為單純的 linux 玩還是不太通用 ~
動態庫最簡單理解是為瞭解決操作系統級別的代碼復用出現的技術. 現在伺服器開發技術中,
幾乎不再出現. 首先不好用, 其次多環境中常容易出錯. 繁榮期應該在上古時代(2000-2005), 動態庫技術
是一個很考驗程式員的修養的基本功. (當前伺服器主流是靜態庫, 客戶端應該還是被動態庫統治) 這裡不妨扯一些
Windows 和 Unix 下動態鏈接庫的區別 https://blog.codingnow.com/2006/11/windows_unix_dynamic_library.html
(沒想到, 當年雲風, 也會被上古的 winds 老前輩們 摩擦摩擦 ~.~ )
想深入瞭解動態庫原理, 可以多看幾遍 <<程式員自我修養>> and <<高級C/C++ 編譯技術>> : )
雖然看完沒什麼暖用. 但也可以解悶(特別是後面那本小冊子)不是嗎 ?
那開始代碼之旅, 不來虛的 ~
前言 - 準備測試環境
動態庫當你面試時候碰到的話, 實在答不上上來. 就別繼續說了概念了. 就簡單說我'不會', 但我會寫會用的很溜.
也許能到 61 分吧. 把這篇文章代碼手打出來 : ) 咱們就玩實心的.
先看編譯模塊 Makefile
main.exe: gcc -fPIC -O2 -Wall -shared -o foo.dll foo.c gcc -g -Wall -O2 -o main.exe main.c dllso.c -ldl clean: -rm -rf *.o *.so *.exe *.out *.dll
待編譯成動態庫的文件 foo.h foo.c
#ifndef _F_FOO #define _F_FOO #include <stdio.h> #ifndef extern # if defined(_MSC_VER) # define extern extern __declspec(dllexport) # else # define extern extern # endif #endif//extern extern void * foo(int hoge); #undef extern #endif//_F_FOO
這裡構建的 extern 巨集, 是不是很飄. 用於解決 cl 和 gcc 對於動態庫導出約束不一樣.
cl 預設沒有 __declspec(dllexport) 就不導出. gcc 預設全部導出, __attribute__((visibility("hidden"))) 可以設置不可見.
#include "foo.h" void * foo(int hoge) { static int _id; ++_id; // 簡單自增長 printf("foo(%d) = %d\n", hoge, _id); return &_id; }
實現沒有什麼好說的.
其中動態庫協助介面設計 dllso.h
#ifndef _H_DLLSO #define _H_DLLSO // // ds_create - 構造載入動態庫文件 // path : 動態庫文件路徑 // return : 失敗返回 NULL // extern void * ds_create(const char * path); // // ds_parse - 解析動態庫文件, 返回執行函數 // so : 動態庫對象 // name : 待解析的函數名稱 // return : 返回解析的函數地址, NULL 是失敗 // extern void * ds_parse(void * so, const char * name); // // ds_delete - 釋放卸載動態庫文件 // so : 動態庫對象 // return : void // extern void ds_delete(void * so); #endif//_H_DLLSO
最終測試文件 main.c
#include "dllso.h" #include <stdio.h> #include <stdlib.h> #define _STR_FOO "./foo.dll" // 簡單動態庫測試 int main(int argc, char * argv[]) { void * so = ds_create(_STR_FOO); if (NULL == so) { fprintf(stderr, "ds_create %s err", _STR_FOO); exit(EXIT_FAILURE); } void * (* foo)(int) = ds_parse(so, "foo"); if (NULL == foo) { fprintf(stderr, "ds_parse err so = %p\n", so); exit(EXIT_FAILURE); } printf("foo() = %p\n", foo(0)); ds_delete(so); return EXIT_SUCCESS; }
到這裡希望讀者理解作者的思路. 動態庫處理劃分為三部分, 裝載, 解析, 卸載.
這裡扯淡一點, 對於動態庫設計處理. winds 離不開 LoadLibrary 函數簇. linux 離不開 dlopen 函數簇.(自行科普)
當前測試核心思路就在 dllso.c
#include "dllso.h" #if defined(_MSC_VER) # include <windows.h> #define RTLD_LAZY LOAD_WITH_ALTERED_SEARCH_PATH #define dlopen(filename, flags) LoadLibraryEx(filename, NULL, flags) #define dlsym(handle, symbol) GetProcAddress(handle, symbol) #define dlclose(handle) FreeLibrary(handle) #else # include <dlfcn.h> #endif // // ds_create - 構造載入動態庫文件 // path : 動態庫文件路徑 // return : 失敗返回 NULL // inline void * ds_create(const char * path) { return dlopen(path, RTLD_LAZY); } // // ds_parse - 解析動態庫文件, 返回執行函數 // so : 動態庫對象 // name : 待解析的函數名稱 // return : 返回解析的函數地址, NULL 是失敗 // inline void * ds_parse(void * so, const char * name) { return dlsym(so, name); } // // ds_delete - 釋放卸載動態庫文件 // so : 動態庫對象 // return : void // inline void ds_delete(void * so) { if (so) { dlclose(so); } }
核心思路是在 winds 上面採用最小的代價構建了一個 linux dlopen 操作三部曲.
實現的很一般般. 或者說不痛快, 勝在可用, 代價小. 推薦喜歡但基礎一般的朋友可以練習練習多寫寫.
文章到這裡也快尾聲了, 算 Over ~
正文 - 原地躊躇, 也走到了 中年
人生的修行, 那麼讓人不可捉摸. 心無旁貸, 好想有機會再能看看大山中柿子樹. 甜甜澀澀的 ~
很幸運你讀到這裡. 不妨帶大家看看 libuv 怎麼封裝 dll 的. 總的思路都一樣, winds 向著 *nix 靠攏. 寫的很平穩厚實.
先看基礎部分 (dl.c, uv.h, internal.h)
/* Platform-specific definitions for uv_dlopen support. */ #define UV_DYNAMIC FAR WINAPI typedef struct { HMODULE handle; char* errmsg; } uv_lib_t;
static void uv__format_fallback_error(uv_lib_t* lib, int errorno){ DWORD_PTR args[1] = { (DWORD_PTR) errorno }; LPSTR fallback_error = "error: %1!d!"; FormatMessageA(FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ARGUMENT_ARRAY | FORMAT_MESSAGE_ALLOCATE_BUFFER, fallback_error, 0, 0, (LPSTR) &lib->errmsg, 0, (va_list*) args); }
static int uv__dlerror(uv_lib_t* lib, const char* filename, DWORD errorno) { DWORD_PTR arg; DWORD res; char* msg; if (lib->errmsg) { LocalFree(lib->errmsg); lib->errmsg = NULL; } if (errorno == 0) return 0; res = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, errorno, MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), (LPSTR) &lib->errmsg, 0, NULL); if (!res && GetLastError() == ERROR_MUI_FILE_NOT_FOUND) { res = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, errorno, 0, (LPSTR) &lib->errmsg, 0, NULL); } if (res && errorno == ERROR_BAD_EXE_FORMAT && strstr(lib->errmsg, "%1")) { msg = lib->errmsg; lib->errmsg = NULL; arg = (DWORD_PTR) filename; res = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_ARGUMENT_ARRAY | FORMAT_MESSAGE_FROM_STRING, msg, 0, 0, (LPSTR) &lib->errmsg, 0, (va_list*) &arg); LocalFree(msg); } if (!res) uv__format_fallback_error(lib, errorno); return -1; }
uv__dlerror 是構造 linux dlerror 前戲.
作者設計的意圖是圍繞 winds FormatMessageA api 錯誤處理用法包裝.
寫的挺漂亮的. 其實還有更好更偷懶的實現方式, 參照基礎項目 structc 中的
stderr https://github.com/wangzhione/structc/blob/master/structc/system/stderr.c
winds 實現 strerror 函數. 用於解決 GetLastError 和 FormatMessage 配合的不爽.
libuv 寫的真好, 不知道 niginx 源碼是什麼水平, 有機會研究一下.一塊分享.
隨後的代碼很輕鬆和標準
int uv_dlopen(const char* filename, uv_lib_t* lib) { WCHAR filename_w[32768]; lib->handle = NULL; lib->errmsg = NULL; if (!MultiByteToWideChar(CP_UTF8, 0, filename, -1, filename_w, ARRAY_SIZE(filename_w))) { return uv__dlerror(lib, filename, GetLastError()); } lib->handle = LoadLibraryExW(filename_w, NULL, LOAD_WITH_ALTERED_SEARCH_PATH); if (lib->handle == NULL) { return uv__dlerror(lib, filename, GetLastError()); } return 0; } void uv_dlclose(uv_lib_t* lib) { if (lib->errmsg) { LocalFree((void*)lib->errmsg); lib->errmsg = NULL; } if (lib->handle) { /* Ignore errors. No good way to signal them without leaking memory. */ FreeLibrary(lib->handle); lib->handle = NULL; } } int uv_dlsym(uv_lib_t* lib, const char* name, void** ptr) { *ptr = (void*) GetProcAddress(lib->handle, name); return uv__dlerror(lib, "", *ptr ? 0 : GetLastError()); } const char* uv_dlerror(const uv_lib_t* lib) { return lib->errmsg ? lib->errmsg : "no error"; }
其中 uv_dlsym 思路很漂亮. 返回 int 錯誤類型. 可惜也不是線程安全的.
其中用到的幾個巨集翻譯如下
#define CP_UTF7 65000 // UTF-7 translation #define CP_UTF8 65001 // UTF-8 translation #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
其中 libuv uv_dlclose 實現行為和 linux 原生的 dlclose 原生行為基本一致, 沒有對 NULL 判斷.
讓使用的朋友自己維護 NULL這塊性能. 總體而言 libuv dl.c 實現的很舒服.
關於動態庫話題簡單的講到這裡了. 希望總是要有的 ~ ~
後記 - 歡迎指正, 感謝閱讀, 錯誤是難免的.
青春往事 - http://music.163.com/m/song?id=528723987&userid=16529894
(記 龍泉寺 : )