PInvokeStackImbalance when calling Delphi function from C# application - c#

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

Related

Using Delphi DLL in C#

I have a third party "mystery dll" written with Delphi(unknown version), working example in delphi (past 2009), dire need to use said dll in my C# code, and almost no relevant knowledge on how to do it.
Here is Delpi example in using this dll:
type
TD_Query = function(host: WideString; port : Word;pud,query : WideString):WideString; stdcall;
procedure TForm11.Button6Click(Sender: TObject);
var
Handle : LongWord;
D_Query : TD_Query;
sss : WideString;
begin
Handle := LoadLibrary('kobrasdk.dll');
sss:='';
if Handle <> 0 then
begin
#D_Query := GetProcAddress(Handle, 'D_Query');
sss:=D_Query('host',8201,'pud','query');
FreeLibrary(Handle);
end;
end;
And here is my attempts to interpret it in C#:
class Program
{
[DllImport("C:\\Games\\kobrasdk.dll", CallingConvention = CallingConvention.StdCall,
CharSet = CharSet.Ansi)]
[return: MarshalAs(UnmanagedType.LPStr)]
public static extern string D_Query(string host, ushort port, string pud, string query);
static void Main(string[] args)
{
D_Query("test", 8201, "test", "test");
}
}
Unfortunately, what I have is an error: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
From what I read during the day, I probably fudged up with return type, or parameter types. Help?
The Delphi ABI differs from the Microsoft ABI for certain types. A Delphi WideString is a managed type (in Delphi terminology) and as return types use an ABI that is incompatible with Microsoft tools.
The Delphi ABI translates a managed return type into a hidden var parameter. So the compiler transforms:
function(host: WideString; port: Word; pud, query: WideString): WideString; stdcall;
into
procedure(var result: WideString; host: WideString; port: Word; pud, query: WideString);
stdcall;
You can therefore access your original Delphi function from C# by importing the function in its transformed guise.
[DllImport(#"...", CallingConvention = CallingConvention.StdCall)]
public static extern void My_D_Query(
[MarshalAs(UnmanagedType.BStr)]
out string result,
[MarshalAs(UnmanagedType.BStr)]
string host,
ushort port,
[MarshalAs(UnmanagedType.BStr)]
string pud,
[MarshalAs(UnmanagedType.BStr)]
string query
);
I mostly figured it out. For some reason unclear to me, C# cant handle WideString return values. If you have access to delphi source code, it might be appropriate to exchange function with procedure, and pass return value as "out" parameter. In my case, I did not have access to source, so I was forced to write a proxy DLL to do so.
For example above, "proxy" dll code:
type
TD_Query = function(host : WideString;port : Word;pud,query : WideString):WideString; stdcall;
procedure My_D_Query(host: WideString; port: Word; pud, query: WideString; out return : WideString); stdcall;
var
Handle: LongWord;
D_Query : TD_Query;
sss : WideString;
begin
Handle := LoadLibrary('kobrasdk.dll');
sss:='';
if Handle <> 0 then
begin
#D_Query:=GetProcAddress(Handle, 'D_Query');
sss:=D_Query(host,port,pud,query);
FreeLibrary(Handle);
end;
return := sss;
end;
Then C# code to access it:
[DllImport("C:\\MyDll.dll", CallingConvention = CallingConvention.StdCall,
CharSet = CharSet.Ansi)]
public static extern void My_D_Query(
[MarshalAs(UnmanagedType.BStr)]
string host,
int port,
[MarshalAs(UnmanagedType.BStr)]
string pud,
[MarshalAs(UnmanagedType.BStr)]
string query,
[MarshalAs(UnmanagedType.BStr)]
out string result
);
Its not pretty, but for me, it was the answer.

Call Delphi Function From C#

I have a below DLL source code.
library Project1;
uses
System.SysUtils,
System.Classes;
type
IStringFunctions = interface
['{240B567B-E619-48E4-8CDA-F6A722F44A71}']
function GetMethodValueAsString():PAnsiChar; stdcall;
end;
TStringFunctions = class(TInterfacedObject, IStringFunctions)
public
function GetMethodValueAsString():PAnsiChar; stdcall;
end;
{$R *.res}
function TStringFunctions.GetMethodValueAsString():PAnsiChar; stdcall;
begin
Result := 'test';
end;
procedure GetImplementation(out instance:IStringFunctions); stdcall; export;
begin
instance := TStringFunctions.Create;
end;
exports GetImplementation;
begin
end.
I want to using in C# like this
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace ConsoleApplication1
{
[ComVisible(true)]
[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("240B567B-E619-48E4-8CDA-F6A722F44A71")]
public interface IStringFunctions
{
[MethodImplAttribute(MethodImplOptions.PreserveSig)]
[return: MarshalAs(UnmanagedType.AnsiBStr)]
string GetMethodValueAsString();
}
class Program
{
[DllImport("kernel32.dll", EntryPoint = "LoadLibrary", CallingConvention = CallingConvention.StdCall)]
static extern int LoadLibrary([MarshalAs(UnmanagedType.LPStr)] string lpLibFileName);
[DllImport("kernel32.dll", EntryPoint = "GetProcAddress", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi)]
static extern IntPtr GetProcAddress(int hModule, [MarshalAs(UnmanagedType.LPStr)] string lpProcName);
[DllImport("kernel32.dll", EntryPoint = "FreeLibrary", CallingConvention = CallingConvention.StdCall)]
static extern bool FreeLibrary(int hModule);
[UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Ansi)]
delegate void GetImplementation([MarshalAs(UnmanagedType.Interface)] out IStringFunctions instance);
static void Main(string[] args)
{
const string dllName = "Project1.dll";
const string functionName = "GetImplementation";
int libHandle = LoadLibrary(dllName);
if (libHandle == 0) throw new Exception(string.Format("Could not load library \"{0}\"", dllName));
var delphiFunctionAddress = GetProcAddress(libHandle, functionName);
if (delphiFunctionAddress == IntPtr.Zero) throw new Exception(string.Format("Can't find function \"{0}\" in library \"{1}\"", functionName, dllName));
GetImplementation getImplementation = (GetImplementation)Marshal.GetDelegateForFunctionPointer(delphiFunctionAddress, typeof(GetImplementation));
if (getImplementation != null)
{
IStringFunctions instance = null;
getImplementation(out instance);
if (instance != null)
{
//!!! don't return value !!!!
String result = instance.GetMethodValueAsString();
Console.WriteLine(result);
}
}
Console.ReadLine();
}
}
}
But instance.GetMethodValueAsString method doesn't working. And exit code.
I want to use returning value from dll function(GetMethodValueAsString) in c#.
I don't understand.
Where's my fault?
Thank you so much
[return: MarshalAs(UnmanagedType.AnsiBStr)]
This is wrong. You are not returning an ANSI encoded string, allocated on the COM heap. You are returning a plain C string, a pointer to null-terminated array of ANSI characters.
Your interface declaration should be:
[MethodImplAttribute(MethodImplOptions.PreserveSig)]
IntPtr GetMethodValueAsString();
Calling the method must be done like this:
IntPtr ptr = instance.GetMethodValueAsString();
string result = Marshal.PtrToStringAnsi(ptr);
Of course, your interface design becomes rather impractical when you need to return a dynamically allocated string. You'd need to export a deallocator too. The clean way to deal with this is to use a BSTR. Like this:
Delphi
IStringFunctions = interface
['{240B567B-E619-48E4-8CDA-F6A722F44A71}']
procedure GetMethodValueAsString(out value: WideString); stdcall;
end;
C#
[MethodImplAttribute(MethodImplOptions.PreserveSig)]
void GetMethodValueAsString([MarshalAs(UnmanagedType.BStr)] out string result);
Is that Delphi DLL visible to your C# code for COM Interop? If not, the easiest way to do this would be to attach the dll to the C# class library project using the "Add Existing Item" menu option. Then in the properties window for this dll set "BuildAction" to None, and "Copy to Output Directory" to "Copy always"
Then you could do something like this in the C# code.
[DllImport("Project1.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi)]
public static extern string GetMethodValueAsString();
Then wherever you need to call that function you could do
var outputMessage = GetMethodValueAsString();
Console.WriteLine(outputMessage);

PInvokeStackImbalance when calling Delphi dll function

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.

How do I get P/Invoking a pointer to a pointer to a struct working from a function parameter?

I'm trying to P/invoke this in such a way I should be able to access the const char* members of this struct from C# (system, game, song, copyright, etc).
This is the struct as it is defined in the C++ header here: gme.h starting at line 79
struct gme_info_t
{
/* times in milliseconds; -1 if unknown */
int length; /* total length, if file specifies it */
int intro_length; /* length of song up to looping section */
int loop_length; /* length of looping section */
/* Length if available, otherwise intro_length+loop_length*2 if available,
otherwise a default of 150000 (2.5 minutes). */
int play_length;
int i4,i5,i6,i7,i8,i9,i10,i11,i12,i13,i14,i15; /* reserved */
/* empty string ("") if not available */
const char* system;
const char* game;
const char* song;
const char* author;
const char* copyright;
const char* comment;
const char* dumper;
const char *s7,*s8,*s9,*s10,*s11,*s12,*s13,*s14,*s15; /* reserved */
};
In my C# code, I have modeled this struct as follows:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct gme_info_t
{
public int length;
public int introLength;
public int loopLength;
public int playLength;
public int i4, i5, i6, i7, i8, i9, i10, i11, i12, i13, i14, i15;
public string system;
public string game;
public string song;
public string author;
public string copyright;
public string comment;
public string dumper;
public string s7, s8, s9, s10, s11, s12, s13, s14, s15;
}
Now, the function I'm calling is one that I have P/Invoked which has the C++ prototype as follows:
gme_err_t gme_track_info( Music_Emu const* me, gme_info_t** out, int track )
where gme_err_t is a const char*
(see gme.h, line 74, if you want a direct look at it)
(see gme.cpp, line 252 for its definition)
So, the function without all of its typedefs is as follows:
const char* gme_track_info( Music_Emu const* me, gme_info_t** out, int track )
The way this function works is that when called with a valid Music_Emu and a valid track,
the result is info about the music track which is assigned to the parameter 'out'.
The const char* being returned is basically for when errors occur, so it's not the main focus. The 'out' parameter is.
I have defined the P/Invoke for this function in C# as follows:
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
public static extern string gme_track_info(IntPtr emuHandle, out gme_info_t trackInfo, int track);
Here is my code currently for attempting to read the copyright string in that struct.
static void Main()
{
// Initialize the MusicEmu reference first (this works fine).
IntPtr emuRef;
string initEmuRef = NativeMethods.gme_open_file("Adventure Island 4.nsf", out emuRef, 48000);
Console.WriteLine("Error Message (if any): " + initEmuRef);
// Now get the track info.
gme_info_t trackInfo;
NativeMethods.gme_track_info(emuRef, out trackInfo, 0);
Console.WriteLine("Copyright: " + trackInfo.copyright); // I get an empty string. When checked with a different NSF reader it prints "1994 Hudson Soft."
// Keep console window up.
Console.ReadLine();
}
Any help would be appreciated. I've tried for roughly 4 hours to make this work. I've looked all around StackOverflow (and the net in general) for possible solutions, but I haven't found any that were close to this sort of question. Most other problems were about a pointer to a pointer to a struct of arrays and such, which isn't very helpful at all for this case.
If any other information is needed, just ask, I'd be happy to provide it.
Your trackInfo parameter should be an out IntPtr. Then you can marshal it to a C# struct with the Marshal.PtrToStructure method.
IntPtr trackInfoPtr;
NativeMethods.gme_track_info(emuRef, out trackInfoPtr);
gme_info_t trackInfo = (gme_info_t)Marshal.PtrToStructure(trackInfoPtr, typeof(gme_info_t));

Returning a string from delphi dll to C# caller in 64 bit

I have a C# application which calls native Delphi dll using the following code:
C#
[DllImport("NativeDLL.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall)]
public static extern int GetString(string out str);
Delphi
function GetString(out a: PChar): Integer; stdcall;
begin
a := PChar('abc');
Result := 1;
end;
which works fine in a 32 bit application. But when I compile both C# exe and Delphi dll for 64 bit I get a strange problem. After a call to GetString in Delphi debugger I can see that an exception is raised somewhere in the .NET code and the following string appears in the Debugger Output window: "Critical error detected c0000374". Google says that this error is related to heap corruption.
I tried using ref/var parameters modifiers instead of out/out. Still no luck. Why do I get this error? Should I use a different calling convention for 64 bit?
BTW. The following combination works fine:
C#
[DllImport("NativeDLL.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall)]
public static extern string GetString(string a);
Delphi
function GetString(a: PChar): PChar; stdcall;
var
inp: string;
begin
inp := a;
Result := PChar('test ' + inp);
end;
works fine. But I do need to return a string as an out parameter.
You cannot pass a string from native managed that way. Your code is wrong in 32 bit also, you just happen to get away with it. The second version of the code is also wrong. It only appears to work.
You need to either:
Allocate from a shared heap so that that the managed code can deallocate off that heap. The shared heap for p/invoke is the COM heap.
Allocate the memory on the managed side, and copy the contents into that buffer on the native side.
Option 2 is always preferable. It looks like this:
[DllImport("NativeDLL.dll", CharSet = CharSet.Unicode)]
public static extern int GetString(StringBuilder str, int len);
On the native side you would have
function GetString(str: PChar; len: Integer): Integer; stdcall;
begin
StrLCopy(str, 'abc', len);
Result := 1; // real code would have real error handling
end;
Then call this like so:
StringBuilder str = new StringBuilder(256);
int retval = GetString(str, str.Capacity);
If you want to try option 1, it looks like this on the managed side:
[DllImport("NativeDLL.dll", CharSet = CharSet.Unicode)]
public static extern int GetString(out string str);
and like this native:
function GetString(out str: PChar): Integer; stdcall;
begin
str = CoTaskMemAlloc(SizeOf(Char)*(Length('abc')+1));
StrCopy(str, 'abc');
Result := 1; // real code would have real error handling
end;
When the managed code copies the contents of str to the string value, it then calls CoTaskMemFree on the pointer that you returned.
And this is trivially easy to call:
string str;
int retval = GetString(out str);

Categories