Hi all and sorry for the cryptic title.
I have an executable which loads a DLL using WinAPI LoadLibrary
/GetProcAddress
. The program calls an exported function with C linkage with a custom class as parameter (as a reference but I will come back later on this). The exported function is defined as follow (GGen
will be defined just after):
// LIB_API is __declspec(dllexport) in DLL context and
// __declspec(dllimport) in executable context
LIB_API void dbgt_generate(IRFile& cr) {
GGen::generate(cr);
}
My custom class is defined as follow and is header-only:
class IRFile {
public:
std::string identifier;
using callback_t = std::string(*)(IRFile*, void*);
static callback_t _callback;
virtual std::string buildFile(void* genData) {
assert(IRFile::_callback);
return IRFile::_callback(this, genData);
}
};
inline IRFile::callback_t IRFile::_callback = nullptr;
The exported function calls a singleton class by the way of a static member (the generate
method):
static std::string cbFile(IRFile* _this, void* r) {
return "irfile";
}
class GGen {
public:
GGen() noexcept {
IRFile::_callback = cbFile;
}
static void generate(IRFile& cr) {
static GGen ggen;
assert(IRFile::_callback);
auto data = cr.buildFile(nullptr);
assert(!data.empty());
}
};
The executable simply initializes an IRFile
instance on the stack and calls the exported function. My problem is that GGen::generate
works well and assert pass without any problem but when buildFile
is called during GGen::generate
, the assert in it does not pass. At the precise cr.buildFile
call moment, IRFile::_callback
appears to be NULL.
After investigations, it appears that IRFile::_callback
"has another address when in the virtual function compared to before" (I know this sound weird). I have figured out that removing virtual does fix the problem but in my complete program, I need it even if not used here. Another point is that when I pass the IRFile instance to the exported function as a copy (pass-by-value) the problem is also gone. But I don't understand why this last point fixes the problem as even if memory sharing was problematic, IRFile::_callback is initialized inside and by the DLL itself... I have added the identifier
field to ensure correct memory is used and it is the case (in other words, this
is correct).
Is it stack corruption? Why does removing virtual changes anything as, in my knowledges, static has nothing to do with vtables? Is this the effect of an undefined behavior with coincidences?
Please don't mind ugly C++ or naming conventions, I had to rewrite parts for testing to simplify.
[Edit 1] Clarified that the problem occurs after IRFile::_callback
is initialized by GGen::generate
and indirectly GGen ctor.
[Edit 2] As suggested in comments, I have tried to inline IRFile::_callback
and export it as DLL symbol without success. I have also tried to put IRFile
in a separate project (.lib and .dll tested) inlined or not, defined in a .cpp or not. I have also tried to encapsulate calls to _callback with setters/getters without success event when in a separate project (.lib/.dll).
[Edit 3] Added inline specifier to the _callback
definition in header file to avoid multiple definition of _callback
according to C++17: