6.0 KiB
Using GTSAM_EXPORT
To create a DLL in windows, the GTSAM_EXPORT
keyword has been created and needs to be applied to different methods and classes in the code to expose this code outside of the DLL. However, there are several intricacies that make this more difficult than it sounds. In general, if you follow the following three rules, GTSAM_EXPORT should work properly. The rest of the document also describes (1) the common error message encountered when you are not following these rules and (2) the reasoning behind these usage rules.
Usage Rules
- Put
GTSAM_EXPORT
in front of any function that you want exported in the DLL if and only if that function is defined in a .cpp file, not just a .h file. - Use
GTSAM_EXPORT
in a class definition (i.e.class GSTAM_EXPORT MyClass {...}
) only if:- At least one of the functions inside that class is defined in a .cpp file and not just the .h file.
- You can
GTSAM_EXPORT
any class it inherits from as well. (Note that this implictly requires the class does not derive from a "header-only" class. Note that Eigen is a "header-only" library, so if your class derives from Eigen, do not useGTSAM_EXPORT
in the class definition!)
- If you have defined a class using
GTSAM_EXPORT
, do not useGTSAM_EXPORT
in any of its individual function declarations. (Note that you can putGTSAM_EXPORT
in the definition of individual functions within a class as long as you don't putGTSAM_EXPORT
in the class definition.)
When is GTSAM_EXPORT being used incorrectly
Unfortunately, using GTSAM_EXPORT
incorrectly often does not cause a compiler or linker error in the library that is being compiled, but only when you try to use that DLL in a different library. For example, an error in gtsam/base will often show up when compiling the check_base_program or the MATLAB wrapper, but not when compiling/linking gtsam itself. The most common errors will say something like:
Error LNK2019 unresolved external symbol "public: void __cdecl gtsam::SO3::print(class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > const &)const " (?print@SO3@gtsam@@QEBAXAEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z) referenced in function "public: static void __cdecl gtsam::Testable<class gtsam::SO3>::Print(class gtsam::SO3 const &,class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > const &)" (?Print@?$Testable@VSO3@gtsam@@@gtsam@@SAXAEBVSO3@2@AEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z) check_geometry_program C:\AFIT\lib\gtsam\build\gtsam\geometry\tests\testSO3.obj
Sorry for the long error message there, but I want to quickly decode it to help understand what is going on. First, there is an unresolved symbol gtsam::SO3::print
. This can occur because either GTSAM_EXPORT
was not added to the print function definition when it should have been, OR because GTSAM_EXPORT
was added to the print function definition when it is fully declared in the header. This error was detected while compiling check_geometry_program
and pulling in ...\testSO3.obj
. Specifically, within the function call gtsam::Testabl<class gtsam::SO3>::Print (...)
. Note that this error did not occur when compiling the library that actually has SO3 inside of it.
But Why?
To me, the behavior of GTSAM_EXPORT was very baffling. However, I believe it can be explained by understanding the rules that MSVC operates under.
First, we need to understand exactly what GTSAM_EXPORT
does. When you use GTSAM_EXPORT
on a function (or class) definition, it actually does two things. First, when you are building the library, it turns into a "dllexport" command, telling the compiler that this function should go into the DLL and be visible externally. When you pull in that same header for a different library, it switches to a "dllimport" command, telling the compiler it should find this function in a DLL somewhere. This leads to the first rule the compiler uses. (Note that I say "compiler rules" when the rules may actually be in the linker, but I am conflating the two terms here when I speak of the "compiler rules".)
Compiler Rule #1 If a dllimport
command is used in defining a function or class, that function or class must be found in a DLL.
Rule #1 doesn't seem very bad, until you combine it with rule #2
Compiler Rule #2 Anything defined in a header file is not included in a DLL.
When these two rules are combined, you get some very confusing results. For example, a class which is completely defined in a header (e.g. LieMatrix) cannot use GTSAM_EXPORT
in its definition. If LieMatrix is defined with GTSAM_EXPORT
, then the compiler must find LieMatrix in a DLL. Because LieMatrix is a header-only class, however, it can't find it, leading to a very confusing "I can't find this symbol" type of error. Note that the linker says it can't find the symbol even though the compiler found the header file that completely defines the class.
This leads to another compiler rule that finishes explaining the usage rules proposed above.
Compiler Rule #3 If a class is defined with GTSAM_EXPORT
, then any classes it inherits from must be definable with a dllexport command as well.
One of the most immediate results of this compiler rule is that any class that inherits from an Eigen class cannot be defined with GTSAM_EXPORT
. Because Eigen is a header-only class, compiler rule #2 prohibits any Eigen class from being defined with a "dllexport" command. Therefore, no Eigen inherited classes can use GTSAM_EXPORT
either. (Note that individual functions with an inherited class can be "exported." Just not the entire class.)
Conclusion
Hopefully, this little document clarifies when GTSAM_EXPORT
should and should not be used whenever future GTSAM code is being written. Following the usage rules above, I have been able to get all of the libraries, together with their test and wrapping libraries, to compile/link successfully.