0

I am calling a Delphi function from C# and get the error:

Managed Debugging Assistant 'PInvokeStackImbalance' has detected a problem in ...

I've exhausted attempts changing the .Net code to fit the Delphi signatures, why it doesn't work with basic Integers has me stumped, does anyone know where I am going wrong?

Even the simplest function using 2 integers produces the error.

I'm targeting x86 and have put in a couple of hours research but the following solutions haven't helped here, here and here and this one.

This is the Delphi Code (compiled DLL version can be downloaded from here):

unit PasBallEntry;
interface

procedure EntryPoint( InInt: integer; InStr: PChar;
                      var OutInt: integer; var OutStr: PChar); stdcall;

procedure ReleaseString( OutStr: PChar); stdcall;


procedure TimTamC( InInt: integer; InStr: PChar;
                   var OutInt: integer; var OutStr: PChar); cdecl;

procedure ReleaseStringC( OutStr: PChar); cdecl;

procedure TimTamCS( InInt: integer; InStr: PChar;
                    var OutInt: integer; var OutStr: PChar); cdecl; stdcall;

procedure ReleaseStringCS( OutStr: PChar); cdecl; stdcall;

procedure OneTwoS( var A, B: integer); stdcall;
procedure OneTwoC( var A, B: integer); cdecl;
procedure OneTwoCS( var A, B: integer); cdecl; stdcall;

exports
EntryPoint    name 'TimTam',
ReleaseString name 'ReleaseString';

implementation

uses Windows, SyncObjs, Classes, Generics.Collections;

var
  Gate: TCriticalSection;
  Strs: TStrings;
  StrP: TList<PChar>;

procedure EntryPoint( InInt: integer; InStr: PChar;
                      var OutInt: integer; var OutStr: PChar);
var
  InStrL, OutStrL: string;
begin
  OutInt  := 2 * InInt;
  InStrL  := InStr;
  OutStrL := InStrL + '_OUT!';
  UniqueString( OutStrL);
  if OutStrL = '' then
      OutStr := nil
    else
      begin
      OutStr := PChar( OutStrL);
      Gate.Enter;
      Strs.Add( OutStrL);
      StrP.Add( OutStr );
      Gate.Leave
      end
end;

procedure ReleaseString( OutStr: PChar);
var
  I: integer;
begin
  if not assigned( OutStr) then exit;
  Gate.Enter;
  StrP.Insert( I, OutStr);
  if I >= 0 then
    begin
    StrP.Delete( I);
    Strs.Delete( I)
    end;
  Gate.Leave
end;

procedure TimTamC( InInt: integer; InStr: PChar;
                   var OutInt: integer; var OutStr: PChar);
begin
  EntryPoint( InInt, InStr, OutInt, OutStr)
end;

procedure ReleaseStringC( OutStr: PChar);
begin
  ReleaseString( OutStr)
end;

procedure TimTamCS( InInt: integer; InStr: PChar;
                    var OutInt: integer; var OutStr: PChar);
begin
  EntryPoint( InInt, InStr, OutInt, OutStr)
end;

procedure ReleaseStringCS( OutStr: PChar);
begin
  ReleaseString( OutStr)
end;

procedure OneTwoS( var A, B: integer);
begin
  A := 1;
  B := 2
end;

procedure OneTwoC( var A, B: integer);
begin
  A := 1;
  B := 2
end;

procedure OneTwoCS( var A, B: integer);
begin
  A := 1;
  B := 2
end;

initialization
  Gate := TCriticalSection.Create;
  Strs := TStringList.Create;
  StrP := TList<PChar>.Create

finalization
  Strs.Free;
  Gate.Free;
  StrP.Free

end.

Here is the .Net code:

[DllImport("PascalBall.dll", EntryPoint = "TimTam", CallingConvention = CallingConvention.Cdecl)]
public static extern void OneTwoC(ref int a, ref int b);

[DllImport("PascalBall.dll", EntryPoint = "TimTam", CallingConvention = CallingConvention.StdCall)]
public static extern void OneTwoS(ref int a, ref int b);

[DllImport("PascalBall.dll", EntryPoint = "TimTam", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
public static extern void TimTamC(int inputInt, string inputString, ref int outputInt, ref string outputString);

private void button1_Click(object sender, EventArgs e)
{
    int a = 0;
    int b = 0;

    //Both these PInvoke calls fail (either StdCall or Cdecl)
    OneTwoS(ref a, ref b);

    OneTwoC(ref a, ref b);

    System.Diagnostics.Debug.WriteLine(a + b);
}

private void button2_Click(object sender, EventArgs e)
{
    int outInt = 1;
    string outStr = "world";
    const int stringBufferSize = 1024;
    var outputStringBuffer = new String('\x00', stringBufferSize);

    try
    {
        TimTamC(1, outputStringBuffer, ref outInt, ref outputStringBuffer);
        ReleaseString(ref outStr);
    }
    catch (Exception ex)
    {
    }
}

Edit 1: I think I have the EntryPoint correct using TimTam, because I get a System.EntryPointNotFoundException if I try anything else, see here:

enter image description here

Community
  • 1
  • 1
Jeremy Thompson
  • 61,933
  • 36
  • 195
  • 321
  • Why does `OneTwoC` have entry point `TimTam`? – Joachim Isaksson Feb 08 '16 at 05:08
  • I edited my question to show `exports EntryPoint name 'TimTam'`, but not having any Delphi experience this could be the problem (because I've tried almost every other permutation of datatype mapping to satisfy the signatures). Ultimately the `TimTamC(1, outputStringBuffer, ref outInt, ref outputStringBuffer);` is the function I really need to call, the OneTwo examples are me cutting down a bare bones repro. – Jeremy Thompson Feb 08 '16 at 05:14
  • 1
    Well, `OneTwoC` and `OneTwoS` do not have the same signature as `EntryPoint` they're wrapping, so calling it will most likely give the stack imbalance you're talking about. `TimTamC` looks reasonable, but I know strings in Delphi (used to) require special care in interop with C#, and I doubt I can help you get it right without Delphi to test on so I'll have to leave the answer to someone else :) – Joachim Isaksson Feb 08 '16 at 05:43
  • I compiled the Delphi DLL for download [here](http://tempsend.com/C1E8061686) as I knew this would deter people from trying. Either way thanks for your advice so far. And you're right with Delphi strings and interop, I think the solution is to use `WideString` as David Hefferman points out here: http://stackoverflow.com/a/26043567/495455 but will need a Delphi colleague to change his code for me to try that way. – Jeremy Thompson Feb 08 '16 at 05:45

1 Answers1

1

There are a very large number of mistakes here. The immediate problem is here:

[DllImport("PascalBall.dll", EntryPoint = "TimTam", CallingConvention = CallingConvention.Cdecl)]
public static extern void OneTwoC(ref int a, ref int b);

[DllImport("PascalBall.dll", EntryPoint = "TimTam", CallingConvention = CallingConvention.StdCall)]
public static extern void OneTwoS(ref int a, ref int b);

Why are you specifying EntryPoint = "TimTam"? That function is not the one you are trying to import and has an incompatible signature. Hence the stack imbalance error.

You need to export OneTwoS and OneTwoC by adding them to the Delphi exports clause. And you need to import these functions in the C# by removing the erroneous EntryPoint specification.

You functions using strings are wrong too and can't be fixed without changing both sides of the code. The simple fix is to use WideString parameters in Delphi, var parameters. Map that to ref string in C#, marshaled as UnmanagedType.BStr. The answer you linked to in comments shows you how: https://stackoverflow.com/a/26043567/495455

Community
  • 1
  • 1
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • Thanks David, I just got home after a frustrating day of work, like Joachim-isaksson's intuition I know you've nailed the root cause with the 2 integer methods. While I have your time, am I correct in thinking the WideString is the solution to the problem with the TimTam**C** call? It produces the same imbalance error and getting that function working is the ultimate outcome. Really appreciate your support here and will update this thread tomorrow as the Delphi colleague & I are going to sit together to nut it out – Jeremy Thompson Feb 08 '16 at 07:55
  • Thxs your update is exactly what we needed to know, will award once verified. – Jeremy Thompson Feb 08 '16 at 07:57
  • 1
    Stack imbalance on TimTamC is calling convention mismatch. Cdecl vs stdcall. When you fix that the code still won't work. Because of string marshalling errors. – David Heffernan Feb 08 '16 at 08:03
  • Frankly the Delphi code is all useless and wrong. Sorry. Throw it all away. All of it. Marshal things exactly like the answer linked to in my answer above. No lists needed. No locking needed. Concentrate on one function at a time. The code here is far too complex to understand. You are just confusing yourself. – David Heffernan Feb 08 '16 at 08:05
  • Hahaha, my colleague won't like that but will accept you're such a legend! – Jeremy Thompson Feb 08 '16 at 08:06
  • 1
    Sorry!! But it really is irredeemable. Anyway, that other linked answer has all you need. – David Heffernan Feb 08 '16 at 08:22