2
votes

A (non-COM) Delphi dll has a function being exported:

function GetQuestions(Digit1, Digit2: string; CountryISO: string):string;

I have added this dll as an existing item in Visual Studio 2012 and have set its Build Action to None, Copy to Output Directory to Copy Always.

The class containing the DllImportAttribute:

public class RefundLibrary
{
    [DllImport("RefundLibrary.dll", EntryPoint = "GetQuestions", 
        CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
    public static extern IntPtr GetQuestions(string digit1, string digit2, 
        string countryISO);
}

When I call this method in the Page_Load of a WebForm (not sure if relevant), it throws a PInvokeStackImbalance for a possible signature mismatch on the below indicated line:

protected void Page_Load(object sender, EventArgs e)
{
    IntPtr str = RefundLibrary.GetQuestions("", "", "BE"); //<- here
    string result = Marshal.PtrToStringUni(str);

    testp.InnerText = result;
}

I also tried to change the DllImport method's return type to string, the error is identical.

I figure the Marshal.PtrToStringUni(str) is correct as far as the Embarcadero docs go?

In RAD Studio, string is an alias for UnicodeString

Is this really a signature mismatch? What am I missing (except, obviously, a decent understanding of P/Invoke)?

1
Are you sure it's StdCall? Imbalance usually happens when that should be cdecl.plinth
Also, I don't think you can import Delphi functions with signatures that use Delphi String arguments in .NET and expect it to Just Work. Delphi strings are RTL-managed structures, with special memory handling, reference counting, and so on.500 - Internal Server Error
@plinth setting CallingConvention.Cdecl doesn't change anything.Wim Ombelets
@500-InternalServerError I also attempted a void method with an out param instead but then I just get an AccessViolation so I'm pretty sure that isn't it either (although I've used this approach before with COM dll's and there it seems to work)Wim Ombelets
Delphi strings are not compatible with anything except Delphi strings. Use PChar (or PAnsiChar) on the Delphi side. You clearly can't return a string, either; your DLL should be receiving a pre-allocated buffer (from .NET you can use a StringBuilder that has capacity allocated) and the buffer size as a parameter and then filling that buffer to return data. (An alternative is to have the .NET side call the function once with a nil buffer; the DLL returns the size of buffer needed. The .NET code then allocates space and passes the proper sized buffer to the DLL in a second call to the function.)Ken White

1 Answers

3
votes

You cannot call that function. It uses the Delphi only register calling convention, and uses Delphi strings. Change it to:

procedure GetQuestions(
    Digit1: WideString;
    Digit2: WideString; 
    CountryISO: WideString;
    out Questions: WideString
); stdcall;

On the C# side:

[DllImport("RefundLibrary.dll")]
public static extern void GetQuestions(
    [MarshalAs(UnmanagedType.BStr)]
    string digit1,
    [MarshalAs(UnmanagedType.BStr)]
    string digit2,
    [MarshalAs(UnmanagedType.BStr)] 
    string countryISO,
    [MarshalAs(UnmanagedType.BStr)]
    out string questions
);

The use of WideString/BStr is great for the out parameter. Because the content is allocated on the shared COM heap which means that the caller can deallocate it.

You could use PWideChar/LPWStr for the input parameters. That would work fine. I used WideString because I wanted to be consistent. It's up to you.