2

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.

Ken White
  • 123,280
  • 14
  • 225
  • 444
Greg Bishop
  • 517
  • 1
  • 5
  • 16
  • Just use UnmanagedExports – David Heffernan Sep 23 '21 at 19:54
  • 3
    I'm not a big specialist in Delphi, but looks like you are trying to call DLL not over COM, but as simple native export function. Try to use CoCreateInstance (I believe Delphi has its own api\wrapper for this) to access the COM object. – Serg Sep 23 '21 at 19:56
  • David: I don't believe UnmanagedExports supports .NET5, am I mistaken? – Greg Bishop Sep 23 '21 at 19:58
  • I think there is a version or an alternative for it?? And obviously @Serg is right, that if you go the COM route then clearly you need to use com in your delphi code! – David Heffernan Sep 23 '21 at 21:13
  • 2
    You've written the C# DLL as a COM library, so you need to access it through COM from your Delphi application. You're not doing that; you're trying to use it as a standard DLL rather than a COM library. – Ken White Sep 23 '21 at 23:38
  • 1
    Delphi has a feature to [import a .NET assembly](https://docwiki.embarcadero.com/RADStudio/Sydney/en/Import_Component_Wizard), and to generate a Delphi unit to interact with it. – Remy Lebeau Sep 24 '21 at 00:11
  • 1
    https://github.com/3F/DllExport this is what I was referring to – David Heffernan Sep 24 '21 at 06:49

1 Answers1

0

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;
    }         
}
}
USauter
  • 295
  • 1
  • 9