0

TL;DR: how can I make CMake-generated Makefiles create shared libraries with .so extensions in Windows instead of .dll? Put another way: how can I write a CMakeLists.txt that creates libfunc.so on both Linux and Windows, instead of libfunc.dll on the latter?

This is a simplification of a larger project. The main application uses dlopen() to dynamically load a shared library. Note that the in-code loaded shared library filename has a .so extension.

// main.c #include <dlfcn.h> #include <stdio.h> #include <stdlib.h> int main(int argc, char* argv[]) { int (*fptr)(void) = NULL; const char* filename = "libfunc.so"; // Nota bene void* handle = dlopen(filename, RTLD_NOW); if (! handle) { fprintf(stderr, "%s\n", dlerror()); exit(EXIT_FAILURE); } fptr = dlsym(handle, "func"); fprintf(stdout, "fptr returns %d\n", (*fptr)()); dlclose(handle); return 0; } 
// dlfcn.h #ifndef DLFCN_H #define DLFCN_H #include <windows.h> #define RTLD_NOW (1<<0) extern inline void *dlopen(const char *lib, int flags) { (void) flags; return LoadLibraryExA(lib, NULL, 0); } extern inline void *dlsym(void *handle, const char *name) { FARPROC fp = GetProcAddress((HINSTANCE) handle, name); return (void *)(intptr_t)fp; } extern inline char *dlerror() { // Not thread-safe! static char msg[1024]; msg[0] = '\0'; FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, GetLastError(), 0, msg, sizeof(msg), NULL); return msg; } extern inline int dlclose(void *handle) { return FreeLibrary(handle) ? 0 : -1; } #endif 
// func.c int func(void) { return 42; } 

This code is compiled for both Linux and Windows.

The original version of this project was built with handwritten makefiles, therefore the filename of the compiled shared library was controlled by a Makefile rule, e.g.:

.PHONY: all CFLAGS := ifeq ($(shell uname), Windows_NT) CFLAGS += -isystem . endif all: libfunc.so a.out libfunc.so: func.o cc -shared -fpic -o $@ $^ a.out: libfunc.so main.c cc -o $@ main.c ${CFLAGS} 

The above Makefile results in libfoo.so being created on both Linux and Windows, because make invokes the following line on both Linux and Windows:

cc -shared -fpic -o libfunc.so func.o 

The project is being migrated to a CMake-based build. This is the CMakeLists.txt that has been written:

cmake_minimum_required(VERSION 3.5) project(hello_world) add_library(func SHARED func.c) add_executable(a.out main.c) if(CMAKE_SYSTEM_NAME STREQUAL "Windows") include_directories(SYSTEM ${CMAKE_CURRENT_SOURCE_DIR}) endif() 

The CMake generator is "Unix Makefiles", and the Makefile generation and subsequent build are successful, however libfunc.dll is generated by the build instead of libfunc.so, therefore the executable fails at runtime, because it is trying to load a file named libfunc.so. If I rename libfunc.dll libfunc.so, the executable is able to run to completion successfully.

I've been told that CMake can be configured to either copy/rename files after their creation, e.g. copying/renaming libfunc.dll to libfunc.so, but not how to do this. So my question is: how can I configure my CMakeLists.txt to create libfunc.so both when the host platform is Linux and Windows? Either copying/renaming the .dll to .so after creation, or just outright creating a .so in the first place are both fine.


This answer informs the extent of my knowledge on the differences between dynamic link libraries and shared libraries. But I believe they are not relevant to my immediate need since, as stated above, renaming the .dll to .so fixes the reported problem.


I'm also aware that there may be other ways to handle this situation: e.g. some form of #ifdef macro to create a compile-time conditional on the shared library extension. But, per past guidance, I don't want to mix many questions into one post. I would like to use this post to learn about the ability to control shared library output filenames in a CMakeLists.txt. I will ask separately if there are best practices how to handle this situation.

2
  • 2
    Changing the extension is not a good idea. The extension for shared libraries is different from linux (.so) to Mac OS (.dylib) and Windows (.dll). For portable code, you will need to determine the proper shared library extension and generate the library filename as appropriate. Commented Jan 12, 2024 at 1:54
  • @BradLanam - I hadn't even considered Mac OS; thank you for pointing that out. FWIW, this is the follow-up question that effectively asks "if changing the extension is not a good idea, what is correct/prevailing convention how to handle it": stackoverflow.com/questions/77804067/… Commented Jan 12, 2024 at 2:41

2 Answers 2

1

You may change extension of the created library by setting SUFFIX property.

For avoid confusion, I would not use Linux-specific .so extension on Windows. You may choose any other extension, e.g. .mod.

Also note, that libraries loaded via dlopen mechanism should be created with MODULE keyword instead of SHARED.

Example:

# CMakeLists.txt add_library(func MODULE func.c) set_target_properties(func PROPERTIES SUFFIX ".mod") 
# Module usage void* handle = dlopen("func.mod", RTLD_NOW); 
Sign up to request clarification or add additional context in comments.

5 Comments

Is this distinction between MODULE and SHARED a CMake concept? I'm not expert here, but broadly speaking, when I've created shared libraries in the past, the command line is something along the lines of cc -fpic -shared .... I.e. I don't see anything in the cc command line that would create two "kinds" of shared libraries. I read this answer: stackoverflow.com/a/4968940/5437543 but I'm still not sure I understand the distinction -- when/why would you create a "shared library" that you "cannot link to" (according to the linked answer)?
If a "regular" shared library uses symbol Foo, then it should be linked with the library which provides (defines) given symbol. Failing to do so will be resulted in "undefined reference" error when create the library. But a library used via dlopen may perfectly have undefined symbols. These symbols could be defined by the application which loads the library. So, creation of such library should differ from the creation of the regular one.
Your comment helped me put together a couple of disparate notions in my head -- thank you. One nice thing about this answer: you can combine it with "CMake Presets", which I learned about recently (stackoverflow.com/a/72004984/5437543) so you only need to add one "cacheVariables": { "CMAKE_SHARED_LIBRARY_SUFFIX_C": ".mod" } statement in the top-level CMakePresets.json (instead of adding multiple set_target_properties(func PROPERTIES SUFFIX ".mod" statements) for the many individual targets that your project may have).
A question about between modules vs. "regular" shared libraries came to mind: if you are creating a shared library, how do you know how other developers are going to load it? I.e. it seems equally valid to load a shared library with dlopen() or to directly calling that library's functions (and therefore needing to link to it). So as the shared library author, how would you know whether you "should" compile your code as a shared library or as a module?
"how do you know how other developers are going to load it?" - As a developer of a library I decide whether the library is intended to be linked with or not. If I develop a library with intention to load it at runtime (with dlopen) and document that, but someone links with that library.. well, this is not my fault. (BTW, CMake forbids to link with a MODULE library).
1

I use the following. In CMakeLists.txt:

set (SHLIB_EXT ${CMAKE_SHARED_LIBRARY_SUFFIX}) ... configure_file (config.h.in config.h) 

In config.h.in:

#cmakedefine SHLIB_EXT "${SHLIB_EXT}" 

The SHLIB_EXT #define is determined automatically by cmake and is available for use by my modules as config.h is included by every module. Paths passed to the dynamic library loader are constructed using this #define.

Edit:

The configure_file directive converts an input file to an output file. Within the input file, there can be certain cmake directives such as #cmakedefine or #cmakedefine01, and also substitutions such as the ${SHLIB_EXT} in the example above.

All the checks for header files and libraries is done within cmake, and those checks are preserved for use by the C code by creating a config.h file.

Excerpts from cmake files:

check_include_file (windows.h _hdr_windows) check_include_file (dlfcn.h _hdr_dlfcn) check_function_exists (LoadLibraryW _lib_LoadLibraryW) check_function_exists (dlopen _lib_dlopen) 

Excerpts from my config.h.in:

#ifndef INC_CONFIG_H #define INC_CONFIG_H #cmakedefine SHLIB_EXT "${SHLIB_EXT}" #cmakedefine BDJ4_VERSION ${BDJ4_VERSION} #cmakedefine01 _hdr_dlfcn #cmakedefine01 _hdr_windows ... /* windows specific */ #cmakedefine01 _lib_LoadLibraryW ... #cmakedefine01 _lib_dlopen 

Then in my dynamic library loader module, I can check for the system capabilities (not for which system) and header files.

#include "config.h" ... #if _hdr_dlfcn # include <dlfcn.h> #endif #if _hdr_windows # define WIN32_LEAN_AND_MEAN 1 # include <windows.h> #endif ... dlhandle_t * dylibLoad (const char *path) { void *handle = NULL; #if _lib_LoadLibraryW HMODULE libhandle; char npath [MAXPATHLEN]; wchar_t *wpath; #endif if (path == NULL || ! *path) { return NULL; } #if _lib_dlopen handle = dlopen (path, RTLD_LAZY); #endif #if _lib_LoadLibraryW ... 

Finding projects that are written to be cross-platform and looking at their CMake setup is useful. You can find different ways of configuring cross-platform compilation and setup.

2 Comments

Can you explain a bit more about .in files and their relationship to CMake and/or .h files? I'm not familiar with this paradigm, and I don't quite understand the flow what feeds into what in your example.
@StoneThrow Added a bunch of info. Hopefully it makes sense. It has become a pretty broad question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.