0

Overview

  • I want to compile a project CalcMod using a shared library XMMathLib.
  • Both should be loaded as projects in Visual Studio (ie in the Solution Explorer, there should be a tree for CalcMod, and a tree for XMMathLib). Hence nesting XMMathLib within CalcMod (and adding it with add_subdirectory()) seems to be the apropriate solution.
  • Also, eventually, we want to export XMMathLib as a (shared) dll library, hence we call add_library(XMMathLib SHARED […]).
  • However, this triggers a cannot open file 'XMMathLib\Debug\XMMathLib.lib' error at the end of compilation.
    Indeed, the folder build\XMMathLib\Debug\ contains XMMathLib.dll, XMMathLib.ilk and XMMathLib.pdb, which is consistent with me asking to generate a shared library for XMMathLib. But then, why does CalcMod look for static library file XMMathLib.lib?!

Details

Here is how the project is organised:

 |- build
 |- src
     |- CalcMod
     |   |- src
     |       |- main.cpp
     |- XMMathLib
     |   |- src
     |   |   |- Demo.cpp
     |   |   |- Demo.h
     |   |- CMakeLists.txt
     |- CMakeLists.txt

And here is the content of each file:

src/CMakeLists.txt

cmake_minimum_required(VERSION 3.22 FATAL_ERROR)
project(CalcMod CXX)
set(CMAKE_CXX_STANDARD 14)
add_subdirectory(XMMathLib)

file(GLOB_RECURSE SOURCES_CALCMOD "CalcMod/src/*.cpp")
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${SOURCES_CALCMOD})
add_executable(
    CalcMod
    ${SOURCES_CALCMOD}
    )
target_link_libraries(
    CalcMod
    XMMathLib
    )
target_include_directories(CalcMod PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})

src/XMMathLib/CMakeLists.txt

cmake_minimum_required(VERSION 3.22 FATAL_ERROR)
project(XMMathLib CXX)
file(GLOB_RECURSE SOURCES "src/*.cpp")
add_library(XMMathLib SHARED ${SOURCES})
target_link_libraries(
    XMMathLib
    )

src/CalcMod/src/main.cpp

#include <iostream>
#include "XMMathLib/src/Demo.h"

int main()
{
    RunDemo();
}

src/XMMathLib/src/Demo.cpp

#include <iostream>

void RunDemo()
{
    std::cout << "hello" << std::endl;
}

src/XMMathLib/src/Demo.h

#pragma once
void __declspec(dllexport) RunDemo();

cmake parameters:

  • Visual Studio 2017
  • Platform: Win32
  • Windows SDK 10.0.19041.0
Gael Lorieul
  • 3,006
  • 4
  • 25
  • 50
  • 1
    A dll in msvc is supposed to create an import library with a .lib extension if it exports any symbols. You link to this .lib file to use the dll. There should be a macro that switches between `__declspec(dllexport)` and `__declspec(dllimport)` depending on if you are building the dll or using it. – drescherjm Jan 03 '22 at 15:37
  • Hi @drescherjm, thanks for your comment :) So this means that the `__declspec(dllexport)` statement was not found? (And that a *.lib file is not exactly a static library in the eg Linux sense?) – Gael Lorieul Jan 03 '22 at 15:40
  • 1
    Your dll should also generate a .exp file. – drescherjm Jan 03 '22 at 15:41
  • 1
    The dll export macro is like this: [https://stackoverflow.com/questions/14980649/macro-for-dllexport-dllimport-switch](https://stackoverflow.com/questions/14980649/macro-for-dllexport-dllimport-switch) – drescherjm Jan 03 '22 at 15:42
  • 1
    Alternatively cmake allows you to export all symbols from a dll without the need of using __declspec(dllexport): [https://www.kitware.com/create-dlls-on-windows-without-declspec-using-new-cmake-export-all-feature/](https://www.kitware.com/create-dlls-on-windows-without-declspec-using-new-cmake-export-all-feature/) – drescherjm Jan 03 '22 at 15:45
  • Ok, making some progress: (i) Adding `#include "Demo.h"` to `src/XMMathLib/src/Demo.cpp` fixes the `cannot open file 'XMMathLib\Debug\XMMathLib.lib'` error (ii) Changed `file(GLOB_RECURSE SOURCES "src/*.cpp")` to `file(GLOB_RECURSE SOURCES "src/*.cpp" "src/*.hpp" "src/*.h")` so that the header files appear in the Solution Explorer. Now I have error `can't execute code because XMMathLib.dll could not be found` (my translation from French). Did not try to address error yet… – Gael Lorieul Jan 03 '22 at 15:50
  • Note: `build\XMMathLib\Debug` contains both `XMMathLib.dll` and `XMMathLib.lib` (as well as `XMMathLib.exp`, `XMMathLib.ilk` and `XMMathLib.pdb`) On the other hand `build\Debug` contains `CalcMod.exe`, `CalcMod.ilk` and `CalcMod.pdb` but no `XMMathLib.dll` which is why it can't find it. How do I bring it there?… – Gael Lorieul Jan 03 '22 at 15:52
  • 1
    ***Now I have error can't execute code because XMMathLib.dll could not be found*** Copy the .dll to the same folder as your executable or set your PATH environment variable to include the path containing the dll. – drescherjm Jan 03 '22 at 15:53
  • @drescherjm Do I have to do that manually?! Isn't there a way for Visual Studio to do it automatically? – Gael Lorieul Jan 03 '22 at 15:54
  • 1
    You can do that with CMake scripting. – drescherjm Jan 03 '22 at 15:55
  • With a call to `add_custom_command()`? – Gael Lorieul Jan 03 '22 at 15:55
  • 1
    That is one way. Here is a related question: [https://stackoverflow.com/questions/14089284/copy-all-dlls-that-an-executable-links-to-to-the-executable-directory](https://stackoverflow.com/questions/14089284/copy-all-dlls-that-an-executable-links-to-to-the-executable-directory) – drescherjm Jan 03 '22 at 15:56
  • I confirm that after copying the XMMathLib.dll (and its friends) from `build\XMMathLib\Debug\` to `build\Debug\`, the program can be run properly. – Gael Lorieul Jan 03 '22 at 15:56
  • Related: [https://coderedirect.com/questions/55353/how-to-copy-dll-files-into-the-same-folder-as-the-executable-using-cmake](https://coderedirect.com/questions/55353/how-to-copy-dll-files-into-the-same-folder-as-the-executable-using-cmake) – drescherjm Jan 03 '22 at 15:57
  • Ok, I have two possible working solutions, but not 100% happy with either… First solution uses `$` to copy the dll of all dependencies to `build\Debug\` . But it leaves behind all non-.dll files including .pdb files which would come handy for debug. Second solution copies the content of `build\XMMathLib\Debug\` to `build\Debug\` but in the case of more than one library similar to XMMathLib it would require a separate call for each library… What's the best approach? Is there a better alternative yet? – Gael Lorieul Jan 03 '22 at 16:28
  • Full command for first solution: `add_custom_command(TARGET CalcMod POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy $ $ COMMAND_EXPAND_LISTS )` – Gael Lorieul Jan 03 '22 at 16:29
  • Full command for second solution: `add_custom_command(TARGET CalcMod POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory $ $ COMMAND_EXPAND_LISTS )` – Gael Lorieul Jan 03 '22 at 16:29
  • `set_target_properties(CalcMod PROPERTIES VS_DEBUGGER_ENVIRONMENT "PATH=$;$ENV{PATH}")` should allow you to run the exe from Visual Studio.You may want to add `install()` commands that install the dlls and the exe to the same directory though,if you want to be able to run the tool from the command line;this allows you to use `cmake --install ...` to copy some files to a directory that can be specified during configuration via `CMAKE_INSTALL_PREFIX` or by specifying `--prefix ...` when running `cmake --install`; functionality for installing pdb files is also available – fabian Jan 03 '22 at 18:30
  • Hi @fabian :) But if I want to run the program with the VS debugger, it would fail, right? (Because `cmake --install` won't be triggered and XMMathLib.dll is separate from CalcMod.exe) – Gael Lorieul Jan 03 '22 at 20:10
  • No, it wouldn't, assuming the dll wasn't moved after the build;The `VS_DEBUGGER_ENVIRONMENT` environment corresponds to `Configuration Properties > Debugging > Environment` in the properties of the VS project corresponding to the target,i.e. when running the executable, the `PATH` environment variable should contain the directory containing the dll built by the project as first entry and dlls are searched in the paths specified in the `PATH` environment variable. The part regarding installing is only relevant, if you want to provide functionality to copy the build results to a given directory. – fabian Jan 04 '22 at 18:07

1 Answers1

0

Many thanks to all of you for helping me out, especially drescherjm !

Below is the fixed variant of my originally broken code:

 |- build
 |- src
     |- CalcMod
     |   |- src
     |       |- main.cpp
     |- XMMathLib
     |   |- src
     |   |   |- Demo.cpp
     |   |   |- Demo.h
     |   |   |- DllExport.h
     |   |- CMakeLists.txt
     |- CMakeLists.txt

And here is the content of each file:

src/CMakeLists.txt

cmake_minimum_required(VERSION 3.22 FATAL_ERROR)
project(CalcMod CXX)
set(CMAKE_CXX_STANDARD 14)

add_subdirectory(XMMathLib)  # Call XMMathLib's CMakeLists.txt

file(GLOB_RECURSE SOURCES_CALCMOD "CalcMod/src/*.cpp" "CalcMod/src/*.h" "CalcMod/src/*.hpp")  # Files to compile. Also all the files that will appear in VS's Solution Explorer (that's why we include the headers here).
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${SOURCES_CALCMOD})  # So that source files appear in Solution Explorer as a tree reproducing their actual location
add_executable(  # Ask VS to generate a CalcMod.exe executable as output
    CalcMod
    ${SOURCES_CALCMOD}
    )
target_link_libraries(  # Tell VS's linker to link with XMMathLib.dll
    CalcMod
    PUBLIC XMMathLib
    # PRIVATE: CalcMod uses XMMathLib and noone else
    # INTERFACE: CalcMod does not use XMMathLib but makes it available to others
    # PUBLIC: CalcMod uses XMMathLib *and* makes it available to others.
    )
target_include_directories(CalcMod PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})  # Add include directories (ie gcc's -I option)

# We want to copy XMMathLib.dll at the same location than CalcMod.exe, otherwise it won't run because it won't find it.

# Option #1: Copy the dlls of all the dependent libraries (only XMMathLib in this case) to where CalcMod.exe is
# Pros: Only copy the useful files (ie the *.dll) leaving behind eg the *.ilk and *.exp
#       No need to specify explicitly all dependent libraries
# Cons: Does not copy the *.pdb which is required by the debuger (contains the debug symbols)
# Copy eg XMMathLib.dll from `build\XMMathLib\Debug` to `build\Debug
#add_custom_command(TARGET CalcMod POST_BUILD
#  COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_RUNTIME_DLLS:CalcMod> $<TARGET_FILE_DIR:CalcMod>
#  COMMAND_EXPAND_LISTS
#  )

# Option #2: Copy all the output files (*.dll, *.pdb, *.ilk, *.exp) generated by XMMathLib where CalcMod.exe
# Pros: Copies *.pdb which is required by the debbuger (contains the debug symbols)
# Cons: Have to manually list all the dependent libraries explicitly (only one in this case: XMMathLib)
#       Also copies *.ilk and *.exp which are used for building/linking but are just bloat after that.
add_custom_command(TARGET CalcMod POST_BUILD
  COMMAND ${CMAKE_COMMAND} -E copy_directory $<TARGET_FILE_DIR:XMMathLib> $<TARGET_FILE_DIR:CalcMod>
  COMMAND_EXPAND_LISTS
  )

src/XMMathLib/CMakeLists.txt

cmake_minimum_required(VERSION 3.22 FATAL_ERROR)

project(XMMathLib CXX)

file(GLOB_RECURSE SOURCES "src/*.cpp" "src/*.hpp" "src/*.h")  # List sources to use in build

add_compile_definitions(XMMATHLIB_EXPORTS)  # When defined, export.h will use __declspec(dllexport)

add_library(XMMathLib SHARED ${SOURCES})  # Ask VS to build XMMathLib.dll

target_link_libraries(  # Tell VS's linker to link with libraries. Actually unnecessary here (no library linked)
    XMMathLib
    )

src/CalcMod/src/main.cpp

#include <iostream>

#include "XMMathLib/src/Demo.h"

int main()
{
    RunDemo();
}

src/XMMathLib/src/Demo.cpp

#include <iostream>
#include "Demo.h"  // We need to include Demo.h, otherwise VS won't see __declspec(dllexport) for RunDemo() and won't export the function

void RunDemo()
{
    std::cout << "hello" << std::endl;
}

src/XMMathLib/src/Demo.h

#include "DllExport.h"

void XMMATHLIB_DLLEXPORT RunDemo();

src/XMMathLib/src/DllExport.h

#pragma once

// Tells VS whether to specify the functions and classes marked with XMMATHLIB_DLLEXPORT
// as to be exported in the *.lib that accompanies the *.dll or not.
// If no class or function is marked with __declspec(dllexport), then XMMathLib.lib won't be generated.

#if XMMATHLIB_EXPORTS
    #define XMMATHLIB_DLLEXPORT __declspec(dllexport)
#else
    #define XMMATHLIB_DLLEXPORT __declspec(dllimport)
#endif

cmake parameters:

  • Visual Studio 2017
  • Platform: Win32
  • Windows SDK 10.0.19041.0
Gael Lorieul
  • 3,006
  • 4
  • 25
  • 50