如何获取 System.Windows.Forms.IDataObject 中的 innerData?
要获取 innerData,先调用 GetType() 取得数据对象的实际运行时类型,再用 BindingFlags.Instance 和 BindingFlags.NonPublic 查找字段。找到字段后,调用 GetValue(),并保存返回值。
前提是当前对象的实现中确实有名为 innerData 的字段。System.Windows.Forms.IDataObject 接口没有定义这个成员,因此这种读取方式不一定适用于所有实现或 .NET 版本。
为什么原来的代码有问题
GetField() 应通过 System.Type 调用
在标准 .NET 反射 API 中,GetField() 是 System.Type 的方法。要查找数据对象实例的字段,应这样调用:
comDataObject:GetType():GetField(...)
将 e:Data 转换为 System.Runtime.InteropServices.ComTypes.IDataObject,只会改变访问对象时使用的接口,不会改变对象的实际运行时类型,也不会暴露私有字段。
缺少 BindingFlags.Instance
NonPublic 只指定查找非公开成员,查找实例字段时还必须加上 Instance:
BindingFlags.Instance | BindingFlags.NonPublic
ABL 可以用 OR 组合这两个枚举值,具体规则见 Type.GetField 文档和 ABL 的位运算 OR 文档。
应先检查字段,再读取字段值
原代码先调用 innerDataField:GetValue(...),再检查 innerDataField 是否有效。如果没有找到字段,程序在读取时就会出错,无法执行后面的检查。
GetValue() 返回的是字段中保存的对象,需要将返回值赋给变量,才能继续使用。
ABL 修改示例
将下面的代码放在能访问事件参数 e 的拖放事件处理过程中。示例假定当前实现中的字段名是 innerData:
DEFINE VARIABLE dataObject AS System.Object
NO-UNDO.
DEFINE VARIABLE dataType AS System.Type
NO-UNDO.
DEFINE VARIABLE innerDataField AS System.Reflection.FieldInfo
NO-UNDO.
DEFINE VARIABLE innerData AS System.Object
NO-UNDO.
dataObject = e:Data.
IF VALID-OBJECT(dataObject) THEN DO:
dataType = dataObject:GetType().
innerDataField = dataType:GetField(
"innerData",
System.Reflection.BindingFlags:Instance OR
System.Reflection.BindingFlags:NonPublic
).
IF VALID-OBJECT(innerDataField) THEN DO:
innerData = innerDataField:GetValue(dataObject).
MESSAGE
"innerData is valid:"
VALID-OBJECT(innerData)
VIEW-AS ALERT-BOX INFORMATION BUTTONS OK.
END.
ELSE DO:
MESSAGE
"Field innerData was not found."
"Runtime type:"
dataType:FullName
VIEW-AS ALERT-BOX INFORMATION BUTTONS OK.
END.
END.
这里可以直接使用 e:Data,无需先转换为 COM 接口。传给 GetValue() 的参数必须是拥有该字段的对象实例。
加上 Instance 后仍然找不到怎么办
先查看 dataType:FullName,确认拿到的对象类型,再检查对应程序集中的字段定义。
innerData 属于实现细节。WinForms 的 v3.1.0 源码中有这个字段,当前主分支源码使用的则是 _innerData,字段类型和内部结构也已不同。查找时应以当前实现为准,不能直接沿用旧示例中的字段名。
查找和读取时还需注意:
- 字段名默认区分大小写。
- 如果字段是基类声明的私有字段,需要沿
BaseType查找,并在声明该字段的类型上调用GetField()。 innerDataField有效,只说明找到了字段,字段值是否为空还要单独检查。- 取到的
innerData可能仍是一层包装对象,不能假定它就是底层 COM 对象。
如果后续需要调用 GetDataFromHGLOBLAL,还要核对实际程序集中的方法名称、参数类型和所属类型。找到字段,并不能保证这个私有方法也存在。
从 WhatsApp 获取拖放数据是否必须读取 innerData
仅凭这段代码,无法判断是否必须访问私有字段。可以先调用 e:Data:GetFormats(FALSE) 查看拖放源提供的格式,再根据格式使用 GetDataPresent() 和 GetData() 读取数据。这些方法属于 System.Windows.Forms.IDataObject 的公开接口。
如果需要直接使用 OLE 数据传输接口,可以在对象支持的情况下,将其转换为 System.Runtime.InteropServices.ComTypes.IDataObject,再通过公开方法访问数据。是否还需要读取内部对象,取决于拖放源提供的格式和当前 WinForms 实现。题目中的信息不足以确定,通过反射读取 innerData 就能完成 WhatsApp 文件拖放。
备注:内容仅供参考。
