C# pointer to struct array - c#

Hi I would like to create a pointer to struct array
I have a 2 structs like this
public struct TRecord
{
string Key;
string Value;
}
public struct TDB
{
public int Count;
TRecord[] Records;
}
but because I am writing a plugin for Delphi app which passes DBPointer pointer (DBPointer has an array of data) I need to point DBPointer to TDB so I can get the data out of it. When trying to do this like
public unsafe TDB* DBPointer; //DBPointer points to TDB struct which has struct array
I get
Error CS0208 Cannot take the address of, get the size of, or declare a pointer to a managed type
and as I read I can't declare a pointer to a string and a struct
So I don't know what to do now because if writing this in pascal it works fine
type TRecord = record
Key: shortstring;
Value: shortstring;
end;
type TPRecord = record
Key: PChar;
Value: PChar;
end;
type TDB = record
Count: integer;
Records: array [0..255] of TRecord;
end;
PTDB = ^TDB;
function ReadValue(Key: PChar; DBPointer: PTDB): PChar; stdcall;
var a,i: integer;
SKey: shortstring;
begin
Result:='';
SKey:=shortstring(Key);
a:=DBPointer^.Count;
if (a>length(DBPointer^.Records)) then a:=length(DBPointer^.Records); //ochrana
for i:=1 to a do
begin
if (DBPointer^.Records[i-1].Key=SKey) then
begin Result:=StrPCopy(Param1,DBPointer^.Records[i-1].Value); exit; end;
end;
end;
Thanks for Anwsering and Best Regards

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;

C# COM => pointer => delphi interface

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?

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;

delphi return array of integer from a COM object function

A typelibrary is created from C # and used that to in Delphi 5.
there is a method in type library that is returning a array or string. In Delphi same array of string I have to get but when I compiled the code following error is comming.
Incompatible types: 'tagSAFEARRAY' and 'Array'
Here is C # code
public int[] sqrRootUpto(int num)
{
int[] result={0};
int tempVal=0;
for (int i = 2; num < tempVal; i++)
{
tempVal = i * i;
result[i] = tempVal;
}
return result;
}
Here is Delphi code Where I am calling those above function.
procedure TForm1.BtnSqrtClick(Sender: TObject);
var
num :Integer;
result : array of Integer;
begin
num := StrToInt(EditSqrtInput.text);
result := newObj.sqrRootUpto(num); //Here I am calling the above method
end;
end.
EDIT
var
result : variant;
begin
result := VarArrayCreate([0, 20], varInteger);
Error!! Incompatible types: 'tagSAFEARRAY' and 'Array'
TBL.pas having this signature for the function
function TMathClass.sqrRootUpto(rNum: Integer): PSafeArray;
begin
Result := DefaultInterface.sqrRootUpto(rNum);
end;
what is diff b/w TSafeArray and TSafeArray
There is already TSafeArray record defined in ActiveX.pas. Maybe this can help you.
try declaring
var
result: TIntegerDynArray;
Edit: After your edit, it's clear that your COM method returns a PSafeArray. An easiest way to use an array of Integer would be to convert it to a variant array first:
function IntSafeArrayToVarArray(const P: PSafeArray): OleVariant;
begin
if Assigned(P) then
begin
VarClear(Result);
tagVariant(Result).vt := varInteger or varArray;
tagVariant(Result).PArray := P;
end
else
Result := Null;
end;
You can then use this function because variant array of integers is assignment-compatible with a TIntegerDynArray:
result := IntSafeArrayToVarArray(newObj.sqrRootUpto(num));
for I := Low(result) to High(Result) do ...

Categories