symbol visibility via defs or attributes?
I thought I'd split off a new issue from #2057 (closed) to have a longer term discussion of what is the right way to handle symbol visibility. The remainder of this description is going to assume you have read the following:
- #205 (closed)
- !1760 (merged)
- #1940 (closed)
- https://forum.graphviz.org/t/use-dllimport-dllexport-and-or-a-def-file-on-windows/620
- https://docs.microsoft.com/en-us/cpp/cpp/declspec?view=msvc-160
- https://docs.microsoft.com/en-us/cpp/build/exporting-from-a-dll-using-declspec-dllexport?view=msvc-160
- https://docs.microsoft.com/en-us/cpp/build/exporting-from-a-dll-using-def-files?view=msvc-160
- https://gcc.gnu.org/wiki/Visibility
So Graphviz (or really any C/C++ app+lib project) needs to manage visibility wrt linkage that is outside the C and C++ standards. To reiterate and stress that last part, there is currently no way to control this kind of symbol visibility with ISO standardized mechanisms.¹ Here is the ideal state for the end user, using the GCC visibility terms:
context | symbol | visibility |
---|---|---|
compiling libfoo |
bar , a symbol only used internally in libfoo |
internal |
compiling libfoo |
baz , a symbol that is part of libfoo’s API |
default |
compiling foo, an app that links against libfoo | baz |
hidden |
compiling foo | a symbol from foo itself | hidden |
There are a number of ways to control symbol visibility:
- Command line options, e.g.
-fvisibility…
- GNU attributes,
__attribute((visibility(…)))
- MSVC
__declspec
- MSVC defs file
(1) is not useful for controlling symbol-by-symbol visibility. (2) is understood by GCC and Clang, but not MSVC. (3) is understood by GCC and MSVC, but not Clang.² (4) is hard to keep in sync with the source.
So essentially I think what we want to aim for is something like the following in a library’s API header:
#ifdef FOO_EXPORT
#ifdef _WIN32
#define FOO_API __declspec(dllexport)
#elif defined(__GNUC__)
#define FOO_API __attibute__((visibility("default")))
#else
#define FOO_API /* nothing; suboptimal build with everything visible */
#endif
#else
#ifdef _WIN32
#define FOO_API __declspec(dllimport)
#elif defined(__GNUC__)
#define FOO_API __attribute__((visibility("hidden")))
#else
#define FOO_API /* nothing; suboptimal build with everything visible */
#endif
#endif
where FOO_EXPORT
is only defined when compiling the library itself. There are some additional complications for C++,³ but for the most part I think this is it. Please comment if you think we should be moving towards something different.
CC @magjac, @compnerd_
¹ Neither extern
nor static
is the droid we are looking for.
² However, in practice the GCC interpretation of this is not so useful as e.g. __declspec(dllimport)
does not imply extern
as it does under MSVC.
³ As https://gcc.gnu.org/wiki/Visibility discusses, things that need RTTI typically need to be visible across SOs.