如何在C#中跨多平台管理不同DLL/So库的导入?
跨平台加载本地库的有效解决方法及.NET推荐实践
一、可行的解决方法
1. 运行时动态加载(使用NativeLibrary类)
这种方式摆脱编译时依赖,在运行时根据平台加载对应库,通过委托绑定本地函数,灵活性最高。示例代码:
using System.Runtime.InteropServices; private static class Imports { // 定义与本地函数匹配的委托 private delegate void FPDF_InitLibraryDelegate(); private delegate void FPDF_DestroyLibraryDelegate(); private delegate DocumentHandle FPDF_LoadMemDocumentDelegate(IntPtr buffer, int size, string password); // 存储委托实例 private static FPDF_InitLibraryDelegate? _initLibrary; private static FPDF_DestroyLibraryDelegate? _destroyLibrary; private static FPDF_LoadMemDocumentDelegate? _loadMemDocument; static Imports() { // 根据运行平台确定库名 string libraryName = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "pdfium.dll" : RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? "pdfium_x64.so" : throw new PlatformNotSupportedException("仅支持Windows和Linux平台"); // 加载本地库 IntPtr libraryHandle = NativeLibrary.Load(libraryName); // 绑定函数到委托 _initLibrary = Marshal.GetDelegateForFunctionPointer<FPDF_InitLibraryDelegate>( NativeLibrary.GetExport(libraryHandle, nameof(FPDF_InitLibrary))); _destroyLibrary = Marshal.GetDelegateForFunctionPointer<FPDF_DestroyLibraryDelegate>( NativeLibrary.GetExport(libraryHandle, nameof(FPDF_DestroyLibrary))); _loadMemDocument = Marshal.GetDelegateForFunctionPointer<FPDF_LoadMemDocumentDelegate>( NativeLibrary.GetExport(libraryHandle, nameof(FPDF_LoadMemDocument))); } // 对外暴露的方法,内部调用委托 public static void FPDF_InitLibrary() { _initLibrary ?? throw new InvalidOperationException("本地库未成功加载"); _initLibrary(); } public static void FPDF_DestroyLibrary() { _destroyLibrary ?? throw new InvalidOperationException("本地库未成功加载"); _destroyLibrary(); } public static DocumentHandle FPDF_LoadMemDocument(IntPtr buffer, int size, string password) { _loadMemDocument ?? throw new InvalidOperationException("本地库未成功加载"); return _loadMemDocument(buffer, size, password); } }
2. 条件编译修正(解决预处理指令的方法可用性问题)
如果偏好编译时绑定,可通过条件编译符号指定不同平台的库名,确保所有平台的方法定义完整:
private static class Imports { // 根据平台条件编译库名(.NET 5+自带OS_WINDOWS/OS_LINUX符号) #if OS_WINDOWS private const string DllName = "pdfium.dll"; #elif OS_LINUX private const string DllName = "pdfium_x64.so"; #else #error 不支持当前平台 #endif private const CharSet PdfiumCharSet = CharSet.Ansi; private const CallingConvention PdfiumCallingConvention = CallingConvention.StdCall; [DllImport(DllName)] public static extern void FPDF_InitLibrary(); [DllImport(DllName)] public static extern void FPDF_DestroyLibrary(); [DllImport(DllName, CallingConvention = PdfiumCallingConvention, CharSet = PdfiumCharSet)] public static extern DocumentHandle FPDF_LoadMemDocument(IntPtr buffer, int size, string password); }
配置方式:在项目文件中添加条件编译符号,或在IDE的项目属性中设置:
<PropertyGroup> <DefineConstants>$(DefineConstants);OS_WINDOWS</DefineConstants> </PropertyGroup>
3. 完善抽象层实现(工厂模式+接口)
若之前的抽象层未生效,可能是实现未分离彻底。通过接口定义统一契约,为每个平台编写独立实现,用工厂类动态选择:
// 抽象接口 public interface IPdfiumService { void InitLibrary(); void DestroyLibrary(); DocumentHandle LoadMemDocument(IntPtr buffer, int size, string password); } // Windows平台实现 public class WindowsPdfiumService : IPdfiumService { private const string DllName = "pdfium.dll"; private const CharSet PdfiumCharSet = CharSet.Ansi; private const CallingConvention PdfiumCallingConvention = CallingConvention.StdCall; [DllImport(DllName)] private static extern void FPDF_InitLibrary(); [DllImport(DllName)] private static extern void FPDF_DestroyLibrary(); [DllImport(DllName, CallingConvention = PdfiumCallingConvention, CharSet = PdfiumCharSet)] private static extern DocumentHandle FPDF_LoadMemDocument(IntPtr buffer, int size, string password); public void InitLibrary() => FPDF_InitLibrary(); public void DestroyLibrary() => FPDF_DestroyLibrary(); public DocumentHandle LoadMemDocument(IntPtr buffer, int size, string password) => FPDF_LoadMemDocument(buffer, size, password); } // Linux平台实现 public class LinuxPdfiumService : IPdfiumService { private const string DllName = "pdfium_x64.so"; private const CharSet PdfiumCharSet = CharSet.Ansi; private const CallingConvention PdfiumCallingConvention = CallingConvention.StdCall; [DllImport(DllName)] private static extern void FPDF_InitLibrary(); [DllImport(DllName)] private static extern void FPDF_DestroyLibrary(); [DllImport(DllName, CallingConvention = PdfiumCallingConvention, CharSet = PdfiumCharSet)] private static extern DocumentHandle FPDF_LoadMemDocument(IntPtr buffer, int size, string password); public void InitLibrary() => FPDF_InitLibrary(); public void DestroyLibrary() => FPDF_DestroyLibrary(); public DocumentHandle LoadMemDocument(IntPtr buffer, int size, string password) => FPDF_LoadMemDocument(buffer, size, password); } // 工厂类 public static class PdfiumServiceFactory { public static IPdfiumService Create() { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return new WindowsPdfiumService(); if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) return new LinuxPdfiumService(); throw new PlatformNotSupportedException("仅支持Windows和Linux平台"); } }
使用时直接通过工厂获取实例:
var pdfiumService = PdfiumServiceFactory.Create(); pdfiumService.InitLibrary();
二、.NET跨平台管理本地依赖的推荐实践
- 优先使用运行时平台检测:通过
RuntimeInformation.IsOSPlatform替代预处理指令,适配单文件发布等多平台场景,无需编译多个版本。 - 规范本地库的部署路径:在项目文件中配置不同平台的库复制规则,确保运行时能找到:
<ItemGroup> <None Include="libs\win\pdfium.dll" CopyToOutputDirectory="PreserveNewest" Condition="'$(OS)' == 'Windows_NT'" /> <None Include="libs\linux\pdfium_x64.so" CopyToOutputDirectory="PreserveNewest" Condition="'$(OS)' == 'Linux'" /> </ItemGroup> - 用NuGet管理跨平台本地库:将不同平台的本地库打包到NuGet包的
runtimes目录(如runtimes/win-x64/native/、runtimes/linux-x64/native/),.NET会自动根据运行平台加载对应库,无需手动处理路径。 - 添加异常处理逻辑:捕获
DllNotFoundException、EntryPointNotFoundException等异常,给出明确的错误提示(如缺失库、架构不匹配),便于排查问题。 - 避免硬编码路径:可通过配置文件或环境变量指定本地库路径,适配不同部署环境的需求。
内容的提问来源于stack exchange,提问作者Hub
相关产品推荐
相关产品推荐

