Re: [問題] shared library interface design?

作者: cole945 (躂躂..)   2018-08-23 22:26:27
※ 引述《lovejomi (JOMI)》之銘言:
: 詢問一個撰寫c++ shared library (.so) 給 client使用的問題
: 看到一份code, .so 的header 提供的api prototype是長這樣
: std::unique_ptr<Foo> CreateFoo();
: 這很明顯的是
: allocate的動作在lib裡面
: deallocate的人一定在caller那端也就是client code
: a. 這是不是一個很不正確的design?
: 基於一個觀念
: 不該把new跟delete的動作, 在不同library間執行
: 之前觀念是因為heap是獨立的, 所以會出問題
下面再講 new/delete 和 heap 獨立的問題..
通常不會在 API 設計上綁死 object 是怎麼 allocate, 這樣會限縮以後
改變 memory management 的彈性.例如有些 runtime engine 會用自已的 memory pool,
不管中間有什麼 leak, 反正在 release runtime engine 後都就整個 pool 合收
但這樣不代表就不能用 unique_ptr. unique_ptr 只是提供 "自動" 呼叫 deletor
的方式, 但 deletor 還是會由你的 library 提供
foo *fp = createFoo();
releaseFoo(fp);
vs.
using foo_ptr = unique_ptr<foo, void(*)(foo*)>;
foo_ptr createFoo() {
return foo_ptr(createFooInternal(), releaseFoo);
}
foo_ptr fp = createFoo();
然後沒人用 fp 時幫你呼叫 releaseFoo(fp);
那不是大同小異嗎?
: b. 但這觀念是不是只有windows上才有呢? 不確定
: 以下連結也只提到dll
: https://stackoverflow.com/questions/443147/c-mix-new-delete-between-libs
跟 e. 一起回答
會有這個問題是因為 Windows DLL 和 Linux Shared Object 的 binding 時機不同
要 call 什麼 function, 在編 DLL 就決定了,
但對 shared object, 是在 runtime 才決定
(可以改這個行為, 但我們以下都以預設的行為討論)
想一下有段 code
executable: main.c libfoo.so: foo.c
______________________________________ _________________________________
1 #include <stdio.h> │ 1 #include <stdio.h>
2 │ 2
3 int g = 1000; │ 3 int g = 2000;
4 │ 4
5 void foo() { │ 5 void foo () {
6 printf ("main: foo %d\n", g); │ 6 printf ("foo: foo %d\n", g);
7 } │ 7 }
8 │ 8
9 void g_plus_plus(); │ 9 void g_plus_plus() {
10 void bar(); │ 10 g++;
11 │ 11 }
12 int main () { │ 12
13 foo(); │ 13 void bar() {
14 g_plus_plus(); │ 14 foo();
15 bar(); │ 15 }
16 } │
a.out 和 libfoo.so 都有提供 g 變數, 和 foo function,
那這段 code 會印出什麼結果?
在 Linux, 會印出
main: foo 1000
main: foo 1001
但在 Windows 會印出
main: foo 1000
foo: foo 2001
因編譯 DLL 時就決定 DLL 要使用 foo.c 裡的 definition
但 SO 在 runtime 決定要哪一個本版
Linux (或 FreeBSD 等) 基本上是走
System V Application Binary Interface
http://www.sco.com/developers/gabi/latest/ch5.dynamic.html
看 Shared Object Dependencies 那一節
細節有點複雜, 不過可以想成, runtime 時才決定 symbol 是哪本版本,
整個程式, 主程式本身和連帶的 SO 都會使用同一個版本,
若 executable 本身和 SO 都有提供, 以 executable 為主.
這樣的規範其實會讓 static link archive library 和 dynamic link so 時
的結果一樣
(當然 function / data 要切分到不同的 object file 避免 redefinition 的問題)
打太久差點忘了為什麼要寫這段 orz
所以在 Windows, 有分 single/multi-thread * debug/release 四種 vc runtime
.exe 和 .dll 用不同的 config 編會連到不同的版本, 兩邊會搭不起來
但 Linux 不會有這個問題
shared object 可以用 static, visilibity attribute,
或 linker 的 -Bsymbolic 等來達到類似 DLL 的行為,
但 DLL 應該是沒辦法做到 SO 這樣的行為 (至少我不知道做法)
: 針對這件事 我並沒有明確的google到有什麼guideline提到該怎麼設計
: 如果是以我經驗來說
: 多半是
: Foo* Create() 搭配 void Release(Foo*)
: 但這感覺比較偏向C, 而這樣好像也限制client端得到這個Foo*後無法用smart pointer去
: hold.
: 所以以我的想法會覺得
: 回傳
: weak_ptr<Foo> or shared_ptr<Foo> Create(); 給client
: 然後Create函數裡面實作的時候使用一個global之類的container,
: 把這sp記錄起來確保 .so是最後一個去delete他的人
: 但這邊衍伸一個疑問
: c. a.exe + b.so 使用load time dynamic link的話,
: a.exe還是b.so的global變數會先開始解構?
: 如果是使用 run time dynamic link的話
基本上 destructor 是 constructor 倒過來的順序
Linux 會用 .init/.init_array/.fini/.fini_array 做 global 的 constructor
destructor
load constructor, unload 時 destructor, a.exe 先, b.so 後,
同理 dlclose b.so 時做 destructor, exit a.exe 才 destrucor
: d. 如果client用dlopen 然後dlsym拿到一個sp後
: 手動呼叫dlclose....這時候繼續使用這個sp 是不是會有undefined的行為產生?
: 就算沒有人解構可是.so已經被close了?
好像沒直接關係?
如果 so 裡 new int(123) 傳回去, 應該是不會怎樣啦,
你是想問如果 object 的 code 在 so裡吧? 我想是會炸掉,
不過為什麼好好的要 dlopen 東西 allocate 東西又再 dlclose 掉?
: e. 不能在不同的library間 new delete這件事 是否是platform/compiler dependent?
: 假設linux上如果我測試發現沒有出問題 是否表示在這compiler/platform下
上面回了, 基本上是 platform ABI 決定的行為 compiler (整個 toolchain 和lib)
要配合 platform ABI
: 100%不會發生問題
你這條件太強了 XD
: 還是說"有可能" 不定時的出現問題 而不能馬上當下發現立刻修正?
: 因為目前使用那個shared library(return unique ptr)並沒發生問題
: 會不會之後在某特定情況下突然出現問題呢?
: 會有 lib 間 heap是獨立的這件事 是gcc / vc實作決定的嗎 還是更底層 OS實作?
我覺得獨立 heap 是稻草人 orz
: f. 以我的觀念來看 如果是.a 的static lib, 是不是就完全沒有design上特別的考量
: 不需要特別去因為寫static library而有特別需要注意的地方?
: g. 如果一個exe使用多個.so 而這些.so都用不同版本的gcc build出來的
: 這樣如果他們expose的api 含有 stl的type
: 是不是就是一個非常不好的design?
不同 gcc 可能會搭不且的 stl library 和 glibc, 混用會有問題
雖然 shared object 有 versioning 的基制, 但實際上還是會有遇一些問題
gcc 的 stl 應該也沒有不同版本的實作完全相同,
SO 和執行檔如果會 pass object 給對方, 例如上面 unique_ptr 的 case,
有可能兩邊的 declaration 有差異而 runtime 才發現問題
: 如果我真的要使用這些不同版本的.so 是不是只能祈禱不會出事情而無法作解決?
: 而這問題是不是只要這些.so用dynamic link libstdc++就沒事了?
: h. 有沒有什麼網頁有特別針對shared library的interface design 有提供guideline?
: 想要稍微go through一下比較能掌握一些必要觀念
最重要的這部份我好像沒辦法給建議 @@
最去經驗是 dynamic link 其實沒有想像中的 protable, 蠻常遇到問題,
但如果你要 static link 又有到 dlopen 等 function, 那 link 時的 glibc
和執行的 glibc 要同一個版本, 不然會有問題. 這樣 static link 其實沒意義
在 Linux 要保證沒問題最好還是要在執行環境重編
: 以上幾個問題有點複雜 請教各位
: 非常感謝
作者: ilikekotomi (Young)   2018-08-24 00:24:00
感謝分享 一直不知道dll和so有這種差別
作者: james732 (好人超)   2018-08-24 01:10:00
作者: cole945 (躂躂..)   2018-08-24 09:05:00
想了一下我a.講的有點誤導. create出來叫人家自已delete的framework也少. 用的人通常自已決定要不要用uniq_ptr去接. 但直接return uniq_ptr強迫人家用的自已經驗沒遇過> 第二推少了一個字 "不少"
作者: Bencrie   2018-08-24 09:40:00
g 這樣不會重複定義喔?@@a試了一下 link so 還真的不會撞到 XD
作者: AstralBrain   2018-08-24 10:04:00
是個undefined behavior, no diagnostic required
作者: lovejomi (JOMI)   2018-08-25 00:27:00
看來要花時間吸收一下,謝謝不過uniqueptr真的不能使用default deleter嗎我遇到 a.exe 使用vector<.so type> 然後.so 裡面也使用這種vector<type>..兩者用不同版本編,link的時候出現warning possible ODR violation....是不是表示a.exe最終可能是link到.so的vector實作,也可能是自己的vector實作?決定權在linker?
作者: Killercat (殺人貓™)   2018-08-28 22:34:00
namespace一樣的話 對事實上嚴格講起來是symbol symbol就是namespace+arg所以「symbol」一樣的話 linker會直接吐error給你程式設計師的自我修養有相當詳細的解釋有點語意不清 其實symbol的話決定權並非linker, linker唯一能做的不是選擇而是直接靠背出來
作者: cole945 (躂躂..)   2018-08-28 22:51:00
你說的.o link的情況,上面在說的是dynamic linking的情況換個方法講,這篇在講的是loader處理module間symbol的問題而你在說的是linker link單一module時的問題如果說linker不用管選擇symbol也太單純化了,至少處理weaksymbol就有影響了. 雖然我沒認真讀過程式設計師的自我修不過我的工作是弄整個toolchain,不至於這個搞不清XD

Links booklink

Contact Us: admin [ a t ] ucptt.com