I call a dll "plcommpro.dll" for a specific operations on Access Controls
the following C# code is working perfect and retrieve data correctly in buffer
[DllImport("plcommpro.dll", EntryPoint = "GetDeviceData")]
public static extern int GetDeviceData(IntPtr h, ref byte buffer,
int buffersize, string tablename, string filename, string filter, string
options);
Now, I need to to write same operation from Delphi, So I tried the following:
TGetDeviceData = Function(iDevID : NativeInt; buffer : Pbyte ; iSize :
Integer;
tablename, filename, strFilter, strOptions : PAnsiChar) : Int64;stdcall;
and I call the function as follows:
var
myBuffer : TBytes;
iRetLog : Integer;
bufferSize : Integer;
sConnect : TConnect;
GetDeviceData : TGetDeviceData;
dllHandle : THandle;
iDevID : Integer;
begin
dllHandle := LoadLibrary('plcommpro.dll') ;
if dllHandle <> 0 then
begin
#sConnect := GetProcAddress(dllHandle, 'Connect');
if #sConnect <> Nil then
begin
strParams := PChar('protocol=TCP,ipaddress=' + grd_Machines.Cells[cl_Machine_IP, iLoop] + ',port=4370,timeout=2000,passwd=');
iDevID := sConnect(strParams);
strTableName := PAnsiChar(AnsiString(('user')));
strDatas := PAnsiChar(AnsiString(''));
strFileName := PAnsiChar(AnsiString(''));
strFilter := PAnsiChar(AnsiString(''));
strOptions := PAnsiChar(AnsiString(''));
#GetDeviceData := GetProcAddress(dllHandle, 'GetDeviceData');
if #GetDeviceData <> Nil then
begin
try
buffersize := 1024*1024;
//bufferSize := MaxInt - 1;
SetLength(myBuffer, 1024*1024);
mem_AttLogs.Lines.Add('buffer Size : ' + IntToStr(buffersize) );
iRetLogs := GetDeviceData(iDevID, PByte(myBuffer[0]), buffersize, strTableName, strFileName, strFilter, strOptions);
if iRetLogs > 0 then
begin
....
//Here: I need to read the returned values from the function; but it always fails
end
The code is modified to explain my case more clearly. Can you help?
The C# declaration you give is obviously flawed: ref byte buffer makes no sense. A buffer is not one byte. It should probably be something like [out] byte[] buffer (thanks to David Heffernan). Both translate an underlying pointer, but the conversion done on the C# side is different.
Since this seems to be interop code to interface with a plain Windows DLL, I can see what the original must have been: a pointer to bytes, which is best translated as PByte in Delphi (but no var, that would introduce one level of indirection too many).
So now it should be something like:
var
GetDeviceData: function(h: THandle; buffer: PByte; buffersize: Integer;
tablename, filename, filter, options: PAnsiChar): Integer stdcall;
Now you finally updated your code, the error you get is quite obvious:
iRetLog := GetDeviceData(iDevID, PByte(myBuffer[0]), buffersize,
strTableName, strFileName, strFilter, strOptions);
That is wrong. You are casting an AnsiChar (i.e. myBuffer[0]) to a pointer. You need a pointer to the first element of myBuffer, so do:
iRetLog := GetDeviceData(iDevID, #myBuffer[0], buffersize,
strTableName, strFileName, strFilter, strOptions); // Note the #
FWIW, since you seem to be using constant strings, you can just do:
iRetLog := GetDeviceData(iDevID, #myBuffer[0], buffersize, 'user', '', '', '');
And to make this easier to maintain, don't use literal numbers if you already have a variable that has this number (and must be the exact same size anyway), so do this instead:
buffersize := 1024*1024;
SetLength(myBuffer, buffersize);
Related
I am converting some C# code to Delphi.
In c#, I have Nullabe types as:
System.Nullable<T> variable
or
T? variable
Then, I can use something as:
int?[] arr = new int?[10];
Is there some Delphi VCL equivalent to it?
I found this interesting article about Nullable types and this simplified implementation:
unit Nullable;
{$mode delphi}{$H+}
interface
uses
Classes, SysUtils;
type
TNullable<T> = record
private
FHasValue: Boolean;
FValue: T;
function GetValue: T;
procedure SetValue(AValue: T);
public
procedure Clear;
property HasValue: Boolean read FHasValue;
property Value: T read GetValue write SetValue;
class operator Implicit(A: T): TNullable<T>;
class operator Implicit(A: Pointer): TNullable<T>;
end;
implementation
{ TNullable }
function TNullable<T>.GetValue: T;
begin
if FHasValue then
Result := FValue
else
raise Exception.Create('Variable has no value');
end;
procedure TNullable<T>.SetValue(AValue: T);
begin
FValue := AValue;
FHasValue := True;
end;
procedure TNullable<T>.Clear;
begin
FHasValue := False;
end;
class operator TNullable<T>.Implicit(A: T): TNullable<T>;
begin
Result.Value := A;
end;
class operator TNullable<T>.Implicit(A: Pointer): TNullable<T>;
begin
if A = nil then Result.Clear
else raise Exception.Create('Pointer value not allowed');
end;
end.
I am using RGiesecke's "Unmanaged Exports" package to create a dll from C# that can be called from a Delphi application.
Specifically, I am looking to pass an array of arrays of a struct.
What I have made work in C# is
public struct MyVector
{
public float X;
public float Y;
}
[DllExport]
public static void DoStuff([MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)]
MyVector[] vectors, int count)
{
// Do stuff
}
Which can then be called from Delphi, doing something like this:
unit MyUnit
interface
type
TVector = array[X..Y] of single;
TVectorCollection = array of TVector;
procedure TDoExternalStuff(const vectors : TVectorCollection; count : integer; stdcall;
procedure DoSomeWork;
implementation
procedure DoSomeWork;
var
vectors : array of TVector;
fDoExternalStuff : TDoExternalStuff;
Handle: THandle;
begin
// omitted: create and fill vectors
Handle := LoadLibrary('MyExport.dll');
#fDoExternalStuff := GetProcAddress(Handle, 'DoStuff');
fDoExternalStuff(vectors, Length(vectors));
end;
end.
However, what I really need to do is to pass an array of array of TVector. An array of structs that hold an array of TVector would also do. But writing
[DllExport]
public static void DoStuff([MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)]
MyVector[][] vectors, int count)
{
// Do stuff
}
Does not work with Delphi
...
TVectorCollection = array of array of TVector;
...
procedure DoSomeWork;
var
vectors : array of array of TVector;
fDoExternalStuff : TDoExternalStuff;
Handle: THandle;
begin
// omitted: create and fill vectors
Handle := LoadLibrary('MyExport.dll');
#fDoExternalStuff := GetProcAddress(Handle, 'DoStuff');
fDoExternalStuff(vectors, Length(vectors)); //external error
end;
And I would also be a bit surprised if it did, since I am not specifying the length of the individual elements of the jagged array anywhere.
Is there a way for me to setup my DllExport function to be able to marshal this type of element?
Is there a way for me to setup my DllExport function to be able to marshal this type of element?
No, p/invoke marshalling never descends into sub-arrays. You will have to marshal this manually.
Personally I'd pass an array of pointers to the first elements of the sub-arrays, and an array of the lengths of the sub-arrays.
On the C# side it will look like this:
[DllImport("kernel32.dll", EntryPoint = "CopyMemory", SetLastError = false)]
public static extern void CopyMemory(IntPtr dest, IntPtr src, uint count);
[DllExport]
public static void DoStuff(
[In]
int arrayCount,
[In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)]
IntPtr[] arrays,
[In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)]
int[] subArrayCount,
)
{
MyVector[][] input = new MyVector[arrayCount];
for (int i = 0; i < arrayCount; i++)
{
input[i] = new MyVector[subArrayCount[i]];
GCHandle gch = GCHandle.Alloc(input[i], GCHandleType.Pinned);
try
{
CopyMemory(
gch.AddrOfPinnedObject(),
arrays[i],
(uint)(subArrayCount[i]*Marshal.SizeOf(typeof(MyVector))
);
}
finally
{
gch.Free();
}
}
}
It's a little messy since we can't use Marshal.Copy because it doesn't know about your struct. And there is []no simple built in way to copy from IntPtr to IntPtr](https://github.com/dotnet/corefx/issues/493). Hence the p/invoke of CopyMemory. Anyway, there's many ways to skin this one, this is just my choice. Do note that I am relying on your type being blittable. If you changed the type so that it was not blittable then you'd need to use Marshal.PtrToStructure.
On the Delphi side you can cheat a little and take advantage of the fact that a dynamic array of dynamic arrays is actually a pointer to an array of pointers to the sub-arrays. It will look like this:
type
TVectorDoubleArray = array of array of TVector;
TIntegerArray = array of Integer;
procedure DoStuff(
arrays: TVectorDoubleArray;
arrayCount: Integer;
subArrayCount: TIntegerArray
); stdcall; external dllname;
....
procedure CallDoStuff(const arrays: TVectorDoubleArray);
var
i: Integer;
subArrayCount: TIntegerArray;
begin
SetLength(subArrayCount, Length(arrays));
for i := 0 to high(subArrayCount) do
subArrayCount[i] := Length(arrays[i]);
DoStuff(Length(Arrays), arrays, subArrayCount);
end;
I am using Robert Giesecke Unmanaged Exports 1.2.6 in VS2010 and my goal is to pass an array of structs from c# (.NET 3.5) to delphi (D7).
I have to admit, that I'm not that familiar with delphi.
I've already read this post, but the suggested answer didn't work for me:
When calling func in delphi the CPU-debugging-window opens and if I continue the app exits without exception and without the desired result.
Here is the code I tried:
C# platform x86
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using RGiesecke.DllExport;
namespace ArrayTest
{
public class Class1
{
public struct Sample
{
[MarshalAs(UnmanagedType.BStr)]
public string Name;
}
[DllExport]
public static int func(
[Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)]
Sample[] samples,
ref int len
)
{
// len holds the length of the array on input
// len is assigned the number of items that have been assigned values
// use the return value to indicate success or failure
for (int i = 0; i < len; i++)
samples[i].Name = "foo: " + i.ToString();
return 0;
}
}
}
Delphi7
program DelphiApp;
{$APPTYPE CONSOLE}
uses
SysUtils,
ActiveX;
type
TSample = record
Name: WideString;
end;
PSample = ^TSample;
function func(samples: PSample; var len: Integer): Integer; stdcall;
external 'ArrayTest.dll';
procedure Test2;
var
samples: array of TSample;
i, len: Integer;
begin
len := 10;
SetLength(samples, len);
if func(PSample(samples), len)=0 then
for i := 0 to len-1 do
Writeln(samples[i].Name);
end;
begin
Test2();
end.
As mentioned earlier, the debugger opens the CPU-Window and if I continue the app exits without exception or error message.
If I run it without dbugger, Windows tells me the app isn't working any more and the app closes.
What am I missing?
Update
Modified code:
[DllExport]
public static int func(
[Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)]
Sample[] samples,
ref int len
)
{
Console.WriteLine("return 0");
return 0;
}
procedure Test2;
var
samples: array of TSample;
i, len: Integer;
begin
len := 10;
SetLength(samples, len);
if func(PSample(samples), len)=0 then
for i := 0 to len-1 do
Writeln('D7: ', i);
end;
Even if I don't access the array on either side, the behaviour still is the same.
Console-output: return 0
Seems like I found the issue:
The code runs fine if .NET 4.0 or higher is used. If you use .NET 3.5 or lower the len-parameter has to be passed by value.
See MSDN-documentation SizeParamIndex v3.5:
The parameter containing the size must be an integer that is passed by value.
See MSDN-documentation SizeParamIndex v4.0:
When arrays are passed as C-style arrays, the marshaler cannot
determine the size of the array. Therefore, to pass an managed array
to an unmanaged function or method, you must provide two arguments:
The array, defined by reference or value.
The array size, defined by reference or value.
Code working with .NET 3.5:
C#
[DllExport]
public static int func(
[Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)]
Sample[] samples,
int len,
ref int outLen
)
{
// len holds the length of the array on input
// outLen is assigned the number of items that have been assigned values
// use the return value to indicates success and the required array size (>=0) or failure (<0)
int requiredSize = 20;
if (requiredSize < len)
{
len = requiredSize;
}
for (outLen = 0; outLen < len; outLen++)
{
samples[outLen].Name = "foo: " + outLen.ToString();
}
return requiredSize;
}
Delphi7
function func(samples: PSample; len: Integer; var outLen: Integer): Integer; stdcall;
external 'ArrayTest.dll';
procedure Test2;
var
samples: array of TSample;
i, len: Integer;
begin
len := 0;
// query the required array size
i := func(PSample(samples), len, len);
if i>0 then
begin
len := i;
SetLength(samples, len);
if func(PSample(samples), len, len)>=0 then
for i := 0 to len-1 do
Writeln(samples[i].Name);
end;
end;
Conclusion:
The code posted in my question and posted by David Heffernan here only works with .NET >= 4.0!
If you have to use .NET <= 3.5 you must pass the arraysize by value and not by reference!
Revised question:
We're using D7 to call a C# method that's in a COM object. The C# method returns a WideString.
Is there a risk that .NET will garbage collect the returned WideString from under our feet while we're using it in our Pascal code? If so, what alternatives do we have to safely return a string from a C# COM object?
Also, is our final Delphi line SubMan := nil the proper way to release the COM object?
C# Code
[DispId(5)]
bool Test(int var1, ref int var2, ref string var3);
public bool Test(int var1, ref int var2, ref string var3)
{
bool result;
if (var1 == 0)
{
var2 = 0;
var3 = "zero";
result = true;
}
else
{
var2 = -1;
var3 = "minus one";
result = false;
}
return result;
}
Pascal code from generated TLB in D7
function Test( var1: Integer;
var var2: Integer;
var var3: WideString): WordBool; dispid 5;
Pascal code calling the COM object. Is there a risk var3 will be GC-ed?
procedure TForm1.Button1Click(Sender: TObject);
var
SubMan: TSubMan;
Var1: Integer;
Var2: Integer;
Var3: WideString;
FunctionResult: Boolean;
begin
SubMan := COTSubMan.Create;
Var1 := 1;
FunctionResult := SubMan.Test(Var1, Var2, Var3);
// Will Var3 above be persistent in the code
// or might it be garbage collected by .NET before we can use it below or later?
ShowMessage( BoolToStr(FunctionResult, TRUE) +
' Var2 = ' + IntToStr(Var2) +
' Var3 = ' + Var3);
SubMan := nil; // Is this the right way to release the COM object?
end;
The code in your question is fine. The COM marshaller handles the lifetimes of the two ref parameters for you. There's no danger from the GC because the semantics of the parameters is clearly expressed. The objects that you work with on the Delphi side are not .net objects and so not subject to GC.
Indeed, the GC is not a factor because this is COM. The fact that there is a .net object on the other side implemented the COM server is neither here nor there. It's the responsibility of the .net COM interop layer to present a valid COM object to you.
Just to confirm, even though the string was originally allocated in the C# COM object, var3 is safe to use throughout our Delphi Button1Click method?
Yes. Remember that this is COM, a standard for binary interop. You don't need to be too concerned by the details. FWIW, a WideString is the Delphi wrapper around the COM BSTR type. That string type is a UTF-16 encoded string, reference counted, and allocated off the shared COM heap. That last point is important. The fact that BSTR instances come of the shared COM heap is what allows them to be allocated in one module, and deallocated in another.
Is SubMan := nil the proper way to release the COM object?
Yes it is. Although it is a little pointless in this code because the local variable is about to leave scope and that will have the exact same effect as your nil assignment.
Using BSTR type is the easiest way since COM will handle memory (de)allocation.
eg:
Delphi:
function ReturnString(out TheString: WideString): Boolean; stdcall;
begin
try
TheString := 'Hello World';
finally
Result := True;
end;
end;
C#:
[DllImport("mydll.dll", CallingConvention=CallingConvention.StdCall)]
public static extern bool ReturnString([MarshalAs(UnmanagedType.BStr)]out string TheString);
I am unable to translate the interface definition below from Delphi to C#:
IDCDSPFilterInterface = interface(IUnknown)
['{BD78EF46-1809-11D6-A458-EDAE78F1DF12}']
// removed functions thjat are already working
function get_FilterName(Index : integer; out Name : PChar): HRESULT; stdcall;
end;
I have tried with StringBuilder in the following way:
[ComVisible(true), ComImport, SuppressUnmanagedCodeSecurity,
Guid("BD78EF46-1809-11D6-A458-EDAE78F1DF12"),
InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IDCDSPFilterInterface : IBaseFilter
{
[PreserveSig]
int get_FilterName(int Index, [MarshalAs(UnmanagedType.LPStr)] StringBuilder Name);
}
I tried with LPStr, LPWStr, which both gives garbage characters in the string builder, and LPTStr which fails with an error message saying that this kind of marshalling combination is not allowed.
The definition of the method in Delhi is:
function TDCDSPFilter.get_FilterName(Index : integer; out Name : PChar): HRESULT; stdcall;
begin
{$IFDEF EXCEPT_DEBUG}try{$ENDIF}
FcsFilter.Lock;
try
{$IFDEF WITH_INTERNAL_DSP}
Result := S_FALSE;
if (Index < 0) or (Index > fFilters.Count -1) then Exit;
Name := PChar(fFilters.Name[Index]);
Result := S_OK;
{$ELSE}
Result := E_NOTIMPL;
{$ENDIF}
finally
FcsFilter.UnLock;
end;
{$IFDEF EXCEPT_DEBUG} except er('TDCDSPFilter.get_FilterName'); end; {$ENDIF}
end;
The fFilters.Name is declared as:
property Name[Index: integer]: String read GetName;
All my other interface methods work well with other basic types (in and ref) except this one with PChar output.
I get S_OK but the string in the StringBuilder is garbage...
I know the method is properly called because if I pass the wrong indexes I get S_FALSE (as the method body is defined to do).
Can anybody help to give the proper Marshalling for the Delphi out PChar?
That is a rather poorly designed interface. This is a COM interface and so it should use the COM string, BSTR.
However, as it stands, the C# side has to marshal the PChar as an out parameter of type IntPtr. The declaration should be:
[PreserveSig]
uint get_FilterName(int Index, out IntPtr Name);
Then the string can be recovered by calling Marshal.PtrToStringAnsi or Marshal.PtrToStringUni depending on the encoding of the string.
You cannot use StringBuilder because that is for the situation where the caller allocates the buffer. And that's not happening here.
As I said, using a COM string would be better. The code looks like this:
Delphi
function TDCDSPFilter.get_FilterName(Index: integer;
out Name : WideString): HRESULT; stdcall;
C#
[PreserveSig]
uint get_FilterName(
int Index,
[MarshalAs(UnmanagedType.BStr)]
out string Name
);
Of course, that assumes that you can modify the interface. Quite likely you cannot. In which case you are stuck with out IntPtr.
Try to use IntPtr for Name parameter then get the content of the string with Marshal.PtrToStringAnsi.
References :
Marshal "char *" in C#
char* to a string in C#