C# COM => pointer => delphi interface - c#

Couple of days I cant make solution to solve my problem. I have interface and class in C# which visible as COM server. And I have Delphi application where I need to access this interface. But in my case I need in C# side to return 32 bit pointer to interface and at Delphi side - to convert pointer to interface. What is done?
c# side. All attributes written well, source code is cutted because original class is much bigger, but idea is understandable:
[ComVisible(true)]
[CLSCompliant(true)]
[Guid("C1A57BD0-AAA2-4AB8-BA2F-ADFA04275AD5")]
[ClassInterface(ClassInterfaceType.None),
ComSourceInterfaces(typeof(IProcPreviewEndArgs))]
public class ProcPreviewEndArgs : IProcPreviewEndArgs
{
[ComVisible(true), PreserveSig]
public IntPtr CreateNew()
{
var obj = new ProcPreviewEndArgs();
GC.SuppressFinalize(obj);
return Marshal.GetComInterfaceForObject(obj, typeof (IProcPreviewEndArgs));
}
}
delphi side:
type
PIProcPreviewEndArgs = ^IProcPreviewEndArgs;
Index, Res: Integer;
ppa : IProcPreviewEndArgs;
pppa : PIProcPreviewEndArgs;
ppa := CoProcPreviewEndArgs.Create; // Creates COM object via Delphi services
ppa.CreateNew(Res); // Creates object of the same type, returns as Ret (Integer)
pppa := PIProcPreviewEndArgs(Res); // Trying to cast Intereger to interface
pppa^.CreateNew(Res); // just to test calling posibility
On pppa^ (getting instance by pointer) ACCESS VIOLATION exception is thrown. pointer Res is not null. It is about 60 000 000. Basicly it is kernel space area.
UPD
[CLSCompliant(true), ComVisible(true),
InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
Guid("10572D64-B612-468F-86B3-D12F0B6E3CD2")]
public interface IProcPreviewEndArgs
{
IntPtr CreateNew();
IntPtr Get(int aMin, int aMax, uint cMin, uint cMax);
void ToAngle(int x, int y, int w, int h, out int nx, out int ny, out int nw, out int nh);
IntPtr GetImage();
}
Delphi (generated by Delphi and working well when using standard Delphi methods to manipulate COM objects):
IProcPreviewEndArgs = interface(IUnknown)
['{10572D64-B612-468F-86B3-D12F0B6E3CD2}']
function CreateNew(out pRetVal: Integer): HResult; stdcall;
function Get(aMin: Integer; aMax: Integer; cMin: LongWord; cMax: LongWord; out pRetVal: Integer): HResult; stdcall;
function ToAngle(x: Integer; y: Integer; w: Integer; h: Integer; out nx: Integer;
out ny: Integer; out nw: Integer; out nh: Integer): HResult; stdcall;
function GetImage(out pRetVal: Integer): HResult; stdcall;
end;
Delphi version: Delphi XE2

I believe that CreateNew returns IProcPreviewEndArgs rather than ^IProcPreviewEndArgs. So your code should read:
var
ppa1, ppa2: IProcPreviewEndArgs;
....
ppa1 := CoProcPreviewEndArgs.Create;
ppa1.CreateNew(Res);
ppa2 := IProcPreviewEndArgs(Res);
ppa2.CreateNew(Res);
If I were you I'd declare those Integer return values that map to C# IntPtr as Pointer in Delphi. That way your code will work if ever you compile a 64 bit version.
I'd also contemplate using safecall rather than stdcall and so get the compiler to convert any errors from HRESULT to Delphi exception.
IProcPreviewEndArgs = interface(IUnknown)
['{10572D64-B612-468F-86B3-D12F0B6E3CD2}']
function CreateNew: Pointer; safecall;
function Get(aMin: Integer; aMax: Integer; cMin: LongWord; cMax: LongWord): Pointer; safecall
procedure ToAngle(x: Integer; y: Integer; w: Integer; h: Integer; out nx: Integer;
out ny: Integer; out nw: Integer; out nh: Integer); safecall;
function GetImage: Pointer; safecall;
end;
I also wonder why you have to return untyped pointers in, for example, CreateNew. Why can't you declare that function to return IProcPreviewEndArgs on both sides?

Delphi fully supports COM/ActiveX, regardless of where the pointer is coming from.
Q: What version of Delphi are you using?
SUGGESTION:
1) Create a C# test client to test your ProcPreviewEndArgs.CreateNew() method. See if it works.
2) Review this article:
http://delphi.about.com/library/weekly/aa121404a.htm
Change your Delphi code to use COM/ActiveX classes and GUIDS (as opposed to Delphi pointers). Make it work the same as the C# test client you got working in Step 1. Make sure you're doing all the "basics", including calling CoInitialize() somewhere in your Delphi .exe before you start using COM/ActiveX.
3) Please consider if you need COM/ActiveX at all. Perhaps your design can use PInvoke without COM/ActiveX?

Related

Calling function in C# DLL from Delphi has parameter stuck on single value

I have a C# DLL with several exported functions. On one of these functions, when calling it from our Delphi XE2 application the length parameter for the array is always read as 31 in the DLL, regardless of what I actually pass.
The C# function declaration
[DllExport(CallingConvention = CallingConvention.StdCall)]
public static bool FraDataRead(int dbHash, uint pointid, [In, Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3)] Double[] FraData, int length)
Delphi function declaration
TGetFraData = function(dbHash : Integer;
pointid : Uint32;
unmanagedArray : Array of Double;
arraySize : Integer) : Bool; stdcall;
Getting the procedure
GetFraData : TGetFraData; //this is a class variable, here for simplicity
#GetFraData := GetProcAddress(FDllHandle, 'FraDataRead');
Calling function
function TIviumSQLAccess.IviFraData(const PointId : Uint32;
const FraData : Array of Double;
const arraySize : Integer): Boolean;
begin
Result := GetFraData(dbHash,
PointId,
FraData,
arraySize);
end;
I have tested calling the function from another C# application, and there the length parameter functions as expected. When calling from Delphi it always calls with 32 as the length (currently) but the DLL always process 31. I have tried both larger and smaller numbers when calling, but it always uses 31.
TGetFraData = function(dbHash : Integer;
pointid : Uint32;
unmanagedArray : Array of Double;
arraySize : Integer) : Bool; stdcall;
This code is wrong because it uses a Delphi open array. These are actually implemented as two arguments, the pointer to the array, and the index of the last element, which is why you see a 31 for your array of length 32.
You need instead to declare the type like this
TGetFraData = function(dbHash : Integer;
pointid : Uint32;
unmanagedArray : PDouble;
arraySize : Integer) : Bool; stdcall;
and then pass a pointer to the first element of the array.

C# Dll library does not return output parameters value to a Delphi application

I've written a Dll with C# with an exported function that save a file.
Here's the C# code
using RGiesecke.DllExport;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
using System.IO;
namespace ClassLibrary1
{
public class Class1
{
[DllExport("Funcion", CallingConvention = CallingConvention.StdCall)]
public static void Funcion(IntPtr pDataIn, Int32 pSize, [Out, MarshalAs(UnmanagedType.I4)] int pArchivo)
{
byte[] documento = new byte[pSize];
Marshal.Copy(pDataIn, documento, 0, pSize);
File.WriteAllBytes("Document2.pdf", documento);
pArchivo = 25;
}
}
}
In Delphi, I load the library and call the exported function and it works fine.
Here's the Delphi Code
procedure TForm1.Button1Click(Sender: TObject);
var
lStream : TMemoryStream;
lArBytes : array of Byte;
lInDocSize : Integer;
lHndle : THandle;
Funcion : procedure(pDataIn : array of Byte;
pInSize : Integer;
var pDocumento : Integer
); stdcall;
begin
try
lHndle := LoadLibrary('ClassLibrary1.dll');
if (lHndle <> 0) then
begin
Funcion := GetProcAddress(lHndle, 'Funcion');
if Assigned(Funcion) then
begin
try
lStream := TMemoryStream.Create;
lStream.LoadFromFile('Document1.PDF');
lStream.Position := 0;
SetLength(lArBytes, lStream.Size);
lStream.Read(lArBytes[0], lStream.Size);
lInDocSize := 0;
Funcion(lArBytes, lStream.Size, lInDocSize);
Label1.Caption := IntToStr(lInDocSize);
except on E : Exception do
begin
RaiseLastOSError;
ShowMessage(e.Message);
end;
end;
end;
end;
finally
end;
end;
I have an error with the output parameter, it is that always returns cero (0) value, not matter what value I assign to parameter, it always has cero value.
I've changing the parameter like this
out int pArchivo
and
ref int pArchivo
But when the function finish I get a memory exception.
With Marshal, function finish fine, without memory errors, but the output paramerter value is always cero (0).
[Out, MarshalAs(UnmanagedType.I4)] int pArchivo
I've read about this problem in this post in Stackoverflow
Passing array of struct from c# to Delphi
But in my case, it doesn't work
What I'm doing wrong?
I hope that you could help... thank you so much
On the Delphi side, a function parameter directly declared as array of ... is known as an "open array". An "open array" gets passed by the compiler using 2 parameters - a pointer to the first array element, and the high index (not the length!) of the array. This allows the calling code to pass either a static array or a dynamic array to the same parameter, and the compiler will pass the array data accordingly.
But, your .NET code is expecting only 1 parameter for the array - a raw pointer to the 1st array element. That is why you are not getting your output value correctly. Your lStream.Size parameter value gets passed where the .NET code expects the 3rd parameter to be, so the Size value gets misinterpreted as the memory address where the .NET code writes its output pArchivo value to, hence the memory error. Which won't matter anyway since you are corrupting the call stack! You end up pushing 4 parameter values onto the stack, and then stdcall on the .NET side pops off only 3 parameter values during stack cleanup when the function exits.
You need to change the declaration of your Funcion variable, either by:
keeping the pDataIn parameter declared as array of Byte, but removing the explicit pInSize parameter. Let the compiler pass it implicitly:
Funcion : procedure(pDataIn : array of Byte;
//pInSize : Integer;
var pDocumento : Integer
); stdcall;
You will then have to change the call of Funcion() to allocate +1 more byte for the lArBytes array so the compiler passes the correct size value to the pSize parameter:
SetLength(lArBytes, lStream.Size+1); // <-- +1 here!
lStream.Read(PByte(lArBytes)^, High(lArBytes)); // <-- no +1 here!
...
Funcion(lArBytes{, High(lArBytes)}, lInDocSize);
Needless to say, this is not intuitive, though it should work since the behavior of open arrays is well-known, though it is a private implementation detail of the Delphi compiler.
using PByte (or just Pointer) instead of array of Byte:
Funcion : procedure(pDataIn : PByte; // <-- here
pInSize : Integer;
var pDocumento : Integer
); stdcall;
You will then have to change the call of Funcion() to pass a pointer to the 1st array element, and pass the array length explicitly:
SetLength(lArBytes, lStream.Size); // <-- no +1 here!
lStream.Read(PByte(lArBytes)^, Length(lArBytes)); // <-- or here!
...
Funcion(#lArBytes[0]{or: PByte(lArBytes)}, Length(lArBytes), lInDocSize);
This is more intuitive, and closer to what the .NET code is expecting.
Alternatively, I suggest you simply get rid of your lArBytes variable altogether. You don't actually need it. Since the .NET code is expecting a raw pointer to the byte data, simply pass your TMemoryStream data directly:
procedure TForm1.Button1Click(Sender: TObject);
var
lStream : TMemoryStream;
lInDocSize : Integer;
lHndle : THandle;
Funcion : procedure(pDataIn : Pointer;
pInSize : Integer;
var pDocumento : Integer
); stdcall;
begin
lHndle := LoadLibrary('ClassLibrary1.dll');
if (lHndle <> 0) then
try
Funcion := GetProcAddress(lHndle, 'Funcion');
if Assigned(Funcion) then
begin
lInDocSize := 0;
lStream := TMemoryStream.Create;
try
lStream.LoadFromFile('Document1.PDF');
Funcion(lStream.Memory, lStream.Size, lInDocSize);
finally
lStream.Free;
end;
Label1.Caption := IntToStr(lInDocSize);
end;
finally
FreeLibrary(lHndle);
end;
end;

Passing C-style arrays of structs/records from delphi 5 to C# via COM

I have a struct defined in a type library imported into both a Delphi 5 and a C# assembly.
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct MyStruct
{
public uint a;
public float b;
}
MyStruct = packed record
a: LongWord;
b: Single;
end;
On the Delphi side I have a pointer to a C-style array of said structs which I would like to pass via COM to a C# assembly. Ideally, I'd like this to end up on the c# side as a myStruct[], but I'll take a pointer to a properly marshalled block of memory, the since the structs are all blittable.
Two ways I've tried are
void DoFoo([MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] MyStruct[] fooArray, int size);
void DoBar(MyStruct[] barArray, int length);
which after being converted to a type library and imported into delphi are
procedure DoFoo(var fooArray: MyStruct; size: Integer); safecall;
procedure DoBar(barArray: PSafeArray; length: Integer); safecall;
I didn't think safe arrays worked with structs, and the other clearly isn't an array.
Any ideas/links/whatever greatly appreciated.
I did, in fact, pass the structs from delphi as a safearray, which came through to c# as a byte[].
// C# interop method.
void DoFoo(byte[] myStructs);
// Delphi SafeArray creation
size := fooNumber* sizeof(Foo);
arrayBounds.lLbound := 0;
arrayBounds.cElements := size;
safeArray := SafeArrayCreate( varByte, 1, arrayBounds );
SafeArrayLock(safeArray);
Move(pointerToFooArray^, safeArray.pvData^, size);
SafeArrayUnlock(safeArray);
auroraDiagnostics.DoFoo(safeArray);
Then I used this solution to convert it back to the original structs.
It's a bit clunky, but it works.

How do I use C# to call a function that receives a Delphi open-array parameter?

How do I convert the Delphi code into C#? It takes an array of Byte, but I'm not sure what the C# equivalent is. My attempt doesn't work and throws exceptions like AccessViolationException.
Delphi:
function SetLevel(a: array of byte): boolean; stdcall; external 'DMX510.dll';
C#:
[DllImport("DMX510.DLL")]
public static extern Boolean SetLevel(Byte[] bytearray);
Byte[] byteArray = new Byte[5];
byteArray[1] = 75;
SetLevel(byteArray);
A Delphi open array is not a valid interop type. You can't easily match that up with a C# byte[] through a P/invoke. In an ideal world a different interface would be exposed by the native DLL but as you have stated in comments, you do not have control over that interface.
However, you can trick the C# code into passing something that the Delphi DLL will interpret correctly, but it's a little dirty. The key is that a Delphi open array declared like that has an extra implicit parameter containing the index of the last element in the array.
[DllImport(#"DMX510.DLL")]
public static extern bool SetLevel(byte[] byteArray, int high);
byte[] byteArray = new byte[] { 0, 75, 0, 0, 0};
SetLevel(byteArray, byteArray.Length-1);
To be clear, in spite of the parameter lists looking so different, the C# code above will successfully call the Delphi DLL function declared so:
function SetLevel(a: array of byte): boolean; stdcall;
I have no idea whether or not passing an array of length 5 is appropriate, or whether you really meant to just set the second item to a non-zero value.
This is the way how I implemented successfully sending arrays from and to Delphi & C#.
C#:
[DllImport("Vendors/DelphiCommunication.dll", CallingConvention = CallingConvention.StdCall)]
public static extern void LoadFromFileCHR(
string sFileName,
ref int iSize,
ref double AreaCoef,
ref double FWaveLength,
ref bool FHasWaveLength,
double[] ChromX,
double[] ChromY
);
Note that single types have REF and arrays DO NOT HAVE REF, but arrays will still work like REF anyway
Delphi:
Type
ArrayDouble100k = array [0..99999] of Double;
procedure LoadFromFileCHR(
FileName : String;
var Size : Integer;
var AreaCoef : Double;
var FWaveLength: Double;
var FHasWaveLength : Boolean;
var ChromX : ArrayDouble100k;
var ChromY : ArrayDouble100k); StdCall;
begin
//...
end;
exports LoadFromFileCHR;
Note that VAR is also with Array parameters (Delphi analog of REF).
I had all sorts of errors, because I had ref with arrays in C# code
Another problem that caused memory corruption for me was that I did not notice that these codes are not the same in Delphi and C#:
Delphi:
for i := 0 to Length(fileCHR.ChromX) do //This is wrong
C#
for(int i = 0; i < fileCHR.ChromX.Length; i++)
The same in delphi would be
for i := 0 to Length(fileCHR.ChromX) - 1 do //This is right
If you overflow boundaries of arrays passed to delphi it could also cause all sorts of errors

Delphi dll function to C#

With a compiled Delphi dll, one of the functions declared is
Mydll.dll
type
TInfo = array [0..255] of byte;
type
public
function GetInfo(Memadr, Infolen: Integer): TInfo;
what is the DLLImport format to use this in C#?
I'd do it like this:
Delphi
type
TInfo = array [0..255] of byte;
procedure GetInfo(Memadr, Infolen: Integer; var Result: TInfo); stdcall;
C#
[DllImport(#"testlib.dll")]
static extern void GetInfo(int Memadr, int Infolen, byte[] result);
static void Main(string[] args)
{
byte[] result = new byte[256];
GetInfo(0, result.Length, result);
foreach (byte b in result)
Console.WriteLine(b);
}
You need to get the calling conventions to match. I've gone for stdcall which is the default for P/invoke (that's why it's not specified in the P/invoke signature).
I'd avoid returning the array as a function return value. It's easier to marshall it this way as a parameter.
In fact in general, if you want to get away from fixed size buffers you could do it like this:
Delphi
procedure GetInfo(Memadr, Infolen: Integer; Buffer: PByte); stdcall;
Then, to fill out the buffer, you'd need to use some pointer arithmetic or something equivalent.
Need to correct an error in my original post,
type
TInfo = array [0..255] of byte;
implementation
function GetInfo(Memadr, Infolen: Integer): TInfo;
procedure TForm1.Button5Click(Sender: TObject);
var Data: TInfo;
i: integer;
s: string;
begin
for i:=0 to 255 do Data[i]:=0;
Data:=GetInfo($04,12);
if (Data[1]=0) then
begin StatusBar1.SimpleText:='No Data'; exit; end;
s:='';
for i:=1 to 8 do
s:=s+Chr(Data[i+1]);
Edit3.Text:=s;
end;

Categories