You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 05:26:02