共享对象子系统(CategorySharedObject)
SDL 提供了一套与系统无关的共享库加载接口,用于在运行时动态加载可执行代码模块——这类模块在不同系统中有不同的称呼:Windows 下称为「DLL」,Linux 下称为「共享库(.so)」,macOS 下称为「动态库(.dylib)」等。
使用流程:
- 编译生成共享库文件;
- 调用
SDL_LoadObject()加载该共享库; - 通过
SDL_LoadFunction()从加载的共享库中查找并获取导出符号(函数)的地址; - 使用完毕后,调用
SDL_UnloadObject()卸载共享库并释放资源。
注意事项
- 这些函数仅支持 C 风格函数名:其他语言(如 C++)的函数名会发生「名字改编(name mangling)」,且不同编译器的处理方式不同,无法直接加载;
- 函数指针调用约定需匹配:声明的函数指针必须与共享库中实际函数的调用约定(如 cdecl、stdcall)一致,否则程序会莫名崩溃;
- 避免命名空间冲突:从共享库加载的符号是否进入应用全局命名空间是未定义行为,若与代码或其他共享库的符号冲突,会导致预期外的结果;
- 卸载后指针失效:共享库卸载后,所有通过
SDL_LoadFunction()获取的函数指针都会失效(即使后续重新加载该库);若计划后续使用这些指针,请勿卸载库(尤其注意不要将这类指针传给atexit(),可能导致库卸载后调用失效指针)。
函数
- SDL_LoadObject:加载指定路径的共享库(DLL/.so/.dylib),返回 SDL_SharedObject 句柄(加载失败返回 NULL,可通过 SDL_GetError() 获取错误信息)
- SDL_LoadFunction:从已加载的共享库中查找指定名称的导出函数,返回函数指针(查找失败返回 NULL)
- SDL_UnloadObject:卸载已加载的共享库,释放相关资源(传入 NULL 无操作)
数据类型
- SDL_SharedObject:共享库句柄类型(本质是平台相关的指针类型,用于标识已加载的共享库)
结构体
- (无)
枚举
- (无)
宏
- (无)
FreeBASIC 示例代码
' 引入 SDL 相关声明(需确保 FreeBASIC 已链接 SDL3 库)
#Include "SDL.bi"
' 补充类型和函数声明(FreeBASIC 绑定可能缺失)
Type SDL_SharedObject As Any Ptr
Declare Function SDL_LoadObject CDecl (ByVal path As ZString Ptr) As SDL_SharedObject
Declare Function SDL_LoadFunction CDecl (ByVal so_handle As SDL_SharedObject, ByVal name As ZString Ptr) As Any Ptr
Declare Sub SDL_UnloadObject CDecl (ByVal so_handle As SDL_SharedObject)
' 定义共享库中导出函数的指针类型(需与实际函数签名一致)
' 示例1:无参数无返回值的函数
Type FuncVoid_Void As Sub()
' 示例2:int 参数 + int 返回值的函数
Type FuncInt_Int As Function(ByVal value As Integer) As Integer
' 示例3:字符串参数 + 字符串返回值的函数
Type FuncStr_Str As Function(ByVal str As ZString Ptr) As ZString Ptr
' 加载共享库并调用导出函数
Sub LoadAndCallSharedLib()
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "【加载并调用共享库】")
' 1. 定义共享库路径(根据平台适配)
Dim As ZString * 256 libPath
#If Defined(SDL_PLATFORM_WINDOWS)
libPath = "mylib.dll" ' Windows 下的 DLL 路径
#Elif Defined(SDL_PLATFORM_LINUX)
libPath = "./libmylib.so" ' Linux 下的共享库路径
#Elif Defined(SDL_PLATFORM_MACOS)
libPath = "./libmylib.dylib" ' macOS 下的动态库路径
#Else
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "不支持的平台")
Exit Sub
#EndIf
' 2. 加载共享库
Dim As SDL_SharedObject libHandle = SDL_LoadObject(@libPath)
If (libHandle = NULL) Then
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "加载共享库失败:%s", SDL_GetError())
Exit Sub
End If
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "共享库 %s 加载成功", libPath)
' 3. 查找并调用第一个导出函数:void HelloWorld()
Dim As Any Ptr funcPtr1 = SDL_LoadFunction(libHandle, StrPtr("HelloWorld"))
If (funcPtr1 = NULL) Then
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "未找到函数 HelloWorld:%s", SDL_GetError())
Else
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "调用 HelloWorld():")
Dim As FuncVoid_Void helloFunc = Cast(FuncVoid_Void, funcPtr1)
helloFunc() ' 调用共享库中的函数
End If
' 4. 查找并调用第二个导出函数:int Add(int a)
Dim As Any Ptr funcPtr2 = SDL_LoadFunction(libHandle, StrPtr("Add"))
If (funcPtr2 = NULL) Then
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "未找到函数 Add:%s", SDL_GetError())
Else
Dim As FuncInt_Int addFunc = Cast(FuncInt_Int, funcPtr2)
Dim As Integer result = addFunc(100)
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "调用 Add(100) → 结果:%d", result)
End If
' 5. 查找并调用第三个导出函数:char* Greet(char* name)
Dim As Any Ptr funcPtr3 = SDL_LoadFunction(libHandle, StrPtr("Greet"))
If (funcPtr3 = NULL) Then
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "未找到函数 Greet:%s", SDL_GetError())
Else
Dim As FuncStr_Str greetFunc = Cast(FuncStr_Str, funcPtr3)
Dim As ZString Ptr greetResult = greetFunc(StrPtr("SDL 用户"))
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "调用 Greet(""SDL 用户"") → 结果:%s", *greetResult)
End If
' 6. 卸载共享库(注意:卸载后所有函数指针失效)
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "卸载共享库 %s", libPath)
SDL_UnloadObject(libHandle)
' 测试:卸载后调用函数指针(会导致崩溃,此处仅作警示)
' If (funcPtr1 <> NULL) Then
' Dim As FuncVoid_Void helloFunc = Cast(FuncVoid_Void, funcPtr1)
' helloFunc() ' 危险:指针已失效!
' End If
End Sub
' 演示:处理加载失败的情况(容错逻辑)
Sub HandleLoadFailure()
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, vbCrLf & "【共享库加载容错处理】")
' 尝试加载不存在的库
Dim As SDL_SharedObject libHandle = SDL_LoadObject(StrPtr("nonexistent_lib.dll"))
If (libHandle = NULL) Then
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "加载不存在的库失败(预期行为):%s", SDL_GetError())
' 容错逻辑:使用内置实现替代
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "使用内置替代函数:")
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "HelloWorld (内置):你好,这是内置实现!")
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Add(100) (内置) → 结果:%d", 100 + 50)
Else
SDL_UnloadObject(libHandle)
End If
End Sub
' 共享库开发示例(供参考:编译为共享库的代码)
' 以下代码需单独编译为 DLL/.so/.dylib,示例中仅作注释说明
' '''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' ' mylib.c (C 语言共享库源码)
' #include <stdio.h>
' #include <string.h>
'
' // 导出函数1:无参数无返回值
' __declspec(dllexport) void HelloWorld() {
' printf("Hello from shared library!\n");
' }
'
' // 导出函数2:int 参数 + int 返回值
' __declspec(dllexport) int Add(int a) {
' return a + 50;
' }
'
' // 导出函数3:字符串参数 + 字符串返回值
' __declspec(dllexport) char* Greet(char* name) {
' static char buffer[100];
' snprintf(buffer, sizeof(buffer), "你好,%s!", name);
' return buffer;
' }
' '''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' 主程序
Sub Main()
' 初始化 SDL
If (SDL_Init(0) < 0) Then
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL 初始化失败:%s", SDL_GetError())
Exit Sub
End If
' 运行示例
LoadAndCallSharedLib()
HandleLoadFailure()
' 清理 SDL
SDL_Quit()
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, vbCrLf & "程序正常退出")
End Sub
' 运行主程序
Main()
核心知识点补充
-
平台适配要点: 平台 共享库后缀 加载路径规则 编译共享库的关键参数 Windows .dll 优先查找当前目录,再查找系统路径 MinGW: -shared -o mylib.dllLinux .so 需指定绝对路径或 LD_LIBRARY_PATH GCC: -shared -fPIC -o libmylib.somacOS .dylib 需指定绝对路径或 @rpath Clang: -shared -fPIC -o libmylib.dylib -
函数导出规则:
- C 语言函数默认可导出(Windows 需加
__declspec(dllexport),Linux/macOS 无需额外修饰); - C++ 函数需用
extern "C"避免名字改编,否则无法通过原函数名查找; - 函数调用约定需统一(如 Windows 下 DLL 函数常用
stdcall,需在函数指针声明时匹配)。
- C 语言函数默认可导出(Windows 需加
-
常见错误及解决:
- 加载库失败:检查路径是否正确、库文件是否存在、库依赖是否完整(Windows 可通过 Dependency Walker 检查,Linux 用
ldd,macOS 用otool -L); - 查找函数失败:检查函数名是否正确(区分大小写)、是否导出、C++ 函数是否加
extern "C"; - 调用函数崩溃:检查函数指针类型/调用约定是否匹配、参数个数/类型是否一致。
- 加载库失败:检查路径是否正确、库文件是否存在、库依赖是否完整(Windows 可通过 Dependency Walker 检查,Linux 用
-
内存管理注意:
- 共享库内部分配的内存,需由库自身提供释放函数(避免跨库内存管理导致崩溃);
- 不要将共享库中的静态变量指针返回给主程序后卸载库(静态变量会随库卸载失效)。
总结
-
核心优势:
- 跨平台统一的共享库加载接口,无需适配 Windows/Linux/macOS 的原生 API(如 LoadLibrary/dlopen);
- 简化动态插件系统开发,支持运行时加载/卸载功能模块;
- 完善的错误处理机制,可通过
SDL_GetError()获取详细的加载/查找失败原因。
-
使用建议:
- 优先加载 C 风格导出函数,避免 C++ 名字改编问题;
- 声明函数指针时严格匹配原函数的参数、返回值和调用约定;
- 实现容错逻辑:共享库加载/查找失败时,使用内置替代函数保证程序可用;
- 避免频繁加载/卸载共享库,卸载前确保所有函数指针不再使用;
- 共享库路径优先使用绝对路径,避免平台相关的路径查找问题。
-
关键点回顾:
SDL_LoadObject加载共享库返回句柄,失败返回 NULL;SDL_LoadFunction查找函数返回指针,失败返回 NULL;- 共享库卸载后,所有通过
SDL_LoadFunction获取的指针立即失效,禁止调用; - 仅支持 C 风格函数名,C++ 函数需加
extern "C"避免名字改编; - 函数调用约定必须匹配,否则会导致程序崩溃。
评论一下?