Linking libraries with g++: I'm truly lost.

Hello. I'm new to C++, trying to implement the Dear ImGui library. I've been banging my head against this for about two days, so I think that the best way to go about this is not to list all the things I've tried (I've forgotten many of them) but to tell my understanding of what's involved in compilation, and pray that someone comes along and corrects whatever I've got wrong. So, to link a library with MinGW/gcc/g++, I must:

1) Include declarations of all relevant things in the code itself. This done by having #include "{something}.h" lines in my c++ files.
2) Tell GCC where to find my source files. This is done by giving a path without any symbols in front of it.
3) Tell GCC where to find header files. This is done by listing any number of directories to search, preceded by a "-I" symbol.
4) Tell GCC where to find the desired library. This is done by listing directories preceded by the "-L" symbol.
5) Tell GCC to actually use the desired libraries. This is done by the -l{libraryname} directive.
Steps 4 and 5 are only needed if the desired libraries have already been compiled into .a or .so files. I can opt to leave the libraries uncompiled and compile them along with my own source and header files via steps 1 and 2.

So, finally, I get something like this:
 
C:\raylib\w64devkit\bin\g++.exe -pedantic-errors -Wall -Wextra -fdiagnostics-color=always -g -I E:/Documents/Downloads/imgui-master -I E:/Documents/Downloads/imgui-master/backends E:\Documents\Downloads\imgui-master\examples\example_win32_directx11/\*.cpp -o E:\Documents\Downloads\imgui-master\examples\example_win32_directx11\main.exe

But no matter what I try, I still get "undefined reference" errors. Lots of them. Actually, it seems like GCC isn't linking any files at all. I'd be very grateful if someone would point out the gap in my understanding. I'm trying to learn C++ through a command-line compiler to get a better understanding of it, but if I can't figure this out then I'm just going to have to go to something like Code:::Blocks.
Last edited on
Make sure your backslashes and forward slashes go the right way.
Here's a complete example:
Get Dear ImGui from its git repository and change to its directory
QZPB\mjb D:\>git clone https://github.com/ocornut/imgui.git
Cloning into 'imgui'...
remote: Enumerating objects: 42932, done.
remote: Counting objects: 100% (328/328), done.
remote: Compressing objects: 100% (165/165), done.
remote: Total 42932 (delta 243), reused 213 (delta 163), pack-reused 42604 eceiving objects: 100% (42932/42932), 75.47 MiB | 2.99 MiB/s
Receiving objects: 100% (42932/42932), 76.27 MiB | 2.98 MiB/s, done.
Resolving deltas: 100% (32594/32594), done.

QZPB\mjb D:\>cd imgui

Now list the contents of the directory, keeping an eye out for files named README, INSTALL, BUILDING and the like. We're particularly interested in the README file, which needs to be read carefully and completely.
QZPB\mjb D:\imgui>dir /B
.editorconfig
.gitattributes
.github
.gitignore
backends
docs
examples
imconfig.h
imgui.cpp
imgui.h
imgui_demo.cpp
</snip>
imstb_truetype.h
LICENSE.txt
misc

There's no README file here, but there is a docs folder.

Before entering the docs folder to continue our search for a README, we can note that there are no subdirectories which conventionally contain source code. Such subdirectories are almost always named something like src, source, code, or include. Initially it looks like all the code in the project lives directly in this root directory.
QZPB\mjb D:\imgui>cd docs

QZPB\mjb D:\imgui\docs>dir /b
BACKENDS.md
CHANGELOG.txt
EXAMPLES.md
FAQ.md
FONTS.md
README.md
TODO.txt

In this case the README file lives in the docs directory; its name is README.md. Read it in its entirety, using any markdown viewer of your choice. Any plaintext editor will make do, but it is better to use one with support for markdown files.
QZPB\mjb D:\imgui\docs>emacsclient -c README.md
README.md says "No specific build process is required. You can add the .cpp files to your existing project." This is about as easy as it gets. It also tells us about the examples, so we can enter the examples directory and list its contents:
QZPB\mjb D:\imgui\docs>cd ..\examples\

QZPB\mjb D:\imgui\examples>dir /b
example_allegro5
</snip>
example_win32_directx9
imgui_examples.sln
libs
README.txt

There is another README file here, which should be read as well.
QZPB\mjb D:\imgui\examples>type README.txt
</snip>

You're trying to build the DX11 example, which seems fine, so we'll enter that directory too:
QZPB\mjb D:\imgui\examples>cd example_win32_directx11\

QZPB\mjb D:\imgui\examples\example_win32_directx11>dir /b
build_win32.bat
example_win32_directx11.vcxproj
example_win32_directx11.vcxproj.filters
main.cpp

There's no README file, but there is a shell script called build_win32.bat which presumably builds the example from scratch. The files example_win32_directx11.vcxproj and example_win32_directx11.vcxproj.filters have to do with Microsoft Visual Studio; you should be able to ignore these.

Let's read the shell script:
QZPB\mjb D:\imgui\examples\example_win32_directx11>type build_win32.bat
@REM Build for Visual Studio compiler. Run your copy of vcvars32.bat or vcvarsall.bat to setup command-line compiler.
@set OUT_DIR=Debug
@set OUT_EXE=example_win32_directx11
@set INCLUDES=/I..\.. /I..\..\backends /I "%WindowsSdkDir%Include\um" /I "%WindowsSdkDir%Include\shared" /I "%DXSDK_DIR%Include"
@set SOURCES=main.cpp ..\..\backends\imgui_impl_dx11.cpp ..\..\backends\imgui_impl_win32.cpp ..\..\imgui*.cpp
@set LIBS=/LIBPATH:"%DXSDK_DIR%/Lib/x86" d3d11.lib d3dcompiler.lib
mkdir %OUT_DIR%
cl /nologo /Zi /MD %INCLUDES% /D UNICODE /D _UNICODE %SOURCES% /Fe%OUT_DIR%/%OUT_EXE%.exe /Fo%OUT_DIR%/ /link %LIBS%

You're not using Microsoft's compiler, cl.exe, so this won't solve your problem, but thankfully this script is simple, much simpler than usual.

The script tells us a couple things: we must link d3d11.lib and d3dcompiler.lib, we must define _UNICODE and UNICODE during the build, and most importantly that all the source code is in the root directory, except for some files in the backends directory. The READMEs had stuff to say about this too.

From the contents of build_win32.bat, we can come up with an initial guess for a g++ invocation (sorry about the long lines):
QZPB\mjb D:\imgui\examples\example_win32_directx11>cd example_win32_directx11

QZPB\mjb D:\imgui\examples\example_win32_directx11>g++ -fno-strict-aliasing -g -DUNICODE -D_UNICODE -I ../.. -I ../../backends main.cpp ../../backends/imgui_impl_dx11.cpp ../../backends/imgui_impl_win32.cpp ../../imgui*.cpp -ld3d11 -ld3dcompiler -o example_win32_directx11.exe
</snip> 
undefined reference to `__imp_GetDeviceCaps'
</snip>
undefined reference to `DwmEnableBlurBehindWindow'
collect2.exe: error: ld returned 1 exit status

To fix the linker's complaints, read the error messages for the names of the missing symbols. The linker can't find a definition for the symbols because they're provided by libraries we haven't told it about. To fix it, go to MSDN and search - first for GetDeviceCaps and then for DwmEnableBlurBehindWindow:
https://docs.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-getdevicecaps
https://docs.microsoft.com/en-us/windows/win32/api/dwmapi/nf-dwmapi-dwmenableblurbehindwindow
On each of those web pages, scroll to the bottom and find where it says "Library: Gdi32.lib" and "Library: Dwmapi.lib", respectively.

This tells us we must link gdi32.lib and Dwmapi.lib. Add those to the link line.
QZPB\mjb D:\imgui\examples\example_win32_directx11>g++ -fno-strict-aliasing -g -DUNICODE -D_UNICODE -I ../.. -I ../../backends main.cpp ../../backends/imgui_impl_dx11.cpp ../../backends/imgui_impl_win32.cpp ../../imgui*.cpp -ld3d11 -ld3dcompiler -lgdi32 -ldwmapi -o example_win32_directx11.exe

In general you'd repeat that process, being extremely careful to select the proper versions of any needed libraries, until the linker shuts up.
Success! Now run ./example_win32_directx11.exe and make sure it doesn't crash.


Last edited on
This
example_win32_directx11/\*.cpp
/\ doesn't look right.

Steps 4 and 5 are only needed if the desired libraries have already been compiled into .a or .so files. I can opt to leave the libraries uncompiled and compile them along with my own source and header files via steps 1 and 2.
Are you doing this somewhere?
@mbozzi that might be the most comprehensive answer to a programing question I've ever received. Thank you. I'm unfamiliar with .bat files, but will dig into your guidance and return with my results.
@coder777 Since I'm getting the errors I'm getting, doesn't that mean that example_win32_directx11/\*.cpp must be working? Because the purpose of that part of the command is to direct the compiler at my source file, and the errors are coming from an attempt to compile (or link) that very file?

In response to your second comment: the Dear ImGui headers and source files are located in E:/Documents/Downloads/imgui-master and E:/Documents/Downloads/imgui-master/backends. I thought that by including these in my command, preceded by a "-I", I was instructing the compiler to compile them. Is that not true?
Flag -I doesn't cause the compiler to compile all of the source code in the specified directory. Instead it tells the preprocessor where to look for files that are included.

So for example, when the preprocessor encounters the directive
#include "xyz.h"
it will search for a file named xyz.h in the default locations as well as any directories specified with -I.

This is why I was careful to specify all the needed source files:
main.cpp 
../../backends/imgui_impl_dx11.cpp
../../backends/imgui_impl_win32.cpp
../../imgui*.cpp

In the last command that I posted.

https://gcc.gnu.org/onlinedocs/gcc/Directory-Options.html

Speaking of which, did the last command in my earlier post work?
Last edited on
In fact, #include "foo.h" looks for "foo.h" in the same directory as the current source file, then in the directories of the currently opened include files (in the reverse order in which they were opened), then in the directories given by -I option, and finally (if still not found) in the compiler's default include directories.

At the same time, #include <foo.h> looks for "foo.h" only in the directories given by -I option, and then in the compiler's default include directories. But, either way, -I only adds search paths for #include directives.


Usually, the .cpp that belong to some 3rd-party library are compiled and bundled to a static library (.a) just once. Then you can simply link that one .a file with your project, whenever you need to build.

You only need to do this one time (or when the library has actually changed):
1
2
3
g++ -c -o some/library/src/foo.o some/library/src/foo.cpp
g++ -c -o some/library/src/bar.o some/library/src/bar.cpp
ar rcs some/library/lib/libxyz.a some/library/src/foo.o some/library/src/bar.o

Then you can simply do this, whenever you need to build your program:
g++ -Isome/library/include -Lsome/library/lib -o program.exe main.cpp -lxyz
Last edited on
Yes, I did get it to work. It took a while, but I understand now what the problem was and why the fix works. It's surprisingly difficult to find novice-friendly discussions of g++ compiler usage online, so thank you for working through this with me. My only remaining questions are:

1) How does the compiler know where to find libraries like d3d11? I notice that in main.cpp, d3d11.h is mentioned in angle-brackets, implying it's part of the standard library. Is that how g++ can link it without being shown where it is? It would surprise me if something as platform-specific as directx would have a place in the standard library.

2) How common is it to encounter a library like this, where the things needed for compilation are listed implicitly in a .bat file or similar, rather than spelled out in a readme?
#include directives have nothing to do with linking libraries! Well, except for special "header only" libraries, but that is another topic. With "normal" libraries, the #include directive just includes the "header" file that defines the "public" API of the library. Not more. The library itself still needs to be linked, in the linker phase !!!

(angle-brackets only effect where the compiler searches for header files, as I pointed out before)

You need to use the -l (lower-case "L") option of the linker command to specify which libraries to link into your executable. Specifying -lfoo on the linker command-line will link the library file "libfoo.a" or "libfoo.so" (on Windows also "libfoo.lib" is possible). Additionally, the -L option can be used to specify the directories where the linker searches for libraries to be linked - in addition to the linker's default search directory.


Pretty much any library works like this: There is an "include" directory containing the header (.h) files that you need to #include in order to call the library from your own code; and there is a "lib" directory containing the actual library file (.a) that you are supposed to link into the executable file. And that is it.

If there are "loose" source files (.c or .cpp) but no read-to-use library (.a) file, then it means that the library has not been built yet. In that case, you need to build the library, producing the .a library file, before you can use it in your project. There should be some kind of Makefile that you can run in order to build the library.
Last edited on
d3d11.h is mentioned in angle-brackets, implying it's part of the standard library

Angle brackets doesn't mean "standard library".

The preprocessor has two search paths - one for angles and the other for quotes. By convention only, the angle bracket search path contains "system directories", which invariably contain standard library headers (if available), as well as any preinstalled, system-specific header files like d3d11.h.

How common is it to encounter a library like this, where the things needed for compilation are listed implicitly in a .bat file or similar, rather than spelled out in a readme?

The files that need to be compiled are actually described in the README.

Normally it would be much harder to understand the build scripts than it would be to read the documentation. But here the batch file was so (unusually) simple that it made more sense to look at that than to parse the instructions.

Whenever I answer similar questions I always stress the importance of reading all the READMEs, all the BUILDINGs, all the relevant documentation. In general if you don't follow the instructions exactly, the software will fail to build, sometimes hours into the process.

FWIW this kind of build is unfortunately rare. Its unusual simplicity is one of the reasons that I could actually build it on my machine without taking all day to do so.

kigar64551 wrote:
In fact, #include "foo.h" looks for "foo.h" in the same directory as the current source file, then in the directories of the currently opened include files (in the reverse order in which they were opened), then in the directories given by -I option, and finally (if still not found) in the compiler's default include directories.

The behavior of the #include directive's "search" is unspecified. In practice, the precise set of locations that are searched, and the order that they're checked, varies substantially depending on the implementation. The rules for GCC's preprocessor, the one OP is using, can be found here:
https://gcc.gnu.org/onlinedocs/cpp/Include-Syntax.html
Last edited on
Topic archived. No new replies allowed.