c++visual-c++linkerlinker-errors

Why does MSVC Linker (link.exe) ignore libraries passed via /LIBPATH flag?


Okay so I'm going through hell trying to compile a program on Windows that compiles quite easily on Linux and I've narrowed it down to a single command generated by CMake whose linker flag that doesn't seem to do what it's supposed to.

Here are the steps I'm doing and where it's going wrong:

I'm using MSVC compiler via the commandline, cl.exe:

Microsoft (R) C/C++ Optimizing Compiler Version 19.40.33812 for x86
Copyright (C) Microsoft Corporation.  All rights reserved.

usage: cl [ option... ] filename... [ /link linkoption... ]

and link.exe:

Microsoft (R) Incremental Linker Version 14.40.33812.0
Copyright (C) Microsoft Corporation.  All rights reserved.

 usage: LINK [options] [files] [@commandfile]

<... some more helptext down here ...>

to compile a simple C++ program that uses SDL2 to display a Window. Here is a minimum reproducible code which I confirmed compiles and runs:

#define SDL_MAIN_HANDLED
#include <SDL.h>
#include <Windows.h>

using namespace std;

int main(int argc, char **argv) {
    SDL_SetMainReady();

    // Initialize SDL
    if (SDL_Init(SDL_INIT_VIDEO) != 0) {
        return 1;
    }

    // Create a window
    SDL_Window *window = SDL_CreateWindow("SDL2 Window",
                                          SDL_WINDOWPOS_CENTERED,
                                          SDL_WINDOWPOS_CENTERED,
                                          640, 480, SDL_WINDOW_SHOWN);

    if (window == nullptr) {
        SDL_Quit();
        return 1;
    }

    // Event loop
    bool isRunning = true;
    SDL_Event event;
    while (isRunning) {
        while (SDL_PollEvent(&event) != 0) {
            if (event.type == SDL_QUIT) {
                isRunning = false;
            }
        }
    }

    // Cleanup
    SDL_DestroyWindow(window);
    SDL_Quit();

    return 0;
}

Pretty straightforward, nothing complicated.


To compile this, I run the following:

cl main.cpp -I "C:/SDL2/include" /c

Which compiles the program into main.obj. The /c flag is so that it doesn't link it.

Then to link, I run link.exe and pass the path to my library and here's where it goes wrong.

link.exe main.obj /LIBPATH:C:/SDL2/lib/x86/SDL2.lib

Microsoft (R) Incremental Linker Version 14.40.33812.0
Copyright (C) Microsoft Corporation.  All rights reserved.

main.obj : error LNK2019: unresolved external symbol _SDL_SetMainReady referenced in function _main
main.obj : error LNK2019: unresolved external symbol _SDL_CreateWindow referenced in function _main
main.obj : error LNK2019: unresolved external symbol _SDL_DestroyWindow referenced in function _main
main.obj : error LNK2019: unresolved external symbol _SDL_PollEvent referenced in function _main
main.obj : error LNK2019: unresolved external symbol _SDL_Init referenced in function _main
main.obj : error LNK2019: unresolved external symbol _SDL_Quit referenced in function _main
main.exe : fatal error LNK1120: 6 unresolved externals

No error messages, nothing, it's as if I didn't pass the library at all (and yes I tried that too but same error!)

I tried running in /VERBOSE and after searching through the 1,338 lines of logs, nowhere is SDL2.lib even mentioned. And yes, I ran BOTH with and without the LIBPATH supplied and then diffchecked the two produced logs and they are literally the same.

Bizarrely when I instead run the linker like this:

link.exe main.obj /LIBPATH C:/SDL2/lib/x86/SDL2.lib

It works! It produced a main.exe and when run shows a blank SDL window as expected. In case you didn't notice, the only difference was me removing a colon : and adding a space.

The most beautiful blank window I've ever seen...

(PS: Make sure to include SDL2.dll in the same folder in case you're testing this on your own)

So for some reason, when passed without the colon and space, it works as expected. However, Microsoft's OWN DOCUMENTATION show the following:

Lies

And somewhat more confusingly, when run without arguments, the link.exe program says the same exact thing:

/LIBPATH:dir

Except when I follow its own instructions, it doesn't work 😭.

So my question is, why is it doing that? Is this a bug? Am I using it incorrectly?

I'm actually not the one to use link.exe, it's CMake that actually generates that - technically correct - call to the linker. So in that case, how come this hasn't been fixed yet and can I do something to make CMake produce the correct output? The fact that I haven't found similar questions online suggests to me that I'm doing something wrong.

Are there any workarounds or can I straight up not use CMake with link.exe?

Sorry if I sound a bit hysterical I've been stuck on this for 3 days and can feel my sanity slipping away


Solution

  • The /LIBPATH option specifies a directory to be searched for a library, it should not include the name of the library itself. Library names are given along with other input files to the linker command.

    Using the example given

    link.exe main.obj /LIBPATH:C:/SDL2/lib/x86/SDL2.lib
    

    should be

    link.exe main.obj SDL2.lib /LIBPATH:C:/SDL2/lib/x86
    

    In other words /LIBPATH operates in a similar way to the -L option of gcc.