共享库 (Shared Libraries)
- 来源: https://www.freebasic.net/wiki/wikka.php?wakka=ProPgSharedLibraries
- 最后更新: 2024-06-26
共享库是编译后的代码,可以在运行可执行文件时加载并使用。
当编译器生成可执行文件时,基本源文件首先转换为目标文件,然后将目标文件链接在一起生成可执行文件。共享库与静态库类似,都包含目标文件。但共享库也像可执行文件一样,只有在可执行文件运行时才会被加载。
该库之所以称为共享库,是因为库中的代码在运行时由可执行文件加载,可以被多个可执行文件加载,即使只有一份共享库的副本。
一旦创建了共享库,我们就可以像直接将源代码与程序一起编译一样使用其中的代码。
在 DOS 上使用共享库 => 参见专页:共享库 - DOS
在 Windows 上从 def 文件创建导入库,或从 DLL 文件创建导入库或 def 文件
共享库示例
以下是使用三个文件创建共享库的简单示例:
mylib.bas- 库的源代码mylib.bi- 库的头文件mytest.bas- 测试程序
我们的库将是一个提供单一函数的模块:
start GeSHi
'' mylib.bas
'' 编译命令:fbc -dll mylib.bas
'' 将两个数字相加并返回结果
Public Function Add2( ByVal x As Integer, ByVal y As Integer ) As Integer Export
Return( x + y )
End Functionend GeSHi
使用以下命令编译库:
fbc -dll mylib.bas
-dll 选项告诉编译器获取源代码 mylib.bas 并将其转换为目标文件 mylib.o,然后将目标文件存储到共享库中。共享库的名称将有 .so 扩展名或 .dll 扩展名,具体取决于平台是 Linux 还是 Windows 版本。一个库可能包含许多模块(源文件),每个模块有许多函数,但在这个简单示例中各只有一个。
创建共享库与创建静态库几乎完全相同,只是在过程定义的第一行添加了 Export 限定符。Export 告诉编译器使函数对加载该共享库的其他可执行文件可见。
要在其他源代码中使用库,我们需要某种方式告诉编译器库中确切包含什么内容。一个好方法是将库的声明(也称为接口或 API)放入头文件:
start GeSHi
'' mylib.bi
#inclib "mylib"
Declare Function Add2( ByVal x As Integer, ByVal y As Integer ) As Integerend GeSHi
头文件无需编译。我们希望以源代码形式保存它,以便与其他源文件一起包含。#inclib 语句将告诉编译器需要在运行时链接的共享库的名称。
有了库(.dll / .so 文件)和头文件(.bi 文件),我们可以在测试程序中试用它们:
start GeSHi
'' mytest.bas
'' 编译命令:fbc mytest.bas
#include once "mylib.bi"
Print Add2(1,2)end GeSHi
#include 语句告诉编译器将 mylib.bi 中的源代码包含进来,就像我们直接将其输入到原始源文件中一样。按照我们编写包含文件的方式,它告诉编译器关于该库所需的一切。
我们使用以下命令编译:
fbc mytest.bas
然后运行 mytest 可执行文件,应该得到以下结果:
3创建库时可以使用多个源模块。基本程序可以通过包含每个所需的头文件来使用多个库。有些库非常大,可能使用多个头文件。在大型项目中,将很少更改的某些代码模块制作为共享库可以显著缩短编译时间和链接时间。
共享库可以选择性地包含用 -g 命令行选项指定的调试信息。
共享库可以选择性地包含模块构造函数、主代码和模块析构函数。模块构造函数和主代码在库加载时执行,模块析构函数在库卸载时执行。
目标文件(因此也包括共享库)是特定于平台的,在某些情况下还特定于编译器和 FreeBASIC 运行时库的特定版本。
在 Windows 上使用共享库
在 Windows 上,共享库必须存储在运行时可以被需要它的可执行文件找到的位置。
操作系统可能搜索以下目录:
- 加载可执行文件的目录。
- 当前目录。
- Windows 和 Windows 系统文件夹。
PATH环境变量中列出的目录。
搜索目录的顺序可能取决于使用的 Windows 版本以及操作系统的配置设置。
在 Linux 上使用共享库
默认情况下,Linux 通常不会搜索当前目录或加载可执行文件的目录。您需要:
- 将 .so 文件复制到包含共享库的目录(例如
/usr/lib)并运行ldconfig配置该库。 - 修改环境变量 LD_LIBRARY_PATH 以搜索当前目录或新创建的共享库所在的特定目录。
要运行可执行文件 ./mytest/ 并临时告诉 Linux 搜索当前目录,使用以下 shell 命令:
LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH ./mytest导出符号的可执行文件
如果可执行文件中有在加载其他共享库时必须对这些共享库可用的符号,请在过程定义的第一行使用 Export 导出限定符,并在创建(链接)可执行文件时使用 -export 命令行选项。
-export 选项与 -dylib 或 -dll 命令行选项一起使用时不会产生额外效果。
动态加载共享库
共享库可以在运行时通过动态加载库及其符号来加载和使用。
Dylibload可用于加载共享库并获取其句柄。Dylibsymbol用于获取已加载共享库中符号的地址(Windows 上不支持变量名,只支持过程名)。Dylibfree在不再需要共享库时用于卸载它。
共享库中的过程必须在过程定义的第一行使用 Export 限定符,以确保符号名称被放入共享库的导出表中:
start GeSHi
'' mydll.bas
'' 编译命令:fbc -dll mydll.bas
'' 在 Windows 上将创建 mydll.dll(以及 libmydll.dll.a 导入库),
'' 在 Linux 上将创建 libmydll.so。
''
'' 注意:libmydll.dll.a 是导入库,只有在创建调用 mydll 中函数的
'' 可执行文件时才需要,发布应用程序时只需分发 DLL 文件,
'' 无需包含导入库,导入库对最终用户毫无用处。
'' 简单的导出函数;`<alias "...">` 禁用 FB 默认的
'' 全大写名称修饰,使 DLL 导出 AddNumbers() 而非
'' ADDNUMBERS()。
Function AddNumbers Alias "AddNumbers"( ByVal a As Integer, ByVal b As Integer ) As Integer Export
Function = a + b
End Functionend GeSHi
start GeSHi
'' load.bas:在运行时加载 mydll.dll(或 libmydll.so),调用 mydll 中的一个
'' 函数并打印结果。编译时不需要 mydll。
'' 编译命令:fbc test.bas
''
'' 注意:编译好的 mydll.dll(或 libmydll.so)动态库
'' 应位于当前目录中。
'' 注意我们将库文件名指定为 "mydll";这是为了确保
'' Windows 和 Linux 之间的兼容性,因为动态库在不同平台
'' 上有不同的文件名和扩展名。
Dim As Any Ptr libhandle = DyLibLoad( "mydll" )
If( libhandle = 0 ) Then
Print "Failed to load the mydll dynamic library, aborting program..."
End 1
End If
'' 此函数指针用于在找到地址后调用 mydll 中的函数。
'' 注意:它必须具有相同的调用约定和参数。
Dim AddNumbers As Function( ByVal As Integer, ByVal As Integer ) As Integer
AddNumbers = DyLibSymbol( libhandle, "AddNumbers" )
If( AddNumbers = 0 ) Then
Print "Could not retrieve the AddNumbers() function's address from the mydll library, aborting program..."
End 1
End If
Randomize Timer
Dim As Integer x = Rnd * 10
Dim As Integer y = Rnd * 10
Print x; " +"; y; " ="; AddNumbers( x, y )
'' 库使用完毕;进程终止时操作系统会自动卸载已加载的库,
'' 但我们也可以在程序执行期间强制卸载以节省资源;
'' 下一行正是这样做的。
'' 请记住,一旦卸载已加载的库,通过 dylibsymbol 获取的所有符号
'' 将失效,访问它们会导致应用程序崩溃。
DyLibFree( libhandle )end GeSHi
在 Windows 上与共享库交换/共享变量
Windows 不支持使用 Common 或 Extern 关键字直接与共享库共享变量。
否则(在任何平台上运行),将参数(按值或按引用)传递给库过程,或从库函数返回变量(按值或按引用),可以间接地与共享库交换数据(按值)或共享数据(按引用)。
在 Windows 上共享主程序和 DLL 代码之间数据(按引用)的解决方法示例(适用于任何平台):
(根据需要可以静态或动态加载 DLL)
start GeSHi
' dllShareData.bas 使用 -dll 编译
' 在主程序和 DLL 代码之间共享数据
' 'Alias' 子句(除 'Export' 外)允许与静态或动态加载的 DLL 兼容
' 共享主程序变量
Dim Shared ByRef As Integer Idll = *CPtr(Integer Ptr, 0)
Sub passIntByRef Alias"passIntByRef"(ByRef i As Integer) Export
Print " dll code receives by reference main integer"
@Idll = @i
End Sub
Sub printIdll Alias"printIdll"() Export
Print " dll code prints its own reference"
Print " " & Idll
End Sub
Sub incrementIdll Alias"incrementIdll"() Export
Idll += 1
End Sub
' 共享 DLL 变量
Dim Shared As Integer Jdll = 5
Function returnIntByRef Alias"returnIntByRef"() ByRef As Integer Export
Print " dll code returns by reference dll integer"
Return Jdll
End Function
Sub printJdll Alias"printJdll"() Export
Print " dll code prints its dll integer"
Print " " & Jdll
End Sub
Sub incrementJdll Alias"incrementJdll"() Export
Jdll +=1
End Subend GeSHi
start GeSHi
' mainShareData.bas
' 在主程序和 DLL 代码之间共享数据
' 'Alias' 子句允许与静态或动态加载的 DLL 兼容
' 静态加载 DLL
#inclib "dllShareData"
Declare Sub passIntByRef Alias"passIntByRef"(ByRef i As Integer)
Declare Function returnIntByRef Alias"returnIntByRef"() ByRef As Integer
Declare Sub printIdll Alias"printIdll"()
Declare Sub printJdll Alias"printJdll"()
Declare Sub incrementIdll Alias"incrementIdll"()
Declare Sub incrementJdll Alias"incrementJdll"()
' 或动态加载 DLL
'Dim As Any Ptr libhandle = DyLibLoad("dllShareData")
'Dim As Sub(Byref i As Integer) passIntByRef = DyLibSymbol(libhandle, "passIntByRef")
'Dim As Function() Byref As Integer returnIntByRef = DyLibSymbol(libhandle, "returnIntByRef")
'Dim As Sub() printIdll = DyLibSymbol(libhandle, "printIdll")
'Dim As Sub() printJdll = DyLibSymbol(libhandle, "printJdll")
'Dim As Sub() incrementIdll = DyLibSymbol(libhandle, "incrementIdll")
'Dim As Sub() incrementJdll = DyLibSymbol(libhandle, "incrementJdll")
' 共享主程序变量
Dim Shared As Integer Imain = 1
Print "main code passes by reference main integer to dll code"
passIntByref(Imain)
Print "main code requests dll code to print its own reference"
printIdll()
Print "main code increments its main integer value"
Imain += 1
Print "main code requests dll code to print its own reference"
printIdll()
Print "main code requests dll to increments its own reference"
incrementIdll()
Print "main code prints its main integer"
Print "" & Imain
Print
' 共享 DLL 变量
Dim Shared ByRef As Integer Jdll = *CPtr(Integer Ptr, 0)
Print "main code requests by reference dll integer from dll code"
Dim As Integer Ptr pJdll = @(returnIntByRef())
Print "main code receives by reference dll integer"
@Jdll = pJdll
Print "main code prints its own reference"
Print "" & Jdll
Print "main code requests dll to increment its dll integer value"
incrementJdll()
Print "main code prints its own reference"
Print "" & Jdll
Print "main code increments its own reference"
Jdll += 1
Print "main code requests dll code to print its dll integer"
printJdll()
Print
' 若为动态加载的 DLL
'DyLibFree(libhandle)
Sleepend GeSHi
在 Windows 上使用返回引用的静态函数来共享主程序和 DLL 代码之间数据的更简单解决方法示例(适用于任何平台):
(根据需要可以静态或动态加载 DLL)
start GeSHi
' dllShareData2.bas 使用 -dll 编译
' 通过返回引用的静态函数在主程序和 DLL 代码之间共享数据
' 'Alias' 子句(除 'Export' 外)允许与静态或动态加载的 DLL 兼容
Function returnIntByRef Alias"returnIntByRef"() ByRef As Integer Export
Static As Integer Jdll = 1
Return Jdll
End Function
Sub printJdll Alias"printJdll"() Export
Print " dll code prints the reference"
Print " " & returnIntByRef()
End Sub
Sub incrementJdll Alias"incrementJdll"() Export
returnIntByRef() +=1
End Subend GeSHi
start GeSHi
' mainShareData2.bas
' 通过返回引用的静态函数在主程序和 DLL 代码之间共享数据
' 'Alias' 子句允许与静态或动态加载的 DLL 兼容
' 静态加载 DLL
#inclib "dllShareData2"
Declare Function returnIntByRef Alias"returnIntByRef"() ByRef As Integer
Declare Sub printJdll Alias"printJdll"()
Declare Sub incrementJdll Alias"incrementJdll"()
' 或动态加载 DLL
'Dim As Any Ptr libhandle = DyLibLoad("dllShareData2")
'Dim As Function() Byref As Integer returnIntByRef = DyLibSymbol(libhandle, "returnIntByRef")
'Dim As Sub() printJdll = DyLibSymbol(libhandle, "printJdll")
'Dim As Sub() incrementJdll = DyLibSymbol(libhandle, "incrementJdll")
Print "main code requests dll code to print the reference"
printJdll()
Print "main code prints the reference"
Print "" & returnIntByRef()
Print
Print "main code requests dll to increment the reference"
incrementJdll()
Print "main code requests dll code to print the reference"
printJdll()
Print "main code prints the reference"
Print "" & returnIntByRef()
Print
Print "main code increments the reference"
returnIntByRef() += 1
Print "main code prints the reference"
Print "" & returnIntByRef()
Print "main code requests dll code to print the reference"
printJdll()
Print
' 若为动态加载的 DLL
'DyLibFree(libhandle)
Sleepend GeSHi
注意:
如果共享变量(按引用)是指向任何结构(内存缓冲区、类型实例等)的指针,则该结构本身也与库共享(按引用传递指针甚至允许对结构进行动态重新分配)。
在 Windows 上从 def 文件创建导入库,或从 DLL 文件创建导入库或 def 文件
在 Windows 上的 FreeBASIC 中使用第三方 DLL 时,可能需要手动构建导入库或 def 文件以满足链接器需求。
在 Windows 上从现有 DLL 的 def 文件 (*.def) 创建导入库文件 (*.dll.a) 的语法:
- 32 位:
drive:\FreeBASIC\bin\win32\dlltool.exe -d XXX.def -l libXXX.dll.a
- 64 位:
drive:\FreeBASIC\bin\win64\dlltool.exe -m i386:x86-64 --as-flags --64 -d XXX.def -l libXXX.dll.a
在 Windows 上直接从现有 DLL (*.dll) 创建导入库文件 (*.dll.a) 的语法:
- 32 位:
drive:\FreeBASIC\bin\win32\dlltool -k -d XXX.dll -l libXXX.dll.a
- 64 位:
drive:\FreeBASIC\bin\win64\dlltool -k -d XXX.dll -l libXXX.dll.a
在 Windows 上从现有 DLL (*.dll) 创建 def 文件 (*.def) 的语法:
- 32 位:
drive:\FreeBASIC\bin\win32\dlltool --dllname XXX.dll --output-def XXX.def
- 64 位:
drive:\FreeBASIC\bin\win64\dlltool --dllname XXX.dll --output-def XXX.def
或使用 -D 和 -z:
- 32 位:
drive:\FreeBASIC\bin\win32\dlltool -D XXX.dll -z XXX.def
- 64 位:
drive:\FreeBASIC\bin\win64\dlltool -D XXX.dll -z XXX.def
其中:
drive:\FreeBASIC\
FreeBASIC 安装路径
XXX
库和 def 文件的名称
参见
- 共享库 - DOS
- 静态库
#inclib#include- 编译器选项:-dll
- 编译器选项:-export
- 编译器选项:-dylib
返回 目录