Create C# DLL and Call from Delphi

Viewed 850

I want to use a 3rd-party dotnet library (just a couple of functions) from Delphi and plan to create a C# DLL to be an interface. I've created a simple demonstration DLL as a test with C# (VS 2019/.NET5) with a function that returns an integer (later, I want to add one that returns a string). When calling the DLL from Delphi 10.4, I get the following error:

The procedure entry point Add could not be located in the dynamic link library D:\Source Code\DelphiTestDLLCSharp\Win64\Debug\TestDLLProject.exe

I'm using this technique to create the DLL.

My C# code is in a class library:

using System;
using System.Runtime.InteropServices;

namespace TestDLLForDelphiV2
{
    [ComVisible(true)]
    [Guid("8F8F51AA-1A66-4689-83EF-CF61ED99EA92")]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]

    public interface ICalc
    {
        int Add();
    }

    [ComVisible(true)]
    [Guid("BEB943AE-A406-4F55-A9E5-DD3B12F8D17B")]
    public class Calc : ICalc
    {
        private int numberOne = 3;
        private int numberTwo = 5;

        // Add two integers
        [ComVisible(true)]
        public int Add()
        {
            return numberOne + numberTwo;
        }         
    }
}

The Visual Studio project file:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net5.0</TargetFramework>
    <Platforms>x64</Platforms>
    <EnableComHosting>true</EnableComHosting>
    <EnableRegFreeCom>true</EnableRegFreeCom>
    <GeneratePackageOnBuild>false</GeneratePackageOnBuild>
  </PropertyGroup>

  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
    <PlatformTarget>x64</PlatformTarget>
    <EnableComHosting>true</EnableComHosting>
    <EnableRegFreeCom>true</EnableRegFreeCom>
  </PropertyGroup>

</Project>

The Delphi code calling the DLL:

unit Main;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
  System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Memo: TMemo;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

uses
  ShellApi;

function Add: Integer; stdcall; // cdecl;  NOTE - I tried with both stdcall and cdecl
External 'TestDLLForDelphiV2.comhost.dll';  //NOTE - Also attempted with TestDLLForDelphiV2.dll

procedure TForm1.Button1Click(Sender: TObject);
var
  i: Integer;
begin
  Memo.Clear;

  i := Add;
  Memo.Lines.Add(IntToStr(i));

end;

end.

The DLL was created with .NET5, which I understand is not supported by regasm.exe, as with prior .NET versions. Per the article above, I've registered the DLL using regsvr32.exe and have also tried the Reg Free COM option (using the EnableRegFreeCom tag in the project file). Both the DLL and Delphi application are compiled as 64-bit. Any tips, constructive help, etc. would be appreciated.

1 Answers

Load the .NET framework with c ++ and create the C# object there

gcroot<Calc^> calc = nullptr;

bool __stdcall GetCalc(int& obj)
{
  using namespace System::Runtime::InteropServices;
  if (calc.operator->() == nullptr)
    calc= gcnew Calc();
  System::IntPtr p = Marshal::GetIUnknownForObject(calc);

  void* q = p.ToPointer();
  obj= (int)q;
  return true;

}

Delphi:

type
  ICalc=interface(IInterface)
  ['{8F8F51AA-1A66-4689-83EF-CF61ED99EA92}']
    function Add: integer; safecall;
end;

procedure test;
type
  TFunc=function(var ASrv: IInterface): boolean; stdcall;
var
  Lhn    : THandle;
  Lfunc  : TFunc;
  Lif    : IInterface;
  LifCalc: ICalc;
begin
  Lhnd  := LoadLibrary(<C++ DllName>);
  try
    Lfunc := GetProcAdress(Lhnd, 'GetCalc');
    
    Lfunc(Lif);
    if Supports(Lif, ICalc, LifCalc) then
      ShowMessage(LifCalc.Add.ToString)
   else
      raise Exception.Create('Error');
 
 finally
   FreeLibrary(Lhnd);
 end;
end;

C#

namespace TestDLLForDelphiV2
{
[ComVisible(true)]
[Guid("8F8F51AA-1A66-4689-83EF-CF61ED99EA92")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]

public interface ICalc
{
    [return: MarshalAs(UnmanagedType.I4)]
    int Add();
}

public class Calc : ICalc
{
    private int numberOne = 3;
    private int numberTwo = 5;

    // Add two integers
    public int Add()
    {
        return numberOne + numberTwo;
    }         
}
}
Related