这个问题与这个问题直接相关;有人建议我不要将许多问题混为一谈,因此我将围绕该主题发布单独的问题。
我正在开发一个项目,其源代码是为 Linux 和 Windows 构建的。该项目最初是使用手写的 Makefile 构建的,因此共享库在 Linux 和 Windows 上都使用
.so
扩展构建。将项目迁移到基于 CMake
的构建后,在 Linux 上使用 .so
扩展构建共享库,在 Windows 上使用 .dll
扩展构建共享库。因此,应用程序在运行时会失败,因为它已被硬编码为带有 dlopen()
扩展的延迟加载 (.so
) 共享库。
我相信动态链接库和共享库之间的差异与当前需求无关,因为如果我将生成的
.dll
文件重命名为.so
,应用程序将成功运行完成。
我的问题是:是否有标准或最佳实践方法来处理Linux和Windows之间的共享库文件扩展名差异?我可以想象一个基于宏的解决方案,例如:
#ifdef SOMETHING
#define EXTENSION ".so"
#else
#define EXTENSION ".dll"
#endif
...
void* handle = dlopen("libfunc" EXTENSION, RTLD_NOW)
...但我想了解是否有广泛采用的最佳实践或普遍的惯例,或者完全正确的方法来处理这个问题。本质上,需求是“在 Windows 中,加载带有
.dll
扩展名的文件;在 Linux 中,加载带有 .so
扩展名的文件。”
(我假设为所有平台创建
Makefile
文件的手写 .so
是出于缺乏经验或方便等原因而这样做的,并且 CMake
通过在 Linux 中生成 .so
文件来执行正确或首选的操作,并且Windows 中的 .dll
文件。即,我假设开发人员有责任适应他在代码中使用 dlopen()
加载的共享库扩展的编译时或运行时条件)
以下是最小的可编译示例的资源:
// 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()
使用
make
编译并在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
$
使用
cmake
编译并在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
$
使用
make
编译并在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
$
使用
cmake
编译并在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
$
是否有标准或最佳实践方法来处理 Linux 和 Windows 之间的共享库文件扩展名差异?
您加载库的代码已经非常不同 - Windows 版本使用
LoadLibraryExA
,而 UNIX 版本使用 dlopen
。
处理平台差异的标准/最佳实践方法是不是在Windows上模拟
dlopen
(就像您的代码那样),而是提供特定于操作系统的load_library
函数(在不同的源文件中)。
也就是说,您的大多数应用程序并不关心差异,而只是调用
load_library("func");
。
unix.c
将 load_library()
实现为(为了清楚起见,省略了溢出检查):
void *load_library(const char *name)
{
char buf[1024] = "lib";
strcpy(buf + 3, name);
strcat(buf, ".so");
return dlopen(buf, ...); // dlopen("lib{name}.so")
}
而
windows.c
将其实现为:
void *load_library(const char *name)
{
char buf[1024];
strcpy(buf, name);
strcat(buf, ".dll");
return LoadLibraryExA(buf, NULL, 0); // LoadLibraryExA("{name}.dll")
}
然后您在
Makefile
中有一个选择器,它根据需要使用 unix.c
或 windows.c
。