The underlying issue here is the visibility of the symbols within your library. You can see via nm -m
that there are several external symbols besides the GetFactory
function you intend to export:
$ nm -m libmylib.dylib
0000000000000f30 (__TEXT,__text) external _GetFactory
0000000000001068 (__DATA,__common) external __ZN9MyFactory6mCountE
0000000000000f50 (__TEXT,__text) weak external __ZN9MyFactory8GetCountEv
0000000000001038 (__DATA,__data) weak external __ZTI8IFactory
0000000000001050 (__DATA,__data) weak external __ZTI9MyFactory
0000000000000f91 (__TEXT,__const) weak external __ZTS8IFactory
0000000000000f86 (__TEXT,__const) weak external __ZTS9MyFactory
0000000000001020 (__DATA,__data) weak external __ZTV9MyFactory
(undefined) external __ZTVN10__cxxabiv117__class_type_infoE (from libc++)
(undefined) external __ZTVN10__cxxabiv120__si_class_type_infoE (from libc++)
(undefined) weak external __Znwm (from libc++)
(undefined) external dyld_stub_binder (from libSystem)
It is the symbols marked as weak external
that are the cause of your problem.
Running your test application with DYLD_PRINT_BINDINGS=YES
reveals:
$ DYLD_PRINT_BINDINGS=YES ./mytest
[ … output showing initialization of libstdc++.dylib and libmylib.dylib omitted …]
Handle A: 0x7fc729c03810 Function A: 0x102a51ee0 Count: 1
Handle B: 0x7fc729c03810 Function B: 0x102a51ee0 Count: 2
dyld: bind: libmylib_copy.dylib:0x102A85038 = libc++abi.dylib:__ZTVN10__cxxabiv117__class_type_infoE, *0x102A85038 = 0x7FFF7CA67B50 + 16
dyld: bind: libmylib_copy.dylib:0x102A85050 = libc++abi.dylib:__ZTVN10__cxxabiv120__si_class_type_infoE, *0x102A85050 = 0x7FFF7CA67BD0 + 16
dyld: bind: libmylib_copy.dylib:0x102A85018 = libstdc++.6.dylib:__Znwm, *0x102A85018 = 0x7FFF938F0325
dyld: bind: libmylib_copy.dylib:0x102A85000 = libdyld.dylib:dyld_stub_binder, *0x102A85000 = 0x7FFF9084E878
dyld: weak bind: libmylib_copy.dylib:0x102A85030 = libmylib.dylib:__ZN9MyFactory8GetCountEv, *0x102A85030 = 0x102A51F00
dyld: weak bind: libmylib_copy.dylib:0x102A85060 = libmylib.dylib:__ZTI8IFactory, *0x102A85060 = 0x102A52038
dyld: weak bind: libmylib_copy.dylib:0x102A85028 = libmylib.dylib:__ZTI9MyFactory, *0x102A85028 = 0x102A52050
dyld: weak bind: libmylib_copy.dylib:0x102A85040 = libmylib.dylib:__ZTS8IFactory, *0x102A85040 = 0x102A51F41
dyld: weak bind: libmylib_copy.dylib:0x102A85058 = libmylib.dylib:__ZTS9MyFactory, *0x102A85058 = 0x102A51F36
dyld: weak bind: libmylib_copy.dylib:0x102A85010 = libmylib.dylib:__ZTV9MyFactory, *0x102A85010 = 0x102A52020
dyld: weak bind: libmylib_copy.dylib:0x102A85018 = libstdc++.6.dylib:__Znwm, *0x102A85018 = 0x7FFF938F0325
Handle C: 0x7fc729c03c20 Function C: 0x102a84ee0 Count: 3
If you look at the weak bind
lines you'll see that the symbols we saw earlier that were marked as weak external
are being resolved to the symbols of the same name in libmylib.dylib
. I believe that this behavior relates to C++'s One Definition Rule ("Every program shall contain exactly one definition of every non-inline function or object that is used in that program"). Since you have multiple definitions of the same non-inline function in your program, the linker is attempting to coalesce the symbols at load time so only a single function is used.
The best solution here would be for each library to place its symbols inside a different namespace. Since you intend to export only C factory function, an anonymous namespace would be sufficient. This also has the effect of marking the remainder of the symbols as being non-external, which can be beneficial for library load time.
Alternatively, you can cheat a little bit on the One Definition Rule by only exporting the symbols that the clients of your library need:
$ clang++ -Wl,-exported_symbol -Wl,_GetFactory -dynamiclib myclass.cpp -o libmylib.dylib
$ cp libmylib.dylib libmylib_copy.dylib
$ ./mytest
Handle A: 0x7fc593403910 Function A: 0x1009e4e90 Count: 1
Handle B: 0x7fc593403910 Function B: 0x1009e4e90 Count: 2
Handle C: 0x7fc593403b10 Function C: 0x1009e7e90 Count: 1
This causes the weak external
symbols that we saw earlier to be marked as private, and therefore they will only resolve to symbols within the same image.