1

NativeAOT provides a sample c# class with methods marked as "UnmanagedCallersOnly", which I understand to be intended for use in unmanaged languages such as Delphi. The sample class the provide is:

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Runtime.InteropServices;

namespace NativeLibrary
{
    public class Class1
    {
        [UnmanagedCallersOnly(EntryPoint = "add")]
        public static int Add(int a, int b)
        {
            return a + b;
        }

        [UnmanagedCallersOnly(EntryPoint = "write_line")]
        public static int WriteLine(IntPtr pString)
        {
            // The marshalling code is typically auto-generated by a custom tool in larger projects.
            try
            {
                // UnmanagedCallersOnly methods only accept primitive arguments. The primitive arguments
                // have to be marshalled manually if necessary.
                string str = Marshal.PtrToStringAnsi(pString);

                Console.WriteLine(str);
            }
            catch
            {
                // Exceptions escaping out of UnmanagedCallersOnly methods are treated as unhandled exceptions.
                // The errors have to be marshalled manually if necessary.
                return -1;
            }
            return 0;
        }

        [UnmanagedCallersOnly(EntryPoint = "sumstring")]
        public static IntPtr sumstring(IntPtr first, IntPtr second)
        {
            // Parse strings from the passed pointers 
            string my1String = Marshal.PtrToStringAnsi(first);
            string my2String = Marshal.PtrToStringAnsi(second);

            // Concatenate strings 
            string sum = my1String + my2String;

            // Assign pointer of the concatenated string to sumPointer
            IntPtr sumPointer = Marshal.StringToHGlobalAnsi(sum);

            // Return pointer
            return sumPointer;
        }
    }
}

(from: Class1.cs)

How can the methods (Add, WriteLine, and sumstring) be called from Delphi?

Edit 1/1/2023

I've gone through the links (and also tried ChatGPT) but I'm still not getting this to work. I have a NativeLibrary.DLL file compiled from the C# code. Using dotPeek, I can see that the Add function is in the DLL. The project file I'm using is:

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

  <PropertyGroup>
    <OutputType>Library</OutputType>
    <TargetFramework>net7.0</TargetFramework>
    <PublishSingleFile>true</PublishSingleFile>
    <SelfContained>true</SelfContained>
    <PublishTrimmed>true</PublishTrimmed>
    <PublishReadyToRun>true</PublishReadyToRun>
    <RuntimeIdentifier>win-x64</RuntimeIdentifier>
    <Platforms>x64</Platforms>
  </PropertyGroup>

</Project>

I've tried calling the Add function from Delphi two different ways.

Method 1

unit TestClass;

interface

uses
  System.SysUtils,
  System.Classes, Windows;

const
  MY_DLL = 'NativeLibrary.dll';

type
  TAddFunc = function(a: Integer; b: Integer): Integer; stdcall;

  TTestClass = class
  public
    class function TestAdd(a: Integer; b: Integer): Integer; static;
  end;

implementation

class function TTestClass.TestAdd(a: Integer; b: Integer): Integer;
var
  Handle: HMODULE; // THandle
  Func: TAddFunc;
begin
  Handle := LoadLibrary(MY_DLL);
  try
    Func := GetProcAddress(Handle, 'Add');
    if Assigned(Func) then
    begin
      Result := Func(a, b);
    end;

  finally
    FreeLibrary(Handle);
  end;
end;

end.

This gets called by:

procedure TMainForm.Button1Click(Sender: TObject);
var
  StringFromDLL: string;
begin
  StringFromDLL := TTestClass.TestAdd(4,10);
  ShowMessage(StringFromDLL);
end;

The application runs, but when TestAdd is called, a Handle is returned, but Func is nil and never gets assigned.

Method 2

unit TestClass2;

interface

uses
  System.SysUtils,
  System.Classes, Windows;

const
  MY_DLL = 'NativeLibrary.dll';

  function Add(a: Integer; b: Integer): Integer; stdcall;
    external MY_DLL name 'Add';

type
  TTestClass2 = class
  public
    class function TestAdd(a: Integer; b: Integer): Integer; static;
  end;

implementation

class function TTestClass2.TestAdd(a: Integer; b: Integer): Integer;
begin
  Result:=Add(a,b);
end;

end.

When I call TestClass2.TestAdd using this technique, upon application startup I get a prompt reading "Application Error - The application was unable to start correctly (0xc000007b). Click OK to close the application."

Greg Bishop
  • 517
  • 1
  • 5
  • 16
  • [ChatGPT](https://chat.openai.com/chat) has a plausible solution. Use the following interrogation: *"NativeAOT provides a sample c# class with methods marked as "UnmanagedCallersOnly", which I understand to be intended for use in unmanaged languages such as Delphi. How do you call these methods from Delphi?"* – Robert Harvey Dec 28 '22 at 20:53
  • There's [another question](https://stackoverflow.com/questions/74683283/unmanagedexports-dllexport-and-nativeaot-broken-in-net6-0) just active today, which isn't specifically about Delphi but it shows how you would do it in a way that should also work for that. – 500 - Internal Server Error Dec 28 '22 at 22:45
  • https://learn.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.unmanagedcallersonlyattribute?view=net-7.0 – David Heffernan Dec 28 '22 at 22:53
  • Sounds like you need `[DllExport]` (see https://stackoverflow.com/questions/6174584/call-c-sharp-dll-from-delphi ?) – Jeremy Lakeman Jan 09 '23 at 04:32
  • @JeremyLakeman: `UnmanagedCallersOnly` takes care of that, so `DllExport` isn't needed. See the link that @500 provided. – Ken White Jan 09 '23 at 04:47
  • `[UnmanagedCallersOnly(EntryPoint = "add")]` - `add` is in lower-case, and DLL exports are case-sensitive. Could that be the issue here? – Ken White Jan 09 '23 at 04:49
  • @Ken White: I've tried it both ways, with "add" and "Add". The results are the same. – Greg Bishop Jan 11 '23 at 02:40
  • OK. Just a thought. – Ken White Jan 11 '23 at 02:45
  • Is your Delphi application 32bit or 64bit? – NineBerry Feb 22 '23 at 03:42
  • @NineBerry: The Delphi application is 64-bit. – Greg Bishop Feb 24 '23 at 00:14

2 Answers2

0
StringFromDLL := IntToStr(TTestClass.TestAdd(4,10));
ShowMessage(StringFromDLL);

Tested on Lazarus 2.2.4 (FPC 3.2.2) x86_64-win64

result

On Lazarus 2.2.0 FPC 3.2.2 i386-win32, "NativeLibrary.dll" not loaded (incompatibility of 32-bit "Test Application" and 64-bit "NativeLibrary.dll")

Jeremy Caney
  • 7,102
  • 69
  • 48
  • 77
type t
  • 1
  • 2
0

For us, the solution was to use cdecl instead of stdcall. If I remember correctly stdcall can only be used for 32bit DLLs. But I can not find the link anymore

type TAddFunc = function(a: Integer; b: Integer): Integer; cdecl;

Hope it helps you further

SQueek
  • 617
  • 4
  • 8