White
White

Reputation: 347

C# Delphi Interop handling of strings

I have C# DLL (.net8.0-windows7.0) using NuGet DNNE.

This is the source code:

using System.Runtime.InteropServices;
using System.Windows;

namespace DNNETests
{
    public class Class1
    {

        [UnmanagedCallersOnly]
        public static void Attach()
        {
            MessageBox.Show("Hallo c# debugger");
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct IsInputFileVersionCompatibleResult
        {
            public int resultCode;
            public IntPtr msg;
            public IntPtr mostRecentRelatedPPPVersion;
        }

        [UnmanagedCallersOnly]
        public static IntPtr Test(int version, IntPtr pstr1, IntPtr pstr2, int version2)
        {
            string str1 = Marshal.PtrToStringBSTR(pstr1) ?? string.Empty;
            string str2 = Marshal.PtrToStringBSTR(pstr2) ?? string.Empty;
            MessageBox.Show("parameter: " + version+Environment.NewLine+
                str1+Environment.NewLine+
                str2+Environment.NewLine+
                version2);
            return Marshal.StringToBSTR("return value");
        }

    }
}

And I have a Delphi application consuming the DLL:

unit Unit1;

interface

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

type
    PAttachToDLL = procedure();
    PTest = function(version : Integer; str1: WideString; str2 : PWideString; version2 : integer):WideString;

  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
  private
    dllAttachToDLL: PAttachToDLL;
    dllTest : PTest;
    procedure makeTestCall;
  public
    { Public-Deklarationen }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
var
  dllhandle: THandle;
begin
  dllhandle := SafeLoadLibrary('C:\path\to\DNNETests\bin\Debug\net8.0-windows7.0\DNNETestsNE.dll');
  @dllAttachToDLL := GetProcAddress(dllhandle, 'Attach');
  @dllTest := GetProcAddress(dllhandle, 'Test');
  dllAttachToDLL();
  makeTEstCall();
  Application.Terminate();
end;

procedure TForm1.makeTestCall();
var
  tmp:WideString;
  ptmp : PWideString;
  str1 : WideString;
  str2 : WideString;
begin
  str1:=WideString('normal widestring');
  str2:=WideString('and here pwidestring');
//  ptmp:=dllTest(8, str1, PWideString(str2), 7);
  tmp:=dllTest(8, (str1), PWideString(str2), 7);
  tmp:=WideString(ptmp);
  ShowMessage(tmp);
end;

end.

When stopping with the debugger on the entry of C# public static IntPtr Test(int version, IntPtr pstr1, IntPtr pstr2, int version2) I can see that both integer parameters are destroyed and the the first IntPtr parameter also is damaged:

Variable Name Value Data Type
version 1375376 int
pstr1 0x0000000000000008 System.IntPtr
pstr2 0x00000000043e73c8 System.IntPtr
version2 71189672 int
Marshal.PtrToStringBSTR(pstr1) 'Marshal.PtrToStringBSTR(pstr1)' threw an exception of type 'System.NullReferenceException' string {System.NullReferenceException}
Marshal.PtrToStringBSTR(pstr2) "normal widestring" string

Now I change the return type in Delphi to PWideString instead of WideString PTest = function(version : Integer; str1: WideString; str2 : PWideString; version2 : integer):PWideString;

The parameters are correctly transfered to C#

Variable Name Value Data Type
version 8 int
pstr1 0x0000000003259a88 System.IntPtr
pstr2 0x0000000003257f98 System.IntPtr
version2 7 int
Marshal.PtrToStringBSTR(pstr1) "normal widestring" string
Marshal.PtrToStringBSTR(pstr2) "and here pwidestring" string

Question is: Why do I need to declare the return type of a function as PWideString and not only as a WideString and why I don't need to send string parameters from Delphi to C# as PWideString also? (Regarding the two str parameters, one is PWideString the other WideString)

Both parts of the project (C# and Delphi) are compiled in x64 mode.

Upvotes: 0

Views: 107

Answers (1)

Remy Lebeau
Remy Lebeau

Reputation: 595114

On the Delphi side, you forgot to specify stdcall as the calling convention for PTest. Delphi's default register calling convention is not compatible with C#. So that right there will corrupt the parameters and call stack, if you are compiling both ends for 32bit.

Also, typecasting a WideString variable directly to PWideString the way you are doing is just plain wrong. A PWideString is a pointer to a WideString variable, not a pointer to a BSTR. You are typecasting the BSTR pointer that the WideString holds internally. Marshal.PtrToStringBSTR() expects a raw pointer to a valid BSTR, not a PWideString pointer.

You need to change PTest to accept the WideString values as-is (which are already compatible with BSTR), or as PWideChar. Not PWideString.

Try this:

PTest = function(version : Integer; str1, str2 : WideString {or PWideChar}; version2 : integer): WideString; stdcall;

Upvotes: 0

Related Questions