Monday, July 13, 2020

Duplicate Symbols

The problem
There are several tests for the Gateware project that attempt to create duplicate symbols. Passing these isolation tests mean that the project can be built without producing any duplicate symbol errors. On Windows and Linux, the isolation tests pass, but on Mac, they do not. Instead, duplicate symbol errors appear from several modules.

What are duplicate symbols?

Translation units are created for every source file in a project. Header guards are used to prevent redefining a class in a translation unit (TU) during compile time. However, it does not prevent against redefinition across TU's. Each TU becomes an object file with a .o or .obj extension. In the linking phase, those objects files are linked together into an executable. 
During this process, if the linker finds multiple object files to have the same definition for a class, a duplicate symbol error is produced.

A solution
In the case of a class, duplicate symbol errors can be avoided by separating the interface from the implementation.
The interface goes into a header file (.h or .hpp) and the  implementation goes into a source file (.cpp, .m, or .mm). Done this way, the implementation is defined once in its own object file. Using this solution fixes the issue with the project. However, releases of Gateware are flattened into a single file. So this solution will not do.

Another solution
In order to still be able to flatten the project into a single header, we need to combine the implementation with the interface. We can do this by implicitly inlining the implementation with interface in the header.
By writing the body with each function declaration, C++ permits us to have more than one definition in different TU's. Therefore, we don't receive duplicate symbols errors in the linking phase.

However Obj-C...
On closer inspection, the duplicate symbol errors are all coming from Objective-C classes. Right now in Gateware, the interface for Objective-C classes is placed in the same header file as the implementation, the same way we do with C++ classes. This solution works fine when a single TU is created; however, it fails during the isolation tests when multiple TU's are created. My current research into this problem suggests that the only way to fix this issue in Objective-C is by separating the implementation and the interface into separate files. Again, that would break ability to flatten Gateware into a single header.

A partial solution
Some of the Objective-C classes aren't necessary and can be rewritten as C++ classes. However, this doesn't work for every Objective-C class that relies on inheritance to receive events. Such is the case with GWindow_mac, which currently defines an Objective-C class that overrides functions from NSWindowDelegate to receive and handle window events.

To be continued...
A week into the problem, and this is where I am so far. The issue is more involved than I understood from the start. However, I haven't exhausted all of my resources yet to solve this problem. I'm currently exploring some leads that Lari found. I also posted a question on the Apple Developer Forums. I'll keep probing, and with any luck, my next post will be about how the issue was solved, instead of just what I tried.

No comments:

Post a Comment