问题描述
我有一些.NET互操作代码,可以在其中加载对象并读取属性,但是在设置对象的属性时遇到了麻烦。以下是Delphi代码的相关部分:
使用
mscorlib_TLB,Winapi.ActiveX;
type
//代码的无关部分
TDotNetObject = class(TObject)
private
FTarget:OleVariant;
FType:_Type;
public
过程SetProperty(const APropertyName:string; const AValue:OleVariant; const AIndex:Integer = -1);
结尾;
函数VariantToPSafeArray(const AValue:Variant):PSafeArray;
开始
结果:= PSafeArray(VarArrayAsPSafeArray(AValue));
结尾;
过程TDotNetObject.SetProperty(const APropertyName:string; const AValue:OleVariant; const AIndex:Integer = -1);
var
LPropertyInfo:_PropertyInfo;
LIndex:PSafeArray;
如果AIndex> = 0则以
开始,然后
LIndex:= VariantToPSafeArray(VarArrayOf([AIndex]))
否则
LIndex:= nil;
LPropertyInfo:= FType.GetProperty(APropertyName,BindingFlags_Instance或BindingFlags_Public或BindingFlags_NonPublic);如果LPropertyInfo<>为
nil然后
LPropertyInfo.SetValue(FTarget,AValue,LIndex);
结尾;
过程UpdateDefectStatus(const ADefectID,AStatus:Integer);
var
LObject:TDotNetObject;
begin
// **获取省略的对象的代码***
LObject.SetProperty('Status',AStatus);
结尾;
mscorlib_TLB单元来自JEDI项目中的JCL,在这里:
LPropertyInfo.SetValue引发错误在TDotNetObject.SetProperty中被调用:无法转换为类型'System.Nullable`1 [MTData.Transport.Tracking.DefectReporting.DefectStatus]。
C#对象的DefectStatus属性声明为:
公共缺陷状态?状态
(即可以为空)
C#中的Status属性类型声明为:
公共枚举DefectStatus
{
///<摘要>
///已报告缺陷。
///< / summary>
已报告,
///< summary>
///评估缺陷。
///< / summary>
已评估,
///< summary>
///工作单上有缺陷。
///< / summary>
OnWorkOrder,
///< summary>
///缺陷已关闭。
///< / summary>
已关闭
}
我找到了使用以下方法处理这种情况的解决方案C#在这里:
但是我对如何在Delphi中执行相同操作有些迷惑。有想法吗?
编辑
鉴于奥利维尔的答案,我试图写一些Delphi代码可以完成等效操作,如下所示:
procedure InvokeToObject;
var
LType,LDefectStatusType:_Type;
LInvokeFlags:TOleEnum;
LArgs:PSafeArray;
LValue:整数;
L结果:OleVariant;
LRes:HRESULT;
LResHex:字符串;
开始
LType:= MTDataClr.GetCoreType('System.Enum');
LDefectStatusType:= MTDataClr.GetType(’MTData.Transport.Tracking.DefectReporting.DefectStatus’);
LInvokeFlags:= BindingFlags_InvokeMethod或BindingFlags_Static;
LValue:= 1;
LArgs:= VariantToPSafeArray(VarArrayOf([[LDefectStatusType,LValue])));
LRes:= LType.InvokeMember_2( ToObject,LInvokeFlags,nil,Null,LAgs,nil,LResult);
LResHex:= IntToHex(LRes);
结尾;
这段代码的目标只是调用Enum类型的ToObject方法。成功获取LType和LDefectStatusType,但是对InvokeMember_2的调用未成功,返回码为:0x80131512,这显然是Missing Member异常。对我做错的任何想法吗?
问题是枚举不是int,这意味着 SetValue()
需要执行两次转换( Int32
到 DefectStatus
和 DefectStatus
到 DefectStatus?
),它不能(只能执行一个)。
下面是一个C#代码,可再现您要执行的操作:
使用系统;
使用System.Reflection;
公共枚举DefectStatus
{
已报告,
已评估,
OnWorkOrder,
已关闭
}
公共类Defect
{
public DefectStatus?状态{get; set;}
}
公共类测试
{
public static void Main()
{
Defect def = new Defect() ;
PropertyInfo pi = typeof(Defect).GetProperty( Status);
//抛出ArgumentException
// pi.SetValue(def,1,null);
//通过其数字值检索被评估的枚举
对象评估= Enum.ToObject(typeof(DefectStatus),1);
//可以正常使用
pi.SetValue(def,评估为null);
Console.WriteLine(def.Status);
}
}
所以您需要在Delphi中检索枚举。为此,您将需要使用API来访问 Enum
类型并在其上调用 ToObject
。
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?
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.
这篇关于设置“可为空”是指将其设置为null。 .NET对象的属性的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!