0

This question is directly related to this one; I've been advised to not mix many questions into one, so I'm posting individual questions around this topic.

I am working on a project whose source is built for both Linux and Windows. The project was originally built with handwritten Makefiles, because of which shared libraries are built with .so extensions both on Linux and Windows. After migrating the project to a CMake-based build, shared libraries are build with .so extensions on Linux, and .dll extensions on Windows. Because of this, the application fails at runtime because it has been hardcoded to lazy-load (dlopen()) shared libraries with .so extensions.

I believe that the differences between dynamic link libraries and shared libraries are not relevant to the immediate need because if I rename the generated .dll files to .so, the application runs to completion successfully.

My question is: is there standard or best-practice way to deal with the shared library filename extension difference between Linux and Windows? I can imagine a macro-based solution, e.g.:

#ifdef SOMETHING #define EXTENSION ".so" #else #define EXTENSION ".dll" #endif ... void* handle = dlopen("libfunc" EXTENSION, RTLD_NOW) 

...but I'd like to learn if there is a widely-adopted best practice or prevailing convention, or outright correct way to handle this. In essence, the need is "when in Windows, load files with .dll extension; when in Linux, load files with .so extension."

(I am assuming that handwritten Makefile that created .so files for all platforms did so out of inexperience or convenience, etc. and that CMake is doing the correct or preferred thing by generating .so files in Linux and .dll files in Windows. I.e. I'm assuming that the onus is on the developer to accommodate compile-time or runtime conditionality on the extension of shared libraries he loads with dlopen() in his code)


Below are resources for a minimal, compilable example:

// 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; } 
// func.c int func(void) { return 42; } 
# Makefile .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} 
# CMakeLists.txt 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() 

Compiling with make and running on Linux:

$ make cc -c -o func.o func.c cc -shared -fpic -o libfunc.so func.o cc -o a.out main.c $ LD_LIBRARY_PATH=$LD_LIBRARY_PATH:. ./a.out fptr returns 42 $ 

Compiling with cmake and running on Linux:

$ cmake -B build && cmake --build build -- The C compiler identification is GNU 11.4.0 -- The CXX compiler identification is GNU 11.4.0 -- Detecting C compiler ABI info -- Detecting C compiler ABI info - done -- Check for working C compiler: /usr/bin/cc - skipped -- Detecting C compile features -- Detecting C compile features - done -- Detecting CXX compiler ABI info -- Detecting CXX compiler ABI info - done -- Check for working CXX compiler: /usr/bin/c++ - skipped -- Detecting CXX compile features -- Detecting CXX compile features - done -- Configuring done -- Generating done -- Build files have been written to: /home/user/dev/shared-lib-test/build [ 25%] Building C object CMakeFiles/func.dir/func.c.o [ 50%] Linking C shared library libfunc.so [ 50%] Built target func [ 75%] Building C object CMakeFiles/a.out.dir/main.c.o [100%] Linking C executable a.out [100%] Built target a.out $ $ LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD/build ./build/a.out fptr returns 42 $ 

Compiling with make and running in w64devkit:

$ make cc -isystem . -c -o func.o func.c cc -shared -fpic -o libfunc.so func.o cc -o a.out main.c -isystem . $ $ ./a.out fptr returns 42 $ 

Compiling with cmake and running in w64devkit:

$ cmake -G "Unix Makefiles" -B build && cmake --build build -- The C compiler identification is GNU 13.2.0 -- The CXX compiler identification is GNU 13.2.0 -- Detecting C compiler ABI info -- Detecting C compiler ABI info - done -- Check for working C compiler: C:/Program Files/w64devkit/bin/cc.exe - skipped -- Detecting C compile features -- Detecting C compile features - done -- Detecting CXX compiler ABI info -- Detecting CXX compiler ABI info - done -- Check for working CXX compiler: C:/Program Files/w64devkit/bin/c++.exe - skipped -- Detecting CXX compile features -- Detecting CXX compile features - done -- Configuring done (3.1s) -- Generating done (0.1s) -- Build files have been written to: C:/Users/user/dev/shared-lib-test/build [ 25%] Building C object CMakeFiles/func.dir/func.c.obj [ 50%] Linking C shared library libfunc.dll [ 50%] Built target func [ 75%] Building C object CMakeFiles/a.out.dir/main.c.obj [100%] Linking C executable a.out.exe [100%] Built target a.out $ $ ./build/a.out.exe The specified module could not be found. $ mv ./build/libfunc.dll ./build/libfunc.so $ $ ./build/a.out.exe fptr returns 42 $ 

1 Answer 1

1

is there standard or best-practice way to deal with the shared library filename extension difference between Linux and Windows?

Your code to load the library is already very different -- the Windows version uses LoadLibraryExA, while the UNIX version uses dlopen.

The standard/best practice way to handle platform differences is not to emulate dlopen on Windows (as your code does), but instead to provide OS-specific load_library function (in different source files).

That is, most of your application doesn't care about the differences, and just calls load_library("func");.

The unix.c implements load_library() as (overflow checking omitted for clarity):

void *load_library(const char *name) { char buf[1024] = "lib"; strcpy(buf + 3, name); strcat(buf, ".so"); return dlopen(buf, ...); // dlopen("lib{name}.so") } 

while windows.c implements it as:

void *load_library(const char *name) { char buf[1024]; strcpy(buf, name); strcat(buf, ".dll"); return LoadLibraryExA(buf, NULL, 0); // LoadLibraryExA("{name}.dll") } 

You then have a selector in the Makefile which uses unix.c or windows.c as appropriate.

Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.