I'm converting a c# project to delphi XE5 and I'm stuck on a (i believe) simple task.
public void TransferFile(object o, Stream stream)
{
string s = o as string;
if (s != null)
{ do something }
else { do something else }
}
where o contains an handle of an object (in this case its value is 689520929) and the cast to string reports null, which is the expected result.
I translated it in delphi as for the following:
procedure TransferFile(o: OleVariant; stream: TFileStream);
var
s: string;
begin
s:= IntToStr(o);
if (s <> '') then do something
else do something else
end;
In this case casting the variant as string reports the number representation (689520929), which leads the program flow to a different behaviour.
Help appreciated :)
Use VarIsStr() to check if an (Ole)Variant holds a string value or not, eg:
procedure TransferFile(o: OleVariant; stream: TFileStream);
var
s: string;
begin
if VarIsStr(o) then begin
s := VarToStr(o);
// do something...
end else begin
// do something else...
end;
end;
Related
please i need help to port this c# code into delphi ,
I tried but the issue with delphi is that
TArray is a genric type look at my delphi code procedure WriteVector(array: TArray);
internal static void WriteValue(object value, Type valueType)
{
var type = value.GetType();
switch (Type.GetTypeCode(type))
{
// the following are already implemented into delphi
case TypeCode.Int32: Write((int)value); break;
case TypeCode.Int64: Write((long)value); break;
case TypeCode.UInt32:Write((uint)value); break;
case TypeCode.UInt64:Write((ulong)value); break;
case TypeCode.Double:Write((double)value); break;
case TypeCode.Object:
if (type.IsArray)
// the issue is here
WriteVector((Array)value);
break;
default:
break;
}
}
internal static void WriteVector(Array array)
{
if (array == null) { return; }
int count = array.Length;
// save count value
var elementType = array.GetType().GetElementType();
for (int i = 0; i < count; i++)
WriteValue(array.GetValue(i), elementType);
}
// My delphi code
procedure WriteValue(Value: TValue; valueType: PTypeInfo);
begin
case Value.Kind of
tkArray:
WriteVector(Value.AsType<TArray>);
end;
end;
procedure WriteVector(array: TArray);
var
count: Integer;
i: Integer;
begin
if array = nil then
begin
Exit;
end;
// count := Length(array);
// save count value
// how the rest can be correctly written ?
end;
UPD. Sorry, I haven't updated the page before posting. I'll leave it unchaged.
Well... I see some kind of cyclic/recursive stuff, where WriteValue calls WriteVector, WriteVector calls WriteValue and that's all. valueType param is not used, non-array values are ignored. Without additional details it's hard to advise good solution. As an option you may use Variant type.
procedure WriteValue(vValue: Variant);
begin
if ( VarIsArray(vValue) ) then begin
WriteVector(vValue);
end
else if ( VarIsStr(vValue) ) then
begin
end
else if ( VarIsType(vValue, [varShortInt, varDate]) ) then
begin
end
else begin
case VarType(vValue) of
varByte: begin
end;
varDouble: begin
end;
else
// default
end;
end;
end;
procedure WriteVector(vArray: Variant);
var
i: Integer;
begin
if ( not VarIsArray(vArray) ) then Exit;
if ( VarArrayDimCount(vArray) <> 1 ) then Exit;
for i := VarArrayLowBound(vArray, 1) to VarArrayHighBound(vArray, 1) do
WriteValue( VarArrayGet(vArray, [i]) );
end;
procedure Test();
var
v: variant;
begin
v := VarArrayOf( ['0', Now(), Byte(1), Double(2), VarArrayOf([3, 4, 5])] );
WriteValue(v);
WriteVector(Null);
end;
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;
I'm having a pretty hard time finding documentation on Lists and Arrays in Arden MLM.
I am trying to pass a list of 4 digit numbers from Arden using ObjectsPlus to a C# DLL that can take that list as an argument and do what the function is designed for.
Here is what I have in Arden MLM but does not work as I get the .net error "Object reference not set to an instance of an object"
Here is the MLM:
list_id_object := OBJECT [id_list_holder];
id_list := new list_id_object with "1154", "1155", "1158";
try
send_alert_start := new net_object 'Webservices';
result := call send_alert_start.'pageToMultipleIds' with
((sender_name as string) as 'String'),
((sender_message as string) as 'String'),
((list_id_object as list) as 'List<Int32>');
endtry;
catch Exception ex
error_occured := true;
error_message := "Error message here\n" || ex.Message || "\n\n";
endcatch;
And here is the C# method that receives that list:
public string testMethod(string sender_name, string sender_message, List<Int32> IdToPage)
{
try
{
testMethod2(sender_name, sender_message, IdToPage);
return "Success";
}
catch(WebException e)
{
return e.ToString();
}
}
This is written for Allscripts SCM but it should show how to build a List. Unfortunately you have to create a standard Arden list and then loop through it and add each item to the List.
std_include_libs := mlm'std_include_libs';
include std_include_libs;
id_list := 1154, 1155, 1158;
try;
using "System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089";
using namespace "System.Collections.Generic";
idList := new net_object 'List<Int32>';
for i in id_list do
void := call idList.'Add' with i as 'Int32';
enddo;
send_alert_start := new net_object 'Webservices';
result := call send_alert_start.'pageToMultipleIds' with ((sender_name as string) as 'String'),
((sender_message as string) as 'String'),
idList;
endtry;
catch Exception ex;
error_occurred := true;
error_message := "Error message here\n" || ex.Message || "\n\n";
endcatch;
see change below.
((list_id_object as list) as 'List<Int32>');
changed to
((list_id as list) as 'List<Int32>');
.
list_id_object := OBJECT [id_list_holder];
id_list := new list_id_object with "1154", "1155", "1158";
try
send_alert_start := new net_object 'Webservices';
result := call send_alert_start.'pageToMultipleIds' with
((sender_name as string) as 'String'),
((sender_message as string) as 'String'),
((list_id as list) as 'List<Int32>');
endtry;
catch Exception ex
error_occured := true;
error_message := "Error message here\n" || ex.Message || "\n\n";
endcatch;
I have a native Delphi exe which calls into C# dll via COM interop. Here's the simplest case which demonstrate this issue:
Delphi:
IClass1 = interface(IDispatch)
['{B29BAF13-E9E4-33D7-9C92-FE28416C662D}']
function Test(const aStr: WideString): WideString; safecall;
end;
var
obj: IClass1;
s: string;
begin
obj := CoClass1.Create as IClass1;
s := obj.Test(''); // Returns '[null]'
end;
C#:
[ComVisible(true)]
public interface IClass1
{
string Test(string aStr);
}
[ComVisible(true)]
public class Class1 : IClass1
{
public string Test(string aStr)
{
if (aStr == null) return "[null]";
if (aStr == "") return "[empty]";
return "Not empty: " + aStr;
}
}
When I call method Test with an empty string in Delphi, the C# part receives null as a parameter value. Why is that? Shouldn't it be an empty string also?
In Delphi, AnsiString, UnicodeString, and WideString values are represented by a nil pointer when they are empty. COM uses BSTR for strings. Delphi wraps BSTR with WideString. So there is no way to pass an "empty" non-nil string to a COM method that takes a WideString as a parameter, it will be nil instead.
In Delphi, a null (i.e., nil) and empty string are treated as equivalent. As such, passing '' for a string (or WideString) parameter passes nil internally -
program Project1;
{$APPTYPE CONSOLE}
{$R *.res}
procedure Foo(const S: WideString);
begin
WriteLn(Pointer(S) = nil);
end;
begin
Foo('Something'); //FALSE
Foo(''); //TRUE
ReadLn;
end.
The equation of null and empty strings was in fact copied from COM... so a COM library isn't really the place to insist on a C#-style distinction between the two.
Why does passing '' to a WideString parameter result in the other side receiving null? Well, that's just how Delphi represents an empty COM BSTR. If you really need to pass an empty string, you need to change IClass1 in the Delphi code to pass TBStr instead of WideString and use SysAllocString or SysAllocStringLen to create an empty TBStr.
Change the declaration of the function in the Delphi code to:
function Test(const aStr: TBStr): WideString; safecall;
And pass SysAllocStringLen('', 0) when you need to pass an empty string.
Here is a complete demonstration:
C#
using System;
using System.Runtime.InteropServices;
namespace ConsoleApplication1
{
[ComVisible(true)]
public interface IClass1
{
string Test(string aStr);
}
[ComVisible(true)]
public class Class1 : IClass1
{
public string Test(string aStr)
{
if (aStr == null) return "[null]";
if (aStr == "") return "[empty]";
return "Not empty: " + aStr;
}
}
class Program
{
[DllImport(#"Project1.dll")]
static extern void Foo(IClass1 intf);
static void Main(string[] args)
{
IClass1 intf = new Class1();
Foo(intf);
}
}
}
Delphi
uses
Ole2;
type
IClass1 = interface(System.IDispatch)
function Test(const aStr: TBStr): WideString; safecall;
end;
var
EmptyBStr: TBStr;
procedure Foo(const intf: IClass1); stdcall;
begin
Writeln(intf.Test(nil));
Writeln(intf.Test(EmptyBStr));
Writeln(intf.Test(SysAllocString('foo')));
end;
exports
Foo;
begin
EmptyBStr := SysAllocStringLen('', 0);
end.
Output
[null]
[empty]
Not empty: foo
To avoid errors by null pointers you can send an empty character with Chr (#0) or AnsiChar(#0) instead of '' that returns a null.
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 ...