Reputation: 3602
I have some .NET interop code where I've managed to load objects and read properties, however I am having trouble with setting a property on an object. Here's the relevant parts of the Delphi code:
uses
mscorlib_TLB, Winapi.ActiveX;
type
// Irrelevant parts of the code omitted
TDotNetObject = class(TObject)
private
FTarget: OleVariant;
FType: _Type;
public
procedure SetProperty(const APropertyName: string; const AValue: OleVariant; const AIndex: Integer = -1);
end;
function VariantToPSafeArray(const AValue: Variant): PSafeArray;
begin
Result := PSafeArray(VarArrayAsPSafeArray(AValue));
end;
procedure TDotNetObject.SetProperty(const APropertyName: string; const AValue: OleVariant; const AIndex: Integer = -1);
var
LPropertyInfo: _PropertyInfo;
LIndex: PSafeArray;
begin
if AIndex >= 0 then
LIndex := VariantToPSafeArray(VarArrayOf([AIndex]))
else
LIndex := nil;
LPropertyInfo := FType.GetProperty(APropertyName, BindingFlags_Instance or BindingFlags_Public or BindingFlags_NonPublic);
if LPropertyInfo <> nil then
LPropertyInfo.SetValue(FTarget, AValue, LIndex);
end;
procedure UpdateDefectStatus(const ADefectID, AStatus: Integer);
var
LObject: TDotNetObject;
begin
// ** Code to obtain the object omitted ***
LObject.SetProperty('Status', AStatus);
end;
The mscorlib_TLB unit comes from JCL in Project JEDI, here:
https://github.com/project-jedi/jcl/blob/master/jcl/source/windows/mscorlib_TLB.pas
An error is thrown when LPropertyInfo.SetValue is called in TDotNetObject.SetProperty:
Project TestProject.exe raised exception class EOleException with message 'Object of type 'System.Int32' cannot be converted to type 'System.Nullable`1[MTData.Transport.Tracking.DefectReporting.DefectStatus]''.
The DefectStatus property on the C# object is declared as:
public DefectStatus? Status
(i.e. it's nullable)
The Status property type in C# is declared:
public enum DefectStatus
{
/// <summary>
/// Defect Reported.
/// </summary>
Reported,
/// <summary>
/// Defect assessed.
/// </summary>
Assessed,
/// <summary>
/// Defect on work order.
/// </summary>
OnWorkOrder,
/// <summary>
/// Defect closed.
/// </summary>
Closed
}
I found a solution for how to handle this situation using C# here:
https://stackoverflow.com/a/13270302/3164070
However I'm a bit lost as to how to do the same in Delphi. Any ideas?
EDIT
Given Olivier's answer, I have attempted to write some Delphi code to do the equivalent, which is as follows:
procedure InvokeToObject;
var
LType, LDefectStatusType: _Type;
LInvokeFlags: TOleEnum;
LArgs: PSafeArray;
LValue: Integer;
LResult: OleVariant;
LRes: HRESULT;
LResHex: string;
begin
LType := MTDataClr.GetCoreType('System.Enum');
LDefectStatusType := MTDataClr.GetType('MTData.Transport.Tracking.DefectReporting.DefectStatus');
LInvokeFlags := BindingFlags_InvokeMethod or BindingFlags_Static;
LValue := 1;
LArgs := VariantToPSafeArray(VarArrayOf([LDefectStatusType, LValue]));
LRes := LType.InvokeMember_2('ToObject', LInvokeFlags, nil, Null, LArgs, nil, LResult);
LResHex := IntToHex(LRes);
end;
The goal with this piece of code is just to invoke the ToObject method of the Enum type. Obtaining LType and LDefectStatusType succeeds, however the call to InvokeMember_2 does not, with a return code of: 0x80131512, which apparently is a Missing Member exception. Any ideas on what I'm doing wrong?
Upvotes: 4
Views: 394
Reputation: 18087
The issue is that an enum is not an int, which means SetValue()
would need to perform a double conversion (Int32
to DefectStatus
and DefectStatus
to DefectStatus?
), which it cannot (it can only perform one).
Here's a C# code that reproduces what you're trying to do:
using System;
using System.Reflection;
public enum DefectStatus
{
Reported,
Assessed,
OnWorkOrder,
Closed
}
public class Defect
{
public DefectStatus? Status {get; set;}
}
public class Test
{
public static void Main()
{
Defect def = new Defect();
PropertyInfo pi = typeof(Defect).GetProperty("Status");
// This throws an ArgumentException
// pi.SetValue(def, 1, null);
// Retrieve the Assessed enum via its numeric value
object assessed = Enum.ToObject(typeof(DefectStatus), 1);
// This works as expected
pi.SetValue(def, assessed, null);
Console.WriteLine(def.Status);
}
}
So you need to retrieve the enum in Delphi. For that you will need to play with the API to access the Enum
type and call ToObject
on it.
Upvotes: 1