I have an issue I don't understand. My project is quite simple for now. I have a shared library Engine which is called by my executable. I'm trying to move the entry point inside my shared library, so the executable part only has functions and some class to create.
---EDIT: I edit to post the project as it is reproductible easily.
To do so, I have these files in my shared library:
Here is entry.h
#include "BaseGame.h"
extern game* create_game();
int main(int argc, char *argv[])
{
auto testgame = create_game();
delete testgame;
return 0;
}
BaseGame.h
class __declspec(dllexport) game
{
public:
game() = default;
virtual ~game();
virtual bool initialize() =0;
virtual bool update(float deltaTime) =0;
virtual bool render(float deltaTime) =0;
virtual void on_resize() =0;
};
Application.cpp
#include "BaseGame.h"
class __declspec(dllexport) Application
{
public:
Application()=default;
~Application()=default;
};
And in my executable, I have two files entry.cpp which defines create_game and my_game.h which inherited from game.
entry.cpp
#include <entry.h>
#include "my_game.h"
game* create_game()
{
return new myGame;
}
and my_game.h:
class myGame : public game
{
public:
myGame(){};
~myGame() override = default;
bool initialize() override;
bool update(float deltaTime) override;
bool render(float deltaTime) override;
void on_resize() override;
};
my_game.cpp:
#include "my_game.h"
bool myGame::initialize()
{
return true;
}
bool myGame::update(float deltaTime)
{
return true;
}
bool myGame::render(float deltaTime)
{
return true;
}
void myGame::on_resize()
{
}
What I don't understand is that I always get an linker error when building my exe:
Création de la bibliothèque ..\bin\testbed.lib et de l'objet ..\bin\testbed.exp
entry-3b33f2.o : error LNK2019: symbole externe non résolu "public: virtual __cdecl game::~game(void)" (??1game@@UEAA@XZ) référencé dans la fonction "public: virtual __cdecl myGame::~myGame(void)" (??1myGame@@UEAA@XZ)
..\bin\testbed.exe : fatal error LNK1120: 1 externes non résolus
Also here is how i build my shared library:
SET assembly=Engine
SET compilerFlags=-g -shared -Wvarargs -Wall -Werror
SET includeFlags=-Isrc
SET linkerFlags=-luser32
SET defines=-D_DEBUG_EG -DGEXPORT -D_CRT_SECURE_NO_WARNINGS
ECHO "Building %assembly%%..."
clang++ %cFilenames% %compilerFlags% -o ../bin/%assembly%.dll %defines% %includeFlags% %linkerFlags%
And here is my executable:
SET assembly=testbed
SET compilerFlags=-g
REM -Wall -Werror
SET includeFlags=-Isrc -I../Engine/src/
SET linkerFlags=-L../bin/ -lEngine.lib
SET defines=-D_DEBUG_EG -DGIMPORT
clang++ %cFilenames% %compilerFlags% -o ../bin/%assembly%.exe %defines% %includeFlags% %linkerFlags%
Even if game is exported. Does anyone see something wrong? PS: I'm using Clang as the compiler.
The game
class lacks a destructor definition. I suggest making it default
:
class myGame : public game
{
public:
myGame(){};
~myGame() override = default; // here
bool initialize() override;
bool update(float deltaTime) override;
bool render(float deltaTime) override;
void on_resize() override;
};
You may also remove the default constructor and destructor from myGame
. It'll be default constructible and have a virtual
destructor by default.
class myGame : public game
{
public:
// myGame(){}; // remove
// ~myGame() override = default; // remove
// ...
Other notes:
my_game.h
should #include "BaseGame.h"
to get the definition of game
. You example is not entirely complete. I assume the destructor ~game()
is actually defined in some cpp file of the shared library. And my_game.h
needs to include BaseGame.h
(since myGame
derives from game
). So basically my_game.cpp
in the executable ends up seeing the definition of game
as shown, including the __declspec(dllexport)
. However, the executable should not see __declspec(dllexport)
(ie the exe should not export game
) but rather it should see __declspec(dllimport)
(ie the exe should import game
). So you want to define game
with __declspec(dllexport)
while building the dll and __declspec(dllimport)
while building the executable.
The typical way to conditionally have one or the other is by using a macro and changing its definition based on a preprocessor symbol which is different for the exe and the dll. For example (adapted from the official documentation ):
// Either in BaseGame.h or in a dedicated header
// that gets included by BaseGame.h.
#ifdef EXPORT_GAME_SYMBOLS
#define GAME_DECLSPEC __declspec(dllexport)
#else
#define GAME_DECLSPEC __declspec(dllimport)
#endif
// BaseGame.h
class GAME_DECLSPEC game
{ ... class definition ... };
While building the dll, you do define EXPORT_GAME_SYMBOLS
in the preprocessor ( -DEXPORT_GAME_SYMBOLS
). But while building the executable, you do not define EXPORT_GAME_SYMBOLS
.
The linker complains only about the destructor of game
because only the destructor has a non-inline implementation in your example. The default constructor is inline ( game() = default;
, meaning that an export is not necessary for the linker to find an implementation) and the other functions are pure (meaning that they do not have an implementation to begin with). Hence, an alternative here would be to completely remove the export/import stuff from game
and define the destructor inline, too ( virtual ~game() = default;
). But of course, if everything is inline, you do not need to build a shared library to begin with...
Side notes:
__declspec(dllexport)
on Application
makes no sense since it is in a .cpp
file of the dll, meaning that code in the executable can never see the definition of Application
. (Except if you #include "Application.cpp"
, which you shouldn't.) So no need to export the symbol. main()
in the header file is uncommon. You could move it eg to my_game.cpp
or a dedicated main.cpp
in your exe project.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.