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