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.
Related
I'm trying to call few Delphi functions from C#:
MyType =array [1 .. 124] of byte
procedure f(bytes: MyType); stdcall;
external 'my.dll' name 'f';
That's my first problem. I've tried:
[DllImport("Delphi/my.dll",
CallingConvention = CallingConvention.StdCall,
CharSet = CharSet.Auto)]
public static extern
void sygLadSyg([MarshalAs(UnmanagedType.LPArray)] byte[] myArray);
void sygLadSyg([MarshalAs(UnmanagedType.SafeArray)] byte[] myArray);
I get exception:
A call to PInvoke function has unbalanced the stack. This is likely because the managed PInvoke signature does not match the unmanaged target signature. Check that the calling convention and parameters of the PInvoke signature match the target unmanaged signature.
What am I doing wrong?
Second problem is passing a bitmap.
function sygAnaliz(bitmapa: TBitmap): byte; stdcall;
external 'awSygnat1.dll' name 'sygAnaliz';
[DllImport("Delphi/awSygnat1.dll",
CallingConvention = CallingConvention.StdCall,
CharSet = CharSet.Ansi)]
public static extern
byte sygAnaliz(IntPtr bitmapPtr);
// and call itself
sygAnaliz(firstIMG.GetHbitmap());
I get exception:
Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
Both functions are memory safe for sure, since they have been used for few years to great effect. Maybe there is something obvious that I miss?
You are not using a COM safe array, so UnmanagedType.SafeArray will not work.
In order to use UnmanagedType.LPArray, you will have to change your Delphi function to this instead:
procedure sygLadSyg(bytes: PByte); stdcall;
begin
// use bytes as needed, but do not exceed 124 bytes...
end;
And then change your C# declaration to this:
DllImport("Delphi/my.dll",
CallingConvention = CallingConvention.StdCall)]
public static extern
void sygLadSyg([MarshalAs(UnmanagedType.LPArray, SizeConst=124)] byte[] myArray);
As for your second problem, your Delphi function is accepting a VCL TBitmap object as input, but C# has no concept of that. It is passing a Win32 HBITMAP handle instead, so you need to change your Delphi function accordingly. It can internally create a temp TBitmap object and assign the HBITMAP to its Handle property:
function sygAnaliz(bitmapa: HBITMAP): byte; stdcall;
var
Bmp: TBitmap;
begin
try
Bmp := TBitmap.Create;
try
Bmp.Handle := bitmapa;
// use Bmp as needed...
finally
Bmp.Free;
end;
Result := ...;
except
Result := ...;
end;
end;
And then the C# declaration should be:
[DllImport("Delphi/awSygnat1.dll",
CallingConvention = CallingConvention.StdCall)]
public static extern
byte sygAnaliz(IntPtr bitmapPtr);
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
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.
tried a lot of examples and haven't succeeded
I have a DLL writen in Delphi which export a function which have return a Array, and then import into C# application. A have success to work with one variable:
Delphi
function GetArrayData(var testArray : integer): WordBool; stdcall; export;
begin
testArray := 1;
Result := True;
end;
C#
[DllImport("some.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi)]
static extern bool GetArrayData([Out] out IntPtr IntegerArrayReceiver);
private void GetButton_Click(object sender, EventArgs e)
{
unsafe
{
IntPtr IntegerArrayReceiver = IntPtr.Zero;
GetArrayData(out IntegerArrayReceiver);
textBoxData.Text = IntegerArrayReceiver.ToString();
}
Please can some one to transform this code to work with a Array. Mean export a array from Delphi and import to C# array. I have source for both Delphi and C# code.
On the Delphi side you write it like this:
function GetArrayData(arr: PInteger; len: Integer): LongBool; stdcall;
var
i: Integer;
P: PInteger;
begin
P := arr;
for i := 0 to len-1 do
begin
P^ := i;
inc(P);
end;
Result := True;
end;
Here we receive a pointer to the first element, and the length of the array.
And on the C# side you would write:
[DllImport("some.dll")]
static extern bool GetArrayData(int[] arr, int len);
....
int[] arr = new int[42];
if (GetArrayData(arr, arr.Length))
....
The calling C# code allocates the array and passes a pointer to the first element to the Delphi code.
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);