Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

C++ visibility support #1040

Closed
wants to merge 29 commits into from
Closed

C++ visibility support #1040

wants to merge 29 commits into from

Conversation

jslee02
Copy link
Member

@jslee02 jslee02 commented Mar 22, 2018

This PR implements the control of binary symbols visibility in DART inspired by flexible-collision-library/fcl#233.

DART_EXPORT preprocessor is added to any class, struct, and function that needs to be exposed publicly to third-party software. I could compile all the tests, examples, and tutorials without any issue, but it would be good to test DART with other third-party software like AIKIDO for sure.

DART_HIDE_ALL_SYMBOLS CMake option is also added, which is OFF by default, for the case that we want to hide all the symbols as DART is built as static library.

Caveat:
There is a preprocessor name conflict between the existing DART_DEPRECATED(version) and another preprocessor generated by generate_export_header(), which is necessary to mark visibility of deprecated APIs. To resolve this, I changed the name to DART_DEPRECATED2 so that the preprocessor for deprecated API becomes DART_DEPRECATED2_EXPORT. I'm open to better workaround.

Alternatively, we could remove DART_DEPRECATED(version) and use one generated by generate_export_header(). The version information can be provided by using Doxygen comment (e.g., /// \deprecated Deprecated in <version>. Please use <other_api> instead).).

This PR is related to #1005.


Before creating a pull request

  • Format new code files using clang-format

Before merging a pull request

  • Set version target by selecting a milestone on the right side
  • Summarize this change in CHANGELOG.md
  • Add comment to the export preprocessors

@jslee02 jslee02 added this to the DART 6.4.0 milestone Mar 22, 2018
@jslee02 jslee02 requested a review from mxgrey March 22, 2018 17:57
@mxgrey
Copy link
Member

mxgrey commented Mar 22, 2018

This is a good start, but I have a few notes:

I changed the name to DART_DEPRECATED2 so that the preprocessor for deprecated API becomes DART_DEPRECATED2_EXPORT.

What I've done in the past is:

  • Put the auto-generated header into detail/export.h (e.g. dart/detail/export.h)
  • Name the auto-generated macros as DETAIL_<BASE>_<MACRO_NAME>
  • Manually write an export.h file (e.g. dart/export.h)
  • Have the manually-written export.h file use the macros that were defined in dart/detail/export.h, e.g.:
#include <dart/detail/export.h>

/* explain the macro here */
#define DART_EXPORT \
    DETAIL_DART_EXPORT

/* explain the macro here */
#define DART_DEPRECATED(v) \
    DETAIL_DART_DEPRECATED
...

This also gives us the opportunity to add documentation to each macro, which may be helpful for end users.

I would suggest we consider naming the export macro as DART_API instead of the default DART_EXPORT, since I think calling it API makes it more explicit about its purpose. We can do that by adding EXPORT_MACRO_NAME DART_API to the generate_export_headers command.

And a final note: We need to create a unique export header for each library in the project, which means each of the optional components that aren't part of libdart need their own unique export header with their own unique export macro name.

@mxgrey
Copy link
Member

mxgrey commented Mar 22, 2018

Revising my last comment: Since we would need a bunch of the "manually written" export.h with slight variations for each library, we can create an export.h.in and configure it for each of the components.

@jslee02
Copy link
Member Author

jslee02 commented Mar 22, 2018

Having another layer of export header sounds good to me.

And a final note: We need to create a unique export header for each library in the project, which means each of the optional components that aren't part of libdart need their own unique export header with their own unique export macro name.

Could you let me know why do we need different configurations for each component?

@mxgrey
Copy link
Member

mxgrey commented Mar 22, 2018

The definition of the export macro will be different depending on whether the header is being included by the library that is defining the class or by a library that is using the class.

To illustrate how things would go wrong, let's say we use the same export macro for libdart-gui-osg as libdart. Then when we compile libdart-gui-osg and the compiler comes across the declaration of Skeleton in dart/dynamics/Skeleton.hpp, then the compiler will think that libdart-gui-osg wants to define and export the symbol of dart::dynamics::Skeleton. This will cause undefined reference and/or other linker errors, since libdart-gui-osg does not have access to the definition of dart::dynamics::Skeleton.

@jslee02
Copy link
Member Author

jslee02 commented Mar 22, 2018

@mxgrey That makes sense to me. I have a follow-up question though.

Although gui-osg component uses different preprocessors (e.g., DART_GUI_OSG_API), I think the component still will try to export the symbol of dart::dynamics::Skeleton because Skeleton.hpp includes dart/export.hpp so that DART_API is defined to export the symbol. How could we prevent this situation? Should we redefine all the export preprocessor except the preprocessors of the component not to try to export symbols of other components?

@mxgrey
Copy link
Member

mxgrey commented Mar 22, 2018

I think the component still will try to export the symbol of dart::dynamics::Skeleton because Skeleton.hpp includes dart/export.hpp so that DART_API is defined to export the symbol.

This is not correct.

When you use the generate_export_header function from cmake, it will add a private compile definition (something along the lines of dart_EXPORTS) to the library for which it's generated (this is why the first argument of the function needs to be the library target for which you're generating the export header). That compile definition, which is only given to libdart during compilation, will make it so that the DART_API macro expands to __declspec(dllexport) (or whatever the platform-specific export attribute is).

When libdart-gui-osg is compiled, it will not be given the dart_EXPORTS compile definition, so the DART_API macro will expand to __declspec(dllimport) (or whatever the platform-specific import attribute is).

@mxgrey
Copy link
Member

mxgrey commented Mar 22, 2018

So the short version is, cmake takes care of your concern automatically.

@jslee02
Copy link
Member Author

jslee02 commented Mar 22, 2018

@mxgrey Thanks for the explanation! I learned a lot while working on this PR.

I've addressed your feedback in the last commits.

test_Collision fails due to a reason that is not relevant to the changes of this PR. In a nutshell, the current implementation of Singleton doesn't work for multiple binary modules (related SO post). I'll handle this in a future PR.

)

# Generate final export header
set(header_guard_name ${base_name_upper}_EXPORT_HPP_)
Copy link
Member

@mxgrey mxgrey Mar 23, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would probably be much cleaner if we created a export.hpp.in and then called configure_file on it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree. I wrote this lines because I thought each component requires individual export.hpp.in, but this is not true.

generate_export_header(${target_name}
EXPORT_MACRO_NAME ${base_name_upper}_DETAIL_API
EXPORT_FILE_NAME detail/export.h
DEPRECATED_MACRO_NAME ${base_name_upper}_DETAIL_DEPRECATED
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: I like to put the DETAIL in front of DART so it gets excluded from autocomplete when users start typing DART....

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's true, but I thought we use DART_ prefix to avoid possible name conflicts. At the same time, I don't see the possibility of name confliction with DETAIL_DART_~.

Let me change it to DETAIL_DART_~ as I don't have a strong preference here.

@jslee02
Copy link
Member Author

jslee02 commented Apr 29, 2018

@mxgrey I've fixed build and test errors. Could you do a second review?

@@ -42,7 +43,7 @@
//==============================================================================

#if defined(__GNUC__) || defined(__clang__)
#define DART_DEPRECATED(version) __attribute__ ((deprecated))
#define DART_DEPRECATED(version) DETAIL_DART_DEPRECATED
#define DART_FORCEINLINE __attribute__((always_inline))
#elif defined(_MSC_VER)
#define DART_DEPRECATED(version) __declspec(deprecated)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should also be redirected to DETAIL_DART_DEPRECATED to be consistent with the other definition.

Copy link
Member

@mxgrey mxgrey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe the export macro should not be applied to any templates. It should only be applied to fully-defined classes (which does include explicit template instantiations, but it does not include any templates with unset parameters).

I've marked a few lines with the comment Example to point out what I mean.

The reason incomplete templates can't be marked as exported is because it prevents a user from instantiating that template with their own custom parameters. Instead of automatically instantiating the template with whatever parameters the user requests, the compiler will assume that a suitable class with those parameters was already instantiated by the DLL that exported the header. This will typically result in a linking error.

@mxgrey
Copy link
Member

mxgrey commented Apr 30, 2018

It looks like the lines that I marked with Example didn't make it into the review for some reason. In any case, template definitions shouldn't be given the export macro.

@jslee02
Copy link
Member Author

jslee02 commented Apr 30, 2018

I haven't fully investigated but without the export macro to the template classes caused test failures of test_Collision and test_Skeleton for different reasons.

test_Collision failed due to creating different static instances for collision factory; FCL and DART collision detectors are registered to one factory, but Bullet and FCL are registered to each different factory.

test_Skeleton failed due to that null is returned from here. (edit: This only happens on macOS)

@mxgrey
Copy link
Member

mxgrey commented Apr 30, 2018

test_Collision failed due to creating different static instances for collision factory

I think that's because this class is never marked as exported. I'm not sure what the correct way to export that would be. Maybe we need to explicitly instantiate the SingletonFactory class. In this specific case, we could decide that we do not want users to be able to use our Singleton template for any of their own template arguments (and therefore put the DART_API macro on it), but we can't apply that reasoning to all of our templates, or else DART won't be extensible.

test_Skeleton failed due to that null is returned from here.

That might be a trickier issue. It's not immediately clear to me why that would happen.

@jslee02
Copy link
Member Author

jslee02 commented Apr 30, 2018

Marking a static member of a template class would be also dangerous? test_Collision can pass with this.

@mxgrey
Copy link
Member

mxgrey commented Apr 30, 2018

Yes, I think that would also be dangerous, because the definition is actually given in the implementation header.

My understanding of the export macro is that it effectively tells the user's compiler "Don't try to create a definition for this symbol, because the definition will be provided by the library that exported this header", and then it tells the user's linker "Don't look for a definition of this symbol within the library that we just compiled; instead, look for it in one of the libraries that we are linking to".

I think if we "export" that static member, then users will get a linking error for it whenever they pass a custom class type into Singleton<T>.

@jslee02 jslee02 modified the milestones: DART 6.5.0, DART 6.6.0 May 12, 2018
@jslee02 jslee02 changed the base branch from release-6.5 to release-6.6 May 12, 2018 15:49
@jslee02 jslee02 removed this from the DART 6.6.0 milestone Jul 19, 2018
@jslee02 jslee02 added the help wanted Indicates wanting help on an issue or pull request label Aug 12, 2018
@jslee02 jslee02 closed this Jan 12, 2019
@jslee02 jslee02 deleted the build/visibility branch July 22, 2021 08:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
help wanted Indicates wanting help on an issue or pull request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants