System.__ComObject 的动态转换

5

是否有可能将System.__ComObject转换为仅在运行时才知道的某种类型? 我有以下代码

Type ComClassType = SomeDLLAssembly.GetType("ClassName");
dynamic comClassInstance = icf2.CreateInstanceLic(null, null, ComClassType.GUID, "License string");
//This will throw exception, because comClassInstance type is __ComObject and it does not contains ComClassMethod
comClassInstance.ComClassMethod();

使用下面的代码可以正常工作,但不幸的是我不能在我的代码中使用InvokeMember,因为这将会非常复杂。
ComClassType.InvokeMember("ComClassMethod", BindingFlags.InvokeMethod, null, comClassInstance, null);

我想问一下,是否可以将“comClassInstance”转换为“ComClassType”,以便通过这种方式调用方法 comClassInstance.ComClassMethod();

4个回答

6
那至少是问题的一部分。当你在编译时不知道类型时,只能在运行时使用强制类型转换。但问题更深层次,在运行时你也不知道类型。__ComObject 是 RCW 的底层包装器,它存储 IDispatch 接口指针,却没有说明可以调用哪些方法或使用哪些属性。
这是更低级别的类型,即动态类型。通常称之为“迟绑定”。这是一种试图调用并查看发生了什么的方式来与另一位程序员编写的代码进行交互。C# 团队很长时间以来都抵制它,静态类型是语言的核心,强制程序员使用反射。但现在不一样了,由于广大需求,他们在 v4 中添加了 “dynamic” 关键字,使 C# 与始终支持它的 Visual Basic 相同。现在可以使用符号传递到反射方法中,DLR 会自动使用反射进行调用,而不是传递字符串。除此之外,与以前完全相同,你必须知道字符串。
迟绑定的主要优点是它对版本控制具有弹性,这是第三方 API 偏爱它的基本原因。但也是其主要缺点,如果你没有好的手册,或者版本变化太大了,那么你将得到一个令人不爽的运行时异常,而且它没有提供任何更多的信息,只是告诉你“它没用了”。编译器不能帮助你弄对它,因为他不知道类型。IntelliSense 也无法帮助你提供自动完成。只有在运行时出现故障并且唯一找出问题原因的方法是浏览(缺少的)手册或与程序员交谈。
许多 COM 组件都支持早期和迟绑定。使用早期绑定需要一个类型库,它是组件支持的接口和 coclass 的机器可读描述。它与 .NET 程序集中的元数据完全相等,并执行相同的角色,有了类型库,编译器现在可以检查你的代码,并且 IntelliSense 可以提供自动完成。
类型库通常被嵌入到可执行文件 (.dll 或 .exe) 中,有时它作为一个单独的文件传递,.tlb 和 .olb 是常见的扩展名。你可以使用 Visual Studio 的“文件”>“打开”>“文件”来查看可执行文件的内部,如果类型库被嵌入,则会看到 TYPELIB 节点。然后可以使用 OleView.exe,“文件”>“查看 Typelib”命令来查看其内容。并运行 Tlbimp.exe 来生成互操作库。
如果找不到类型库,而且没有好的最新编程手册,则只有电话可以帮助您。联系组件所有者或作者寻求帮助。

不错的小说。你听说过QueryInterface吗?你听说过关键字“is”和“as”如何使用它们吗?这里的解决方案应该是在反射中调用“is”。我正在寻找那个,而不是小说。 - Kobor42
5
你非常粗鲁,很难看出你的观点。试着提出建设性的评论以达成共识。 - Hans Passant
2
@Kobor42,您可以使用反射来获取is,但是无法使用反射进行强制转换或使用as。http://stackoverflow.com/q/21673363/57428。您可以尝试使用GetIUnknownForObject()和GetObjectForIUnknown()来链接,然后将结果用作`dynamic`。 - sharptooth
@Kobor42 嗯,我还没有尝试过,所以我不知道答案实际上是什么。在我看来,如果你写下自己的答案并解释一下实际上起作用的原因会更好。 - sharptooth
同时,我们完全跳过了这个问题。不过我已经想出了必要时如何解决:选择一个已知的组件对象,为其创建一个实例并将其转换为对象。然后使用 "as" 将其转换回已知的组件类型。编译并在 ildasm 中打开程序集。反向工程完成。 - Kobor42
显示剩余2条评论

3

调用COM对象的方法不需要进行转换。

在C#中,可以使用dynamic关键字(在调用之前不需要编写转换表达式)。

dynamic comClassInstance = Activator.CreateInstance(Type.GetTypeFromProgID("ClassName"));
comClassInstance.ComClassMethod();
var result = comClassInstance.ComClassFMethod(param);

或者

在vb.net中创建一个库项目(选项严格应该为关闭,项目属性)。在vb.net中完成所有与COM相关的操作,并将其添加到您的C#项目中引用。

将实例类型声明为Object

对COM组件进行CreateObject调用,并将方法调用为comClassInstance.ComClassMethod()

Public Class Abc
    Private _comClassInstance As Object

    Public Sub New()
        _comClassInstance = CreateObject("ClassName")
    End Sub

    Public Sub ComClassMethod()
        _comClassInstance.ComClassMethod()
    End Sub

    Public Function ComClassFMethod(param As String) As Integer
        Return _comClassInstance.ComClassFMethod(param)
    End Function 
End Class

0
如果你的“ClassName”是COM可见的,你应该能够使用基本类型转换来转换返回的COM对象。
Type ComClassType = SomeDLLAssembly.GetType("ClassName");
ClassName myInstance = (ClassName)icf2.CreateInstanceLic(null, null, ComClassType.GUID, "License string");
myInstance.ComClassMethod();

但是我在编译时不知道 ClassName,只有在运行时才知道,所以我不能这样强制转换。SomeDLLAssembly 是一个动态加载的 DLL。 - Michal Kalous
1
哦,那我建议使用InvokeMember。我不确定它是否有效,但你也可以尝试“dynamic comClassInstance = Convert.ChangeType(comClassInstance, ComClassType);”。 - Simo Erkinheimo
很不幸,在我的情况下使用InvokeMember方法非常复杂,但看起来这是唯一的解决方案。我已经尝试使用Convert.ChangeType(comClassInstance, ComClassType); 但是我得到了一个异常,即对象__COMObject必须实现IConvertible接口。 无论如何,非常感谢您的努力。 - Michal Kalous
1
我进行了一些快速测试,如果你返回的对象类型是com-visible的话,你的第一种方法实际上应该完美地工作。 - Simo Erkinheimo
我也是这么想的,但出现了异常,提示该对象中不存在该方法,尽管对于同一对象的Invoke方法却可以正常工作。我真的很困惑。 - Michal Kalous

0

我不确定这是否符合您的要求,如果是的话,您需要根据您创建实例的方式进行适应,因为我不太清楚icf2来自哪里。然而,如果您只是想清理大部分代码与COM对象交互的方式,那么一种方法是扩展dynamic解析过程。您可以通过继承DynamicObject并重写TryInvokeMember方法来实现这一点(还有其他方法可以重写,但TryInvokeMember似乎是关键的一个)。当动态调度程序无法定位方法时,将在对象上调用此方法,以便您可以进行一些自定义解析。

因此,在基本级别上(就像我跳过错误处理等所有伟大的示例一样),您可以编写如下类:

public class ComWrapper : DynamicObject {
    Object _instance;
    Type _type;

    // Create + save type/instance information in constructor
    // You seem to do this differently, so you'd want to change this...
    public ComWrapper(Guid guid) {
        _type = Type.GetTypeFromCLSID(guid, true);
        _instance =  Activator.CreateInstance(_type, true);            
    }

    // Invoke requested method, passing in args and assigning return value
    // to result
    public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, 
                                         out object result) {
        result = _type.InvokeMember(binder.Name, 
                                    System.Reflection.BindingFlags.InvokeMethod, null,
                          _instance, args);

        return true;  // Return true to indicate it's been handled
    }
}

然后,基于创建一个Word实例并告诉它退出的示例, 您可以像这样使用上面的代码:

dynamic val = new ComWrapper(new Guid("{000209FF-0000-0000-C000-000000000046}"));

val.Quit(0, 0, false );

我认为动态类在幕后可能已经做了与上述类似的事情,因此这可能实际上不能解决您的问题。但是,它可能会让您更接近找到解决方案。例如,在不支持调度的 COM 对象上调用方法将通过 InvokeMethod 报告错误,但通过动态实例调用时将报告 不包含定义


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接