简体   繁体   中英

Undefined reference when creating entry point in shared library clang

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:

  1. entry.h
  2. BaseGame.h
  3. Application.cpp

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:

  • All your header files should have header guards to prevent including the same file twice in one translation unit.
  • 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:

  • The __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.
  • As others have noted, having the 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM