I am creating a .net WebAPI to facilitate a gradual move from Delphi code to C#, but our codebase is very large at 1.5M+ lines of code. So we are trying to break our application into small libraries we can load into the C# API and then gradually rewrite the functions or services over time. We have decided that our libraries will export 3 common functions:
Initialize
, Execute
, DisposeStr
. All dlls we create will export these functions so that we have a common way to communicate with our native code.
My issue is that I will have a DelphiEmployeeService.cs
, DelphiWorktimeService.cs
etc. (Maybe a hundred times over) and I would need to have basically the same lines in the beginning of each service wrapper:
[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);
To get around this I wanted to make a DelphiImplementation.cs
base class, but I am unsure on how to make the descendants register which dll name to load...
What I would like would be something along the lines of
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"
}
Is something like this even possible?
You can use the NativeLibrary
class to dynamically load DLLs and execute the functions. It should be fairly simple to create delegates from the function pointers, and store them in the class in order to call them.
public class DelphiImplementation
{
protected abstract string LibraryName { get; }
[return: MarshalAs(UnmanagedType.Bool)]
[UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode)]
protected delegate bool InitializeDelegate();
[UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode)]
protected delegate IntPtr ExecuteDelegate(string parameters);
[UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode)]
protected delegate void DisposeStrDelegate(IntPtr str);
private IntPtr Handle { get; } = NativeLibrary.Load(LibraryName);
protected InitializeDelegate Initialize { get; }
protected ExecuteDelegate Execute { get; }
protected InitializeDelegate DisposeStr { get; }
protected DelphiImplementation()
{
Initialize = Marshal.GetDelegateForFunctionPointer<InitializeDelegate>(NativeLibrary.GetExport(Handle, nameof(Initialize)));
Execute = Marshal.GetDelegateForFunctionPointer<ExecuteDelegate>(NativeLibrary.GetExport(Handle, nameof(Execute)));
DisposeStr = Marshal.GetDelegateForFunctionPointer<DisposeStrDelegate>(NativeLibrary.GetExport(Handle, nameof(DisposeStr)));
}
}
You can the simply do someImplementation.Initialize()
etc.
If you want to unload the DLL after use then you can use IDisposable
with a using
.
public class DelphiImplementation : IDisposable
{
....
public void Dispose()
{
NativeLibrary.Free(Handle);
}
}