PInvokeStackImbalance when calling Delphi dll function - c#

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)?

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.

Related

C# import dll function with const string& as parameter

I have written a Banking System DLL in C++ with a C Interface with the following function that is used to rename a customer:
int renameCustomer(const string& firstname_, const string& name_, const unsigned int customerId_);
Now I want to use the function in a .NET Assembly. I tried to import the function from the dll this way
[DllImport("BankManagement.dll", EntryPoint = "renameCustomer", CallingConvention = CallingConvention.Cdecl)]
public static extern int renameCustomer(ref string firstname_, ref string name_, uint customerId_);
and I would like to use it in the following function:
public int changeCustomerName(uint id_, string firstname_, string name_)
{
return renameCustomer(ref firstname_, ref name_, id_);
}
In a test application I call the function this way:
BankManageIntern.BankIntern myBankIntern = new BankManageIntern.BankIntern();
int checkCust = myBankIntern.changeCustomerName(0, "Name", "Firstname");
My Logger shows me that the customer could not be renamed because the input for name is empty. Now I believe that I made a mistake passing the strings to the functions. I have already tried all kinds of ways to pass the string, which I found on Google, but nothing works. Does anybody of you have an idea? The customer exists and the customer id is valid. I'm sure that's not the problem.
One condition is that I may not change the Function of the DLL. I can only make changes in the .NET Assembly and the Application.
try the following:
instead of trying to pass an string, use an pointer to an char. this can be done by using an IntPtr.
private static extern int renameCustomer( IntPtr firstname, IntPtr lastname, uint id);
in your Application you can use the var datatype. It's similar to the auto_ptr in C++.
var firstnamePtr = Marshal.StringToHGlobalAnsi(firstname);
var lastnamePtr = Marshal.StringToHGlobalAnsi(lastname);
and call with that your function from the DLL.
var status = renameCustomer(firstnamePtr, lastnamePtr, id);
Hope that will help you with your problem! :)

PInvokeStackImbalance when calling Delphi function from C# application

I am forced to work with unmanaged delphi dll. I dont have an access to the source code. Only vague documentation:
type
TServiceData = packed record
DBAlias: PChar;
LicKey: PChar;
Pass: PChar;
end;
PServiceData = ^TServiceData;
function CreateRole(SrvData: PServiceData; var UserName: PChar): byte; stdcall;
UserName is supposed to be an out param.
My C# code:
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct SERVICE_DATA
{
public string DBALias;
public string LicKey;
public string Pass;
}
[DllImport(dllname, CallingConvention = CallingConvention.StdCall)]
public static extern byte CreateRole(SERVICE_DATA data, out string str);
I have no idea what can cause the stack imbalance (except calling convention which seems to be correct). I dont know if strings in my structure are marshalled correctly but according to other threads this would not cause PStackImbalanceException. Any help will be much appreciated:)
EDIT.
I have implemented suggestions from David and now I am getting access violation exception:
"Unhandled Exception: System.AccessViolationException: Attempted to read or write protected memory. This is often an indication that other memory is corrupt”
My structure and method declaration is just copy-pasted from the answer, there is nothing fancy in the way I am calling it:
string str;
var data = new SERVICE_DATA();
data.DBALias = "test";
data.LicKey = "test";
data.Pass = "test";
var result = CreateRole(ref data, out str);
There are a couple of things wrong with the translation:
The Delphi code receives a pointer to the record. The C# code passes it by value. This is the reason for the stack imbalance warning.
The user name parameter is probably incorrectly handled on the Delphi side. It would need to be a pointer to dynamically allocated memory, allocated on the COM heap by a call to CoTaskMemAlloc. I'd guess that you aren't doing that and so you'll hit problems when the marshaller attempts to deallocate the pointer with a call to CoTaskMemFree.
I'd probably use the COM string type for the strings. I would also avoid packing records because that is bad practise as a general rule.
I'd write it like this:
Delphi
type
TServiceData = record
DBAlias: WideString;
LicKey: WideString;
Pass: WideString;
end;
function CreateRole(const SrvData: TServiceData; out UserName: WideString): Byte;
stdcall;
C#
[StructLayout(LayoutKind.Sequential)]
public struct SERVICE_DATA
{
[MarshalAs(UnmanagedType.BStr)]
public string DBALias;
[MarshalAs(UnmanagedType.BStr)]
public string LicKey;
[MarshalAs(UnmanagedType.BStr)]
public string Pass;
}
[DllImport(dllname, CallingConvention = CallingConvention.StdCall)]
public static extern byte CreateRole(
[In] ref SERVICE_DATA data,
[MarshalAs(UnmanagedType.BStr)] out string str
);
Here is a complete test project to show that this works as expected:
Delphi
library Project1;
type
TServiceData = record
DBAlias: WideString;
LicKey: WideString;
Pass: WideString;
end;
function CreateRole(const SrvData: TServiceData; out UserName: WideString): Byte;
stdcall;
begin
UserName := SrvData.DBAlias + SrvData.LicKey + SrvData.Pass;
Result := Length(UserName);
end;
exports
CreateRole;
begin
end.
C#
using System;
using System.Runtime.InteropServices;
namespace ConsoleApplication1
{
class Program
{
const string dllname = #"...";
[StructLayout(LayoutKind.Sequential)]
public struct SERVICE_DATA
{
[MarshalAs(UnmanagedType.BStr)]
public string DBALias;
[MarshalAs(UnmanagedType.BStr)]
public string LicKey;
[MarshalAs(UnmanagedType.BStr)]
public string Pass;
}
[DllImport(dllname, CallingConvention = CallingConvention.StdCall)]
public static extern byte CreateRole(
[In] ref SERVICE_DATA data,
[MarshalAs(UnmanagedType.BStr)] out string str
);
static void Main(string[] args)
{
SERVICE_DATA data;
data.DBALias = "DBALias";
data.LicKey = "LicKey";
data.Pass = "Pass";
string str;
var result = CreateRole(ref data, out str);
Console.WriteLine(result);
Console.WriteLine(str);
}
}
}
Output
17
DBALiasLicKeyPass

AccessViolationException when calling C function from dll on C#

I'm trying to use PInvoke in order to call an unmanaged function from a C dll. Due to the fact that the source of the dll can't be released to developers, some of them have called the function in Delphi using the following declaration. We use SAT.dll with a CDECL calling convention.
function AssociarAssinatura( numeroSessao : Longint; codigoDeAtivacao: PChar;
CNPJvalue : PChar; assinaturaCNPJs : PChar ) : PChar ;
cdecl; External 'SAT.DLL';
Based on that structure, I made the following Console Application in C# in order to test the same function from the same DLL. I made some research and found out that the equivalent to Longint in delphi is int in C# and the equivalent of PChar is a pointer to a string (But I used C#'s string).
class Program
{
[DllImport("SAT.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern string AssociarAssinatura(int numeroSessao,
string codigoDeAtivacao, string CNPJvalue, string assinaturaCNPJs);
static void Main(string[] args)
{
Console.WriteLine("Comienza");
int numeroSessao = 111111;
string codigoDeAtivacao = "123123123";
string cnpJvalue = "2222222222222211111111111111";
string assinaturaCnpJs = "lrafwegmqcgvpzpbdmcmcgdvf";
string resposta = AssociarAssinatura(numeroSessao, codigoDeAtivacao,
cnpJvalue, assinaturaCnpJs);
Console.WriteLine(resposta);
}
}
When I call the function, an AccesViolationException is thrown. The code of AssociarAssinatura has some inner prints that show that the code from the function is indeed running well. Due to this I guess the problem is related when the function is returning it's value. My best guess is that somehow I'm having issues with the calling convention. Any thoughts?
Your problem here is most likely related to your PChar type in Delphi. In C#, strings are Unicode by default, and when calling your func, there will actually be a conversion from a PChar to PWideChar, which means a new block of memory will be allocated to hold this new PWideChar. This interop and difference between how strings are handled in .NET and in Delphi is more than likely causing your AccessViolationException.
You can use the MarshalAs attribute to explicitly tell .NET how to handle the specific type:
[DllImport("SAT.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern string AssociarAssinatura(int numeroSessao,
[MarshalAs(UnmanagedType.LPStr)] string codigoDeAtivacao, [MarshalAs(UnmanagedType.LPStr)] string CNPJvalue, [MarshalAs(UnmanagedType.LPStr)] string assinaturaCNPJs);
Which will explicitly specify how the string is handled. After that, your code should be fine.

Passing String from Native C++ DLL to C# App

I have written a DLL in C++. One of the functions writes to a character array.
C++ Function
EXPORT int xmain(int argc, char argv[], char argv2[])
{
char pTypeName[4096];
...
//Other pTypeName ends up populated with "Portable Network Graphics"
//This code verifies that pTypeName is populated with what I think it is:
char szBuff[64];
sprintf(szBuff, pTypeName, 0);
MessageBoxA(NULL, szBuff, szBuff, MB_OK);
//The caption and title are "Portable Network Graphics"
...
//Here, I attempt to copy the value in pTypeName to parameter 3.
sprintf(argv2, szBuff, 0);
return ret;
}
C# Import
//I believe I have to use CharSet.Ansi because by the C++ code uses char[],
[DllImport("FirstDll.dll", CharSet=CharSet.Ansi)]
public static extern int xmain(int argc, string argv, ref string zzz);
C# Function
private void button2_Click(object sender, EventArgs e)
{
string zzz = "";
int xxx = xmain(2, #"C:\hhh.bmp", ref zzz);
MessageBox.Show(zzz);
//The message box displays
//MessageBox.Show displays "IstuÈst¼ÓstÄstlÄstwÄstiÑstõÖstwÍst\
// aÖst[ÖstÃÏst¯ÄstÐstòÄstŽÐstÅstpÅstOleMainThreadWndClass"
}
I have attempted to pass a parameter from C# by reference and have the C++ DLL populate the parameter. Even though I have verified that the value is correct in the DLL, gibberish gets passed to the C# application.
What can I do to write the correct string value to the C# string?
Use a StringBuilder to pass a character array that native code can fill in (see Fixed-Length String Buffers).
Declare the function:
[DllImport("FirstDll.dll", CharSet=CharSet.Ansi)]
public static extern int xmain(int argc, string argv, StringBuilder argv2);
Use it:
// allocate a StringBuilder with enough space; if it is too small,
// the native code will corrupt memory
StringBuilder sb = new StringBuilder(4096);
xmain(2, #"C:\hhh.bmp", sb);
string argv2 = sb.ToString();
Give some other information to the DLLImport call. Look at the following example of my own:
[DllImport("tcpipNexIbnk.dll", EntryPoint = "SendData", CallingConvention = CallingConvention.Cdecl)]
public static extern int Send([MarshalAs(UnmanagedType.LPWStr)]string message);
Notice two things, the CallingConvention parameter:
CallingConvention = CallingConvention.Cdecl)
Use that as it is.
And then just behind the c# string type, you can play with the different Unmanaged types using the MarshalAS instruction, that will cast your C# string parameter to the native string type you have in your c++ program:
public static extern int Send([MarshalAs(UnmanagedType.LPWStr)]string message);
Hope it helps.

Return contents of a std::wstring from C++ into C#

I have an unmanaged C++ DLL that I have wrapped with a simple C interface so I can call PInvoke on it from C#. Here is an example method in the C wrapper:
const wchar_t* getMyString()
{
// Assume that someWideString is a std::wstring that will remain
// in memory for the life of the incoming calls.
return someWideString.c_str();
}
Here is my C# DLLImport setup.
[DllImport( "my.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl )]
private static extern string GetMyString();
However the string is not correctly marshalled, often screwing up the first character or sometimes way off showing a bunch of chinese characters instead. I have logged output from the implementation on the C side to confirm that the std::wstring is correctly formed.
I have also tried changing the DLLImport to return an IntPtr and convert with a wrapped method using Marshal.PtrToStringUni and it has the same result.
[DllImport( "my.dll", CallingConvention = CallingConvention.Cdecl )]
private static extern IntPtr GetMyString();
public string GetMyStringMarshal()
{
return Marshal.PtrToStringUni( GetMyString() );
}
Any ideas?
Update with Answer
So as mentioned below, this is not really an issue with my bindings but the lifetime of my wchar_t*. My written assumption was wrong, someWideString was in fact being copied during my calls to the rest of the application. Therefore it existed only on the stack and was being let go before my C# code could finish marshalling it.
The correct solution is to either pass a pointer in to my method as described by shf301, or make sure my wchar_t* reference does not get moved / reallocated / destroyed before my C# interface has time to copy it.
Returning the std::wstring down to my C layer as a "const &std::wstring" means my call to c_str() will return a reference that won't be immediately dealloc'd outside the scope of my C method.
The calling C# code then needs to use Marshal.PtrToStringUni() to copy data from the reference into a managed string.
You are going to have to rewrite your getMyString function for the reasons mentioned in Hans Passant's answer.
You need to have the C# code pass a buffer in to your C++ code. That way the your code (ok, the CLR Marshaller) controls the lifetime of the buffer and you don't get into any undefined behavior.
Below is an implementation:
C++
void getMyString(wchar_t *str, int len)
{
wcscpy_s(str, len, someWideString.c_str());
}
C#
[DllImport( "my.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode )]
private static extern void GetMyString(StringBuffer str, int len);
public string GetMyStringMarshal()
{
StringBuffer buffer = new StringBuffer(255);
GetMyString(buffer, buffer.Capacity);
return buffer.ToString();
}
You need to specify MarshalAs attribute for the return value:
[DllImport( "my.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
[return : MarshalAs(UnmanagedType.LPWStr)]
private static extern string GetMyString();
Make sure the function is indeed cdecl and that the wstring object is not destroyed when the function returns.

Categories