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

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

Related

Delphi Dll to 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.

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

how to call a C function from C# with a WCHAR* out parameter?

I'm having a bit of problem with marshaling and I just can't figure it out by myself. I've searched for this topic, but hadn't have any luck yet, so basically I'm trying to call an unmanaged C function from my managed C# application. The signature of the C function looks like this:
long MyFunction(WCHAR* upn, long upnSize, WCHAR* guid, long* guidSize);
I don't access to the .dll file, I just know that the function is being exposed for usage and I know what the function is supposed to do, but I don't know what happening inside, so the function receives a WCHAR* upn holding a UserPricipalName and a long with the length of the supplied UPN. Also a WCHAR pointer is passed along, where the function writes back a corresponding GUID which is associated with the passed UPN. The guidSize pointer supplies the size of the pointer, if it's too small the written GUID is not fully written. If everything goes fine the function should return 0 (it never happened yet, when called from c#)
Now my efforts to invoke and call this function look like this:
[DllImport(#"MyDll.dll", EntryPoint = "MyFunction", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
public static extern long MyFunction(IntPtr upnPtr, long upnSize, [Out, MarshalAsAttribute(UnmanagedType.LPWStr) ] StringBuilder guidPtr, IntPtr guidSizePtr);
//my attempt to call the Dll's exposed function
string upn = foo#bar.com;
long upnSize = upn.Length;
IntPtr upnPtr = Marshal.StringToHGlobalUni(upn);
IntPtr guidSizePtr = Marshal.AllocHGlobal(sizeof(long));
Marshal.WriteInt32(GuidSizePtr, 128);
var guidSB = new StringBuilder(128);
result = MyFunction(upnPtr, upnSize, guidSB, guidSizePtr);
as a result I receive an AccessViolationException. I've played around with many variations to call the function, but I never managed to receive a 0 as return value and I was never able to read out the GUID as I'm supposed to do.
Any help with this would be appreciated.
Declare the function as:
[DllImport(#"MyDll.dll", EntryPoint = "MyFunction", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
public static extern int MyFunction([MarshalAsAttribute(UnmanagedType.LPWStr)] string upnPtr, int upnSize, [MarshalAsAttribute(UnmanagedType.LPWStr)] StringBuilder guidPtr, ref int guidSizePtr);
Call it as follows:
string upn = "foo#bar.com";
var guidSB = new StringBuilder(128);
int guidSizePtr =guidSB.Capacity;
MyFunction(upn, upn.Length, guidSB, ref guidSizePtr);
Note that long in C++ is 32-bit, so you should define all such instances as int in your C# code.

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