I'm building a plug-in (loadable bundle) for Adobe Illustrator (macOS) which uses boost extensively and boost is linked statically.
The host application also uses boost albeit a different version and they include it as a framework.
As it turns out, when my bundle is loaded by the host app, the dynamic linker prefers the function from the framework that comes with the application and not from the bundle, causing compatibility errors.
Is there a way to instruct the dynamic linker to always prefer symbols defined in the bundle?
The host application also uses boost albeit a different version and they include it as a framework.
As it turns out, when my bundle is loaded by the host app, the dynamic linker prefers the function from the framework that comes with the application and not from the bundle, causing compatibility errors.
Is there a way to instruct the dynamic linker to always prefer symbols defined in the bundle?
In order to support templates C++ allows some symbols to defined multiple times. This required because when you have a type like std::vector<int> in multiple files it will be expanded in each file, resulting in duplicate symbols. The standard specifies how this works via ODR (the One Definition Rule), which essentially says when the linker encounters such symbols it should just pick one. That works fine when you are compiling everything from scratch as part of a single project, but as you have discovered it can be a major issue once you bring dynamic libraries and plugins into the picture. It is also worth noting that this can also be an issue at static link time if you have a static archive contains an incompatible implementation of a symbol.
In your case the dynamic linker will always choose the implementation in the main executable. The reason is that C standard requires that function pointer equivalence is maintained (in other words, &std::vector<int>::insert must always return the same value). Since the main program has already been running it is possible some code has taken the address of one of the conflicting symbols, which means the dynamic linker must treat the already present implementations as the canonical ones or it would violate that equivalence.
My recommendation here would be to use stop exporting the symbols in question (in fact I would stop exporting any symbols that you do not need to). That will technically break ODR, but in practice that should not be an issue unless you expect to pass objects using the conflicting functions between the plugin and the main executable. Limiting the exports will cause the static linker to stop emitting the export information, which means the dynamic linker will not know about it and will not end up pointing it to the implementation in your app. This will also allow the static linker to more aggressively dead strip (resulting in smaller binaries), and improve app startup time (since the dynamic linker will have less work to do).
You can control your exports via Exported Symbols File and Unexported Symbols File entries in the Xcode Build Settings. The format of those files is detailed in the linked manage (man ld(1)) under -exported_symbols_list.
In your case the dynamic linker will always choose the implementation in the main executable. The reason is that C standard requires that function pointer equivalence is maintained (in other words, &std::vector<int>::insert must always return the same value). Since the main program has already been running it is possible some code has taken the address of one of the conflicting symbols, which means the dynamic linker must treat the already present implementations as the canonical ones or it would violate that equivalence.
My recommendation here would be to use stop exporting the symbols in question (in fact I would stop exporting any symbols that you do not need to). That will technically break ODR, but in practice that should not be an issue unless you expect to pass objects using the conflicting functions between the plugin and the main executable. Limiting the exports will cause the static linker to stop emitting the export information, which means the dynamic linker will not know about it and will not end up pointing it to the implementation in your app. This will also allow the static linker to more aggressively dead strip (resulting in smaller binaries), and improve app startup time (since the dynamic linker will have less work to do).
You can control your exports via Exported Symbols File and Unexported Symbols File entries in the Xcode Build Settings. The format of those files is detailed in the linked manage (man ld(1)) under -exported_symbols_list.