Reputation: 244
I am trying to understand how the /FORCE:UNRESOLVED
linker flag is meant to be used on Windows. I would like to have a library built with unresolved symbols with the expectation that they will be filled in at runtime. I assumed that this could be accomplished by specifying the flag at link-time and then having the definition exist in an executable that loads this library. I did not expect there to be any difference between load-time and run-time dynamic linkage in the symbol resolution logic, but I attempted both just to be sure. In both cases, the existence of the undefined symbol causes the program to silently crash at the point when the dynamic library is loaded.
Here is a MWE demonstrating the issue (I've included the complete CMake for easy reproduction, but I don't think there's anything relevant in there). If I remove the power_six
function from the library and stop trying to call it, everything works as expected, but if the definition exists in the library then the main executable crashes when the library is loaded. Could someone explain what I am doing wrong, and how symbol resolution should work in this case? I've found good resources in the Windows documentation on library path resolution and the PE spec is indicative of how linkage is stored, but I don't see any information on symbol resolution rules in this case. Is the expectation that DllMain be implemented in such a way as to fill in the symbols somehow? If so, how should that be done?
// lib\lib.h
int square(int x);
int power_six(int x);
// lib\lib.c
#include "lib.h"
#include "windows.h"
// This must be provided by another file in the process space.
int cube(int x);
int square(int x) {
return x * x;
}
int power_six(int x) {
return cube(square(x));
}
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) {
// No-op included because the FORCE docs indicate that the entry point must
// be defined for this flag to have an effect.
}
# lib\CMakeLists.txt
cmake_minimum_required(VERSION 3.26.4 FATAL_ERROR)
project(lib VERSION 0.0.1 LANGUAGES C)
# For simplicity in testing, always export symbols on Windows
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
add_library(lib SHARED lib.c)
target_include_directories(lib PUBLIC "$<INSTALL_INTERFACE:include>" "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>")
target_link_options(lib PRIVATE "/FORCE:UNRESOLVED")
include(GNUInstallDirs)
install(
TARGETS lib
DESTINATION ${CMAKE_INSTALL_LIBDIR}
EXPORT ${PROJECT_NAME}-exports
)
install(
FILES lib.h
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)
include(CMakePackageConfigHelpers)
foreach(tree_type BUILD INSTALL)
if(tree_type STREQUAL "BUILD")
set(install_location ".")
else()
set(install_location "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}")
endif()
set(build_location "${PROJECT_BINARY_DIR}/${install_location}")
write_basic_package_version_file(
"${build_location}/${PROJECT_NAME}-config-version.cmake"
VERSION ${CMAKE_PROJECT_VERSION}
COMPATIBILITY AnyNewerVersion)
configure_package_config_file("${CMAKE_CURRENT_LIST_DIR}/cmake/config.cmake.in"
"${build_location}/${PROJECT_NAME}-config.cmake"
INSTALL_DESTINATION "${install_location}")
if(tree_type STREQUAL "BUILD")
export(EXPORT ${PROJECT_NAME}-exports
FILE "${PROJECT_NAME}-targets.cmake"
NAMESPACE ${PROJECT_NAME}::)
else()
install(DIRECTORY "${build_location}/" DESTINATION "${install_location}")
install(EXPORT ${PROJECT_NAME}-exports
DESTINATION "${install_location}"
FILE "${PROJECT_NAME}-targets.cmake"
NAMESPACE ${PROJECT_NAME}::)
endif()
endforeach()
// lib\cmake\config.cmake.in
@PACKAGE_INIT@
cmake_minimum_required(VERSION @CMAKE_MINIMUM_REQUIRED_VERSION@)
include("${CMAKE_CURRENT_LIST_DIR}/lib-targets.cmake" REQUIRED)
include("${CMAKE_CURRENT_LIST_DIR}/lib-config-version.cmake" REQUIRED)
set(${CMAKE_FIND_PACKAGE_NAME}_CONFIG "${CMAKE_CURRENT_LIST_FILE}")
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(${CMAKE_FIND_PACKAGE_NAME} CONFIG_MODE)
# executable\main.c
#include <stdio.h>
#include "windows.h"
// Here's the definition of cube that should be visible to lib.c
int cube(int x) {
return x * x * x;
}
// Option 1: Load-time linkage
/*#include "lib.h"*/
/*int main() {*/
/* int const x = 2;*/
/* int const x_2 = square(x);*/
/* printf("The square of %d is %d\n", x, x_2);*/
/* int const x_6 = power_six(x);*/
/* printf("The sixth power of %d is %d\n", x, x_2);*/
/*}*/
// Option 2: Run-time dynamic linking
typedef int (__cdecl *MYPROC)(int);
int main() {
int const x = 2;
int x_2 = -1;
int x_6 = -1;
char const * path_to_lib = ...;
HINSTANCE hinstLib;
MYPROC ProcAdd;
BOOL fFreeResult = FALSE;
printf("Attempting to load the library\n");
hinstLib = LoadLibrary(path_to_lib);
if (hinstLib != NULL)
{
printf("Found the lib\n");
ProcAdd = (MYPROC) GetProcAddress(hinstLib, "square");
if (NULL != ProcAdd)
{
printf("Found the function\n");
x_2 = (ProcAdd) (x);
}
ProcAdd = (MYPROC) GetProcAddress(hinstLib, "power_six");
if (NULL != ProcAdd)
{
printf("Found the function\n");
x_6 = (ProcAdd) (x);
}
fFreeResult = FreeLibrary(hinstLib);
}
printf("The square of %d is %d\n", x, x_2);
}
// executable\CMakeLists.txt
cmake_minimum_required(VERSION 3.26.4 FATAL_ERROR)
project(example VERSION 0.0.1 LANGUAGES C)
add_executable(example main.c)
find_package(lib)
target_link_libraries(example PRIVATE lib::lib)
include(GNUInstallDirs)
install(
TARGETS example
DESTINATION bin
EXPORT ${PROJECT_NAME}-exports
)
Update I asked about this in the Microsoft developer forum and the conclusion was that this is not possible.
Upvotes: 1
Views: 68