linux動態庫編譯和使用詳細剖析 - 後續

来源:https://www.cnblogs.com/life2refuel/archive/2018/04/01/8687841.html
-Advertisement-
Play Games

動態庫後續補充, 本身內容有點多, 這裡簡單分享一下. 希望有魚漁 : ) ...


引言 - 也許是修行

  很久以前寫過關於動態庫科普文章, 廢話反正是說了好多. 核心就是在 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

      青春往事 - 龍泉寺 - 鳳凰嶺 

  (記 龍泉寺 : )


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 給定一個有序數組,你需要原地刪除其中的重覆內容,使每個元素只出現一次,並返回新的長度。 不要另外定義一個數組,您必須通過用 O(1) 額外記憶體原地修改輸入的數組來做到這一點。 個人代碼,較為弱智。 class Solution {public: int removeDuplicates(vector ...
  • 一直來,都是使用Vivado中自帶的GMIItoRGMII IP核來完成GMII轉RGMII的功能;儘管對GMII及RGMII協議都有一定的瞭解,但從沒用代碼實現過其功能。由於使用IP時,會涉及到MDIO配置IP寄存器的問題,覺得麻煩。因此決定用代碼實現GMII轉RGMII的功能。 參考Lattic ...
  • 在做JavaWeb的SSH框架開發的時候,遇到過很多的細節問題,這裡大概記錄下 我使用的IDE是Eclipse(老版本)三大框架:Spring4、Struts2、Hibernate5 1.web.xml的配置 1.ContextLoaderListener的作用: ContextLoaderList ...
  • 用 Go 編寫一個簡單的 WebSocket 推送服務 本文中代碼可以在 "github.com/alfred zhong/wserver" 獲取。 背景 最近拿到需求要在網頁上展示報警信息。以往報警信息都是通過簡訊,微信和 App 推送給用戶的,現在要讓登錄用戶在網頁端也能實時接收到報警推送。 依 ...
  • 項目地址 : https://github.com/racaljk/yvm 虛擬機現在已可運行(不過還有很多待發現待修複的bugs),已支持語言特性有: + Java基本算術運算,流程式控制制語句,面向對象。 + RTTI + 字元串拼接(+,+=符號重載) + 異常處理 詳細內容請參見項目 "READ ...
  • PHP不支持的高級OPP特性 對象克隆 克隆實例 __clone()方法 繼承 類繼承 繼承和構造函數 繼承與延遲靜態綁定 介面 實現一個介面 實現多個介面 抽象類 命名空間介紹 ...
  • 前言 隨著spring boot2.0的發佈。項目組的API介面已經考慮向spring boot轉型。底層介面我們一直用的mybatis,所以這篇文章我特意練習了下在spring boot種集成mybatis。 一、準備工作 1、pom.xml 2、項目結構 配置文件依然放在resources目錄下 ...
  • 介紹: Mybatis-Plus(簡稱MP)是一個 Mybatis 的增強工具,在 Mybatis 的基礎上只做增強不做改變,為簡化開發、提高效率而生。(摘自mybatis-plus官網)Mybatis雖然已經給我們提供了很大的方便,但它還是有不足之處,MP的存在就是為了稍稍彌補Mybatis的不足 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...