Delphi Dll to C# - c#

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.

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.

Calling Delphi DLLs from C# and passing bitmap and byte array

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

How do I call this Delphi method in C# using Dllimport?

New Programmer in need of Help!
The Delphi code that is compiled into the DLL
function SetCurrentSerial(Size : Integer; Msg : Pointer) : Integer stdcall;
var
TempByte : PByte;
TempStr : string;
i: Integer;
begin
Result := 0;
TempByte := Msg;
TempStr := '';
for i := 0 to Size - 1 do
begin
TempStr := TempStr + ' ';
end;
for i := 0 to Size - 1 do
begin
TempStr[i+1] := Chr(TempByte^);
Inc(TempByte);
end;
if not DLLClass.SelectDeviceSerial(TempStr) then
begin
Result := -1;
end;
end;
The C# Code
//Import a Function Pointer
[DllImport("Test.dll", CallingConvention= CallingConvention.StdCall, CharSet = CharSet.Ansi)]
public unsafe static extern int SetCurrentSerial(int Size, byte[] Msg);
I need to store the pointer value, Size and Msg in a buffer and print the value in a console window.
I will greatly appreciate a fully constructed code. Thank you in advance.
Here is the code I've so far tried.
//C# Code
class Program
{
[DllImport("Test.dll")]
public unsafe static extern int SetCurrentSerial(int Size, void* Msg);
unsafe static void Main()
{
int Res;
byte[] buffer = new byte[1024];
Res = SetCurrentSerial(255, &buffer);
Console.WriteLine("%s\n", buffer);
}
}
Your DLL function is designed incorrectly. You are passing a string from the calling code to the DLL. That is really simple to do and you can remove almost all of your code. The Delphi code should be like this:
function SetCurrentSerial(Serial: PAnsiChar): LongBool; stdcall;
begin
Result := DLLClass.SelectDeviceSerial(Serial);
end;
Then the C# code should be:
[DllImport("Test.dll", CallingConvention = CallingConvention.StdCall,
CharSet = CharSet.Ansi)]
public static extern bool SetCurrentSerial(string Serial);
Call the function like this:
bool succeeded = SetCurrentSerial(Serial);
if (!succeeded)
{
// handle error
}
I've replaced your integer return value with a boolean indicating success or failure. Should you prefer to revert to an integer that would look like this:
Delphi
function SetCurrentSerial(Serial: PAnsiChar): Integer; stdcall;
begin
if DLLClass.SelectDeviceSerial(Serial) then begin
Result := 0;
end else begin
Result := -1;
end;
end;
C#
[DllImport("Test.dll", CallingConvention = CallingConvention.StdCall,
CharSet = CharSet.Ansi)]
public static extern int SetCurrentSerial(string Serial);
Update
Apparently you cannot change this DLL. That is a shame because it is really very badly designed and implemented. However, to call that function from C# you need to declare the p/invoke like this:
[DllImport("Test.dll", CallingConvention = CallingConvention.StdCall)]
public static extern int SetCurrentSerial(int Size, byte[] Msg);
And then call it like this:
byte[] Msg = Encoding.Default.GetBytes(serial);
int retval = SetCurrentSerial(Msg.Length, Msg);
if (retval != 0)
{
// handle error
}

Error in trying to call a DELPHI XE2 DLL from VS2013 C# program

I am trying to use Pinvoke from a C# VS2013 program to access an existing DelphiXE2 dll program. The Delphi program takes an xml file and an IB database file and updates the database according to the xmlfile. The DelphiXE2 dll program is successfully being called from another Delphi program. In C# am trying to invoke the GETxml program. I am getting an {"External component has thrown an exception."} error.
I need a syntax check on what I am trying to do as I am new to C# and DELPHI.
C#
[DllImport("MSA.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, EntryPoint = "GetXML")]
extern static int GetXML([MarshalAs(UnmanagedType.LPStr)] string a, [MarshalAs(UnmanagedType.LPStr)] string b, Boolean c);
int retval = 0;
retval = GetXML(txtPath.Text.ToString().Trim(), txtCabFile.Text.ToString().Trim(), false);
Existing Delphi program: (I noticed that the database open is not occurring in this dll function. In fact it is occurring in the calling Delphi program before the call to GetXML. Not sure if this is a problem or not since I am calling from C# program.)
function GetXML(DatabasePath: pChar; OFileName: pChar; Silent: Boolean = True): Integer; stdcall;
var
xmlDatabase: TIBDatabase;
xmlTransaction: TIBTransaction;
objXML: TXML;
begin
Result := -1;
if FileExists(oFilename) then
begin
objXML := nil;
xmlDatabase := nil;
try
xmlDatabase := TIBDatabase.Create(nil);
xmlTransaction := TIBTransaction.Create(xmlDatabase);
xmlTransaction.DefaultDatabase := xmlDatabase;
xmlDatabase.DatabaseName := DatabasePath;
xmlDatabase.Params.Add('user_name=SYSTASS');
xmlDatabase.Params.Add('password=pswdf88');
xmlDatabase.LoginPrompt := False;
xmlDatabase.DefaultTransaction := xmlTransaction;
xmlDatabase.Connected := True;
xmlTransaction.StartTransaction;
try
objXML := TXML.Create(XMLDatabase, IsSecGDB(XMLDatabase)); //uses IBQUERY to start transaction
objXML.XMLIntoDB(oFilename, Silent);
xmlTransaction.Commit;
result := 1;
except
on e: Exception do
begin
xmlTransaction.Rollback;
raise Exception.Create('GetXML Exception: ' + e.Message);
end;
end;
finally
FreeAndNil(objXML);
if xmlDatabase <> nil then
xmlDatabase.Close;
FreeAndNil(xmlDatabase);
end;
end
else
begin
raise Exception.Create('Filename ' + oFilename + ' parameter was not found.');
end;
end;
First of all, let's look at the mis-match across the two sides of the interop boundary. You are passing ANSI strings from C#, but the Delphi code expects UTF-16.
[DllImport("MSA.dll", CallingConvention = CallingConvention.StdCall,
CharSet = CharSet.Unicode, EntryPoint = "GetXML")]
extern static int GetXML(
[MarshalAs(UnmanagedType.LPStr)]
string a,
[MarshalAs(UnmanagedType.LPStr)]
string b,
Boolean c
);
Although you specified CharSet.Unicode you then went on and told the marshaller explicitly to marshal as PAnsiChar by using UnmanagedType.LPStr. For UTF-16 you would use UnmanagedType.LPWStr
However, your p/invoke is needlessly complex. I would write your p/invoke like this:
[DllImport("MSA.dll", CharSet = CharSet.Unicode)]
extern static int GetXML(string a, string b, bool c);
Note that CallingConvention.StdCall is the default. There's no need to specify EntryPoint here since it just repeats the function name. And you do not need the MarshalAs attributes since you specified CharSet.Unicode.
Now, a far bigger problem that you face is that you throw a native Delphi exception across the interop boundary. That's why the p/invoke layer objected and said:
External component has thrown an exception
Throwing exceptions across this interop boundary is an absolute no-no. Stop doing that. You'll have to handle errors the old-school way with error codes.

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