如何在C#中枚举COM对象的成员?
我通过COM连接到某个程序并接收System .__ ComObject。我知道它的几种方法,这样我就可以这样做:如何在C#中枚举COM对象的成员?
object result = obj.GetType().InvokeMember("SomeMethod", BindingFlags.InvokeMethod, null, obj, new object[] { "Some string" });
像这样
dynamic dyn = obj;
dyn.SomeMethod("Some string");
这两种方法都工作正常。但是,如何确定COM对象的内部类型信息并通过其所有成员枚举?
我尝试这样做:
[ComImport, Guid("00020400-0000-0000-C000-000000000046"),
InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IDispatch
{
void Reserved();
[PreserveSig]
int GetTypeInfo(uint nInfo, int lcid, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(TypeToTypeInfoMarshaler))] out System.Type typeInfo);
}
...
IDispatch disp = (IDispatch)obj;
Type t;
disp.GetTypeInfo(0, 0, out t);
但t是空结尾。谁能帮我?
您无法获得COM对象的类型。这将需要您为COM组件创建一个互操作库。如果COM服务器有一个类型库,只需添加一个引用或运行Tlbimp.exe实用程序,这当然是低痛点。如果它存在,那么类型库通常嵌入在DLL内部。当你这样做的时候,编辑器和对象浏览器都可以更好地了解COM类上可用的方法和属性。
查看IDispatch强制转换工作使得类型库很可能也可用。对于COM服务器作者来说,创建一个非常简单。您可以用来查看类型库的另一个工具是OleView.exe,View + Typelib。
如果这不起作用,那么你确实可以从IDispatch中挖出东西。您的声明看起来很腥,IDispatch :: GetTypeInfo的第三个参数是ITypeInfo,一个COM接口。无需定制编组器,ITypeInfo在System.Runtime.InteropServices.ComTypes命名空间中可用。您可以使用Reflector从框架代码中挖出IDispatch声明。
当然,没有任何替代体面的文件。当您获得使用此组件的许可时,您应该可以获得一些。
您可以使用: http://www.nektra.com/products/deviare-api-hook-windows/
它作为可以用来获得已注册的COM对象的信息及功能,拦截他们的COM对象提供了一个完整的API。
我刚刚发表了一篇关于如何做的CodeProject文章。本文提供了一个小巧的C#DispatchUtility
辅助类,它很容易包含在其他项目中。在内部,它使用IDispatch和.NET的TypeToTypeInfoMarshaler的自定义声明将IDispatch的ITypeInfo转换为丰富的.NET Type实例。
在你的例子中,你可以调用DispatchUtility.GetType(obj, true)
来取回一个.NET Type实例,然后你可以调用GetMembers。
FWIW,DispatchUtility
对IDispatch.GetTypeInfo的声明与您的声明几乎完全相同。但是,在调用GetTypeInfo时,它会传递LOCALE_SYSTEM_DEFAULT(2048)而不是0作为lcid参数。也许GetTypeInfo为您的disp.GetTypeInfo(0, 0, out t)
调用返回了失败HRESULT。既然你用[PreserveSig]
来声明它,你需要检查它的结果(例如,通过调用Marshal.ThrowExceptionForHR
)。
这里的DispatchUtility
类评论最多的去除的一个版本:
using System;
using System.Runtime.InteropServices;
using System.Reflection;
public static class DispatchUtility
{
private const int S_OK = 0; //From WinError.h
private const int LOCALE_SYSTEM_DEFAULT = 2 << 10; //From WinNT.h == 2048 == 0x800
public static bool ImplementsIDispatch(object obj)
{
bool result = obj is IDispatchInfo;
return result;
}
public static Type GetType(object obj, bool throwIfNotFound)
{
RequireReference(obj, "obj");
Type result = GetType((IDispatchInfo)obj, throwIfNotFound);
return result;
}
public static bool TryGetDispId(object obj, string name, out int dispId)
{
RequireReference(obj, "obj");
bool result = TryGetDispId((IDispatchInfo)obj, name, out dispId);
return result;
}
public static object Invoke(object obj, int dispId, object[] args)
{
string memberName = "[DispId=" + dispId + "]";
object result = Invoke(obj, memberName, args);
return result;
}
public static object Invoke(object obj, string memberName, object[] args)
{
RequireReference(obj, "obj");
Type type = obj.GetType();
object result = type.InvokeMember(memberName,
BindingFlags.InvokeMethod | BindingFlags.GetProperty,
null, obj, args, null);
return result;
}
private static void RequireReference<T>(T value, string name) where T : class
{
if (value == null)
{
throw new ArgumentNullException(name);
}
}
private static Type GetType(IDispatchInfo dispatch, bool throwIfNotFound)
{
RequireReference(dispatch, "dispatch");
Type result = null;
int typeInfoCount;
int hr = dispatch.GetTypeInfoCount(out typeInfoCount);
if (hr == S_OK && typeInfoCount > 0)
{
dispatch.GetTypeInfo(0, LOCALE_SYSTEM_DEFAULT, out result);
}
if (result == null && throwIfNotFound)
{
// If the GetTypeInfoCount called failed, throw an exception for that.
Marshal.ThrowExceptionForHR(hr);
// Otherwise, throw the same exception that Type.GetType would throw.
throw new TypeLoadException();
}
return result;
}
private static bool TryGetDispId(IDispatchInfo dispatch, string name, out int dispId)
{
RequireReference(dispatch, "dispatch");
RequireReference(name, "name");
bool result = false;
Guid iidNull = Guid.Empty;
int hr = dispatch.GetDispId(ref iidNull, ref name, 1, LOCALE_SYSTEM_DEFAULT, out dispId);
const int DISP_E_UNKNOWNNAME = unchecked((int)0x80020006); //From WinError.h
const int DISPID_UNKNOWN = -1; //From OAIdl.idl
if (hr == S_OK)
{
result = true;
}
else if (hr == DISP_E_UNKNOWNNAME && dispId == DISPID_UNKNOWN)
{
result = false;
}
else
{
Marshal.ThrowExceptionForHR(hr);
}
return result;
}
[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("00020400-0000-0000-C000-000000000046")]
private interface IDispatchInfo
{
[PreserveSig]
int GetTypeInfoCount(out int typeInfoCount);
void GetTypeInfo(int typeInfoIndex, int lcid, [MarshalAs(UnmanagedType.CustomMarshaler,
MarshalTypeRef = typeof(System.Runtime.InteropServices.CustomMarshalers.TypeToTypeInfoMarshaler))] out Type typeInfo);
[PreserveSig]
int GetDispId(ref Guid riid, ref string name, int nameCount, int lcid, out int dispId);
// NOTE: The real IDispatch also has an Invoke method next, but we don't need it.
}
}
这CustomMarshaler是惊人的!我一直在构建托管的TypeInfo和TypeLibraryInfo类来封装ITypeInfo和ITypeLib。这用一个函数来代替那些整个大类,我调用GetCOMType。您可以执行COM对象所需的所有Invokes和GetValues。感谢您向我们展示这种令人敬畏的技术。 – Mike 2016-03-30 14:09:36
+1真我已经调整了一点原始代码,但它的作用像魅力。当我得到Type t时,无论该类型是.Net类型还是__ComObject类型(即使TypeInfo仅在内存中存在),我都可以枚举该类型的所有成员。这应该被标记为正确的答案(而不是当我们明显可以的时候,我们不能这样做) – SoLaR 2017-02-19 23:07:23
谢谢。事实上,这个组件是一个内置脚本语言的商业应用程序。其成员的完整列表在运行时确定。它没有类型库。 – 2010-11-13 06:45:31
@HansPassant我碰到了这里的腥第三个参数的解释:https://www.codeproject.com/articles/523417/reflection-with-idispatch-based-com-objects – jnm2 2017-02-11 13:41:13