Reputation: 347
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
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