There is a dynamic library code structured as follows:
CMakeLists.txt:
cmake_minimum_required(VERSION 3.24)
project(foo)
set(CMAKE_CXX_STANDARD 17)
add_library(foo SHARED library.cpp)
target_compile_definitions(foo PRIVATE MYLIB_EXPORTS)
library.h:
#pragma once
#ifdef _WIN32
#ifdef MYLIB_EXPORTS
#define MYLIB_API __declspec(dllexport)
#else
#define MYLIB_API __declspec(dllimport)
#endif
#else
#define MYLIB_API
#endif
#include <iostream>
class Foo {
public:
int a = 123;
void say_hello();
};
extern "C" MYLIB_API void say_hello_wrapper() {
Foo obj;
obj.say_hello();
}
library.cpp:
#include <iostream>
#include "library.h"
void Foo::say_hello() {
std::cout << "Hello from Foo! a = " << a << std::endl;
}
There is also a simple example application that imports functions from this library
(The header file with the library is not shipped):
CMakeLists.txt:
cmake_minimum_required(VERSION 3.24)
project(foo_exe)
set(CMAKE_CXX_STANDARD 17)
add_executable(foo_exe main.cpp)
target_link_libraries(foo_exe PRIVATE foo)
target_link_directories(foo_exe PRIVATE ${CMAKE_SOURCE_DIR})
main.cpp:
#ifdef _WIN32
#ifdef MYLIB_EXPORTS
#define MYLIB_API __declspec(dllexport)
#else
#define MYLIB_API __declspec(dllimport)
#endif
#else
#define MYLIB_API
#endif
extern "C" MYLIB_API void say_hello_wrapper();
int main() {
say_hello_wrapper();
return 0;
}
So, the question is: what are the possible pitfalls with this kind of import? What could go wrong?
Why is using dlopen and retrieving a function pointer generally considered safer than this kind of implicit linking?
I've heard that functions may have offsets, and there might be issues because of that.
Or maybe such problems only happen on Windows where an import library is required.
Note: I am not interested in explicit dynamic loading via dlsym
.
Looking forward to expert opinions. Thanks!
Your question seems to be: Are there any circumstances in which declaring
a dynamic library function in a client program's source code by
writing it there directly, rather than by #include
-ing its declaration
from a header file, could make something go wrong when the program is dynamically
linked in the usual way that would be avoided if the program were
not dynamically linked but instead programmatically loaded the dynamic
library and probed its API?
(The programmatic loading and probing would be done with dlopen
, dlsym
et al. on Unix,
LoadLibrary
, GetProcAddress
et al. on Windows).
Let's assume that the declaration you consider writing directly in the client source code, say:
extern "C" void say_hello_wrapper()
on Unix, or on Windows:
extern "C" __declspec(dllimport) void say_hello_wrapper()
is a true and complete expression of the way in which your
compiler ought to generate references to the symbol say_hello_wrapper
dynamically exported by the shared library, so that these uses are sure to be consistent with the definition in the library.
In that case it makes no difference whatever, in any circumstances,
whether you write it directly into your source code, #include
it
from a header a file, copy-paste it from a header file into your source
code, or copy-paste the entire contents of that header file into your source code (which
is in effect what #include
does).
You invite us to suppose that you would have to write the declaration directly in the source code because library does not ship with a header file.
Clearly that would be an off-putting feature of the library to a quite unrealistic extent. Any client programmer compelled to use it would have no choice but to try to find out the library's API from any other documentation they could access, or from hearsay or by reverse engineering the binary, and then hand-code the necessary declarations in the client source.
This error-prone process would lead to inaccurate uses of the API being compiled
and linked in client code at a rate vastly greater than would be the case if
a header file was there to be #include
-ed, and this would certainly make
things go wrong. In the best case, the flawed linkage
would fail. In the next best case, it would succeed but the program would
crash at runtime. In the worst case, the program would link and not crash but misbehave in a
baffling way.1
But all such adverse outcomes will stem from mis-declaring the API and using it according to that mis-declaration. And they will arise equally (perhaps with different particular manifestations) whether the client programmer dynamically links the library in the usual way, or dynamically loads it programmatically, or indeed does not use the dynamic library at all and links the corresponding static library instead.
If you declare an external function (or data object) accurately in client code then it makes no difference to the compiler, static linker or dynamic linker how the declaration gets into the client code.
A header file as such has no significance for compilation or linkage. We create a header file and use it for compilation of both library and client code because this convention gives fairly good practical assurance that the API declarations consumed by client code are accurate declarations of the definitions in the library, because (at least before pre-processing) the declarations are in both cases the very same chunk of text.
Accuracy of declarations is often more complicated than you might think: browse a few of your compiler's Standard C++ or C header files. Don't consider not using a library's header files for linking against it, just because in theory you could.