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

如何通过基类复用LibraryImport实现多DLL相同函数导入?

问题:基于.NET LibraryImport的Delphi服务基类实现是否可行?

我正在创建一个.NET WebAPI,用以逐步将Delphi代码迁移至C#,但现有代码库规模超过150万行。因此我们计划将应用拆分为可加载到C# API中的小型库,并逐步重写其中的函数或服务。我们规定所有创建的DLL都要导出Initialize、Execute、DisposeStr这三个通用函数,以统一与原生代码交互的方式。

问题在于,我需要创建DelphiEmployeeService.cs、DelphiWorktimeService.cs等上百个服务包装类,每个类开头都要重复编写几乎相同的导入代码:

[LibraryImport("<DLLNAME>", EntryPoint = "Initialize")]
[return: MarshalAs(UnmanagedType.Bool)]
[UnmanagedCallConv(CallConvs = new Type[] { typeof(System.Runtime.CompilerServices.CallConvStdcall) })]
private static partial bool Initialize();

[LibraryImport("<DLLNAME>", EntryPoint = "Execute", StringMarshalling = StringMarshalling.Utf16)]
[UnmanagedCallConv(CallConvs = new Type[] { typeof(System.Runtime.CompilerServices.CallConvStdcall) })]
private static partial IntPtr Execute(string parameters);

[LibraryImport("<DLLNAME>", EntryPoint = "DisposeStr")]
[UnmanagedCallConv(CallConvs = new Type[] { typeof(System.Runtime.CompilerServices.CallConvStdcall) })]
private static partial void DisposeStr(IntPtr str);

为了避免重复代码,我想创建一个DelphiImplementation.cs基类,但不确定如何让子类指定要加载的DLL名称。我期望的实现方式如下:

public class DelphiImplementation
{
  protected abstract string LibraryName;

  [LibraryImport(LibraryName, EntryPoint = "Initialize")]
  [return: MarshalAs(UnmanagedType.Bool)]
  [UnmanagedCallConv(CallConvs = new Type[] {typeof(System.Runtime.CompilerServices.CallConvStdcall) })]
  private static partial bool Initialize();

  [LibraryImport(LibraryName, EntryPoint = "Execute", StringMarshalling = StringMarshalling.Utf16)]
  [UnmanagedCallConv(CallConvs = new Type[] { typeof(System.Runtime.CompilerServices.CallConvStdcall) })]
  private static partial IntPtr Execute(string parameters);

  [LibraryImport(LibraryName, EntryPoint = "DisposeStr")]
  [UnmanagedCallConv(CallConvs = new Type[] { typeof(System.Runtime.CompilerServices.CallConvStdcall) })]
  private static partial void DisposeStr(IntPtr str);
}
public class EmployeeDelphiService : DelphiImplementation
{
  protected override string LibraryName => "Employee.dll";
}

请问这样的实现是否可行?


解答

这种实现不可行,核心原因是[LibraryImport]特性的第一个参数(库名称)必须是编译时常量,而抽象属性LibraryName属于运行时才能确定的值,编译器无法在编译阶段解析它,会直接抛出编译错误。

针对你的场景,有几种更合适的替代方案:

方案1:使用泛型基类+源代码生成器(推荐)

利用.NET的源代码生成器,根据子类指定的库名称自动生成LibraryImport方法。你可以定义一个基类标记,然后编写生成器扫描所有继承该标记的类,自动生成对应的导入代码。

示例标记基类:

public abstract class DelphiServiceBase
{
    protected abstract string LibraryName { get; }
    // 定义抽象方法让生成器实现
    public abstract bool Initialize();
    public abstract IntPtr Execute(string parameters);
    public abstract void DisposeStr(IntPtr str);
}

生成器会在编译时为每个子类(比如EmployeeDelphiService)生成包含LibraryImport的具体实现,彻底避免手动重复代码。

方案2:手动使用Marshal.GetDelegateForFunctionPointer绑定

放弃LibraryImport,改为手动加载DLL并绑定函数委托,这样可以在运行时指定库名称。

基类实现示例:

public abstract class DelphiImplementation : IDisposable
{
    protected abstract string LibraryName { get; }
    private IntPtr _libraryHandle;

    // 定义委托类型
    private delegate bool InitializeDelegate();
    private delegate IntPtr ExecuteDelegate([MarshalAs(UnmanagedType.LPWStr)] string parameters);
    private delegate void DisposeStrDelegate(IntPtr str);

    private InitializeDelegate? _initialize;
    private ExecuteDelegate? _execute;
    private DisposeStrDelegate? _disposeStr;

    protected DelphiImplementation()
    {
        _libraryHandle = NativeLibrary.Load(LibraryName);
        if (_libraryHandle == IntPtr.Zero)
            throw new DllNotFoundException($"Failed to load {LibraryName}");

        // 绑定函数
        _initialize = Marshal.GetDelegateForFunctionPointer<InitializeDelegate>(
            NativeLibrary.GetExport(_libraryHandle, "Initialize"));
        _execute = Marshal.GetDelegateForFunctionPointer<ExecuteDelegate>(
            NativeLibrary.GetExport(_libraryHandle, "Execute"));
        _disposeStr = Marshal.GetDelegateForFunctionPointer<DisposeStrDelegate>(
            NativeLibrary.GetExport(_libraryHandle, "DisposeStr"));
    }

    public bool Initialize() => _initialize!();
    public IntPtr Execute(string parameters) => _execute!(parameters);
    public void DisposeStr(IntPtr str) => _disposeStr!(str);

    public void Dispose()
    {
        if (_libraryHandle != IntPtr.Zero)
        {
            NativeLibrary.Free(_libraryHandle);
            _libraryHandle = IntPtr.Zero;
        }
    }
}

// 子类实现
public class EmployeeDelphiService : DelphiImplementation
{
    protected override string LibraryName => "Employee.dll";
}

这个方案的优点是完全支持运行时指定库名称,缺点是需要手动处理委托绑定和DLL的加载/释放,性能略低于LibraryImport(但大多数业务场景下差异可忽略)。

方案3:使用T4模板生成重复代码

如果不想用源代码生成器,也可以用T4模板批量生成所有服务类的代码。你可以在模板中枚举所有需要的DLL名称,然后自动生成每个服务类的完整代码,包括LibraryImport部分。

这种方式简单直接,适合一次性生成大量相似代码,但灵活性不如源代码生成器(后续新增服务需要修改模板重新生成)。


内容的提问来源于stack exchange,提问作者Matt Baech

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 16:24:53