Edited at

C#でITypeInfoクラスを使い倒してCOMオブジェクトの関数名とパラメータを表示させる

C#で実行時のCOMオブジェクトのインタフェースの関数情報の取得ができた話。

インタフェース名までわかればOleView.exeでほとんどの情報がとれてしまいそうなので需要がないかも。。。

ただ、何もインストールしなくても使えることは利点のはず。


ソースコード

まとまった情報が落ちていないので正しく使えているのか自信がない。


using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using ComTypes = System.Runtime.InteropServices.ComTypes;

public class MyParamDesc
{
// どっちが正か不明
// https://docs.microsoft.com/en-us/windows/win32/api/wtypes/ne-wtypes-varenum
// https://docs.microsoft.com/en-us/previous-versions/windows/embedded/ms897140(v%3Dmsdn.10)

enum VARENUM {
VT_EMPTY, VT_NULL, VT_I2, VT_I4, VT_R4,
VT_R8, VT_CY, VT_DATE, VT_BSTR, VT_DISPATCH,
VT_ERROR, VT_BOOL, VT_VARIANT, VT_UNKNOWN, VT_DECIMAL,
VT_I1, VT_UI1, VT_UI2, VT_UI4, VT_I8,
VT_UI8, VT_INT, VT_UINT, VT_VOID, VT_HRESULT,
VT_PTR, VT_SAFEARRAY, VT_CARRAY, VT_USERDEFINED, VT_LPSTR,
VT_LPWSTR, VT_RECORD, VT_INT_PTR, VT_UINT_PTR, VT_FILETIME,
VT_BLOB, VT_STREAM, VT_STORAGE, VT_STREAMED_OBJECT, VT_STORED_OBJECT,
VT_BLOB_OBJECT, VT_CF, VT_CLSID, VT_VERSIONED_STREAM, VT_BSTR_BLOB,
VT_VECTOR, VT_ARRAY, VT_BYREF, VT_RESERVED, VT_ILLEGAL,
VT_ILLEGALMASKED, VT_TYPEMASK
} ;

string varType;
string paramStr;

// https://docs.microsoft.com/ja-jp/windows/win32/api/oaidl/ns-oaidl-typedesc
// https://www.winehq.org/pipermail/wine-patches/2015-October/142759.html
public static MyParamDesc FromIndex(MyFuncDesc funcDesc, int index)
{
var ret = new MyParamDesc();
var refStr = "";

ComTypes.ELEMDESC e = funcDesc.elemdesc(index);
ComTypes.TYPEDESC t = e.tdesc;

ComTypes.PARAMDESC pa = e.desc.paramdesc;
ret.paramStr = Regex.Replace(pa.wParamFlags.ToString(), "PARAMFLAG_F", "").ToLowerInvariant(); // 雑
ret.varType = "";
//pa.wParamFlags.ToString();

while ((VARENUM)t.vt == VARENUM.VT_SAFEARRAY ||
(VARENUM)t.vt == VARENUM.VT_PTR) {
refStr += "*";
t = Marshal.PtrToStructure<ComTypes.TYPEDESC>(t.lpValue);
}

if ((VARENUM)t.vt == VARENUM.VT_LPSTR) {
using ( var myType2 = funcDesc.TypeInfo.FromTypeDesc(t) ) {
ret.varType += myType2.name;
}
}
else {
ret.varType += ((VARENUM)(t.vt)).ToString();
}
ret.varType += refStr;
return ret;
}

// default valueの取得(未実装): 下記が参考になりそう
// https://csharp.hotexamples.com/examples/-/PARAMDESC/-/php-paramdesc-class-examples.html

public override string ToString()
{
return ("["+ paramStr + "] " + varType);
}
}

public class MyFuncDesc : IDisposable
{
IntPtr _funcDescPtr;
ComTypes.FUNCDESC _funcDesc;
MyTypeInfo _myTypeInfo;

public int memid{get{return _funcDesc.memid;}}
public int cParams{get{return _funcDesc.cParams;}}
public ComTypes.FUNCKIND funckind{get{return _funcDesc.funckind;}}
public MyTypeInfo TypeInfo {get{return _myTypeInfo;}}

public ComTypes.ELEMDESC elemdesc(int index)
{
var elem = new ComTypes.ELEMDESC();
elem = Marshal.PtrToStructure<ComTypes.ELEMDESC>(_funcDesc.lprgelemdescParam + Marshal.SizeOf(elem)*index);
return elem;
}

public static MyFuncDesc FromIndex(MyTypeInfo typeInfo, int index)
{
if (typeInfo == null) {return null;}

IntPtr ptr = IntPtr.Zero;
try {
typeInfo.raw_ITypeInfo.GetFuncDesc(index, out ptr);
}
catch ( COMException ) { return null; }

if (ptr == IntPtr.Zero) { return null; }

ComTypes.FUNCDESC desc;
try {
desc = Marshal.PtrToStructure<ComTypes.FUNCDESC>(ptr);
}
catch ( Exception e ) {
typeInfo.ReleaseFuncDesc(ptr);
throw e;
}

var ret = new MyFuncDesc();
ret._myTypeInfo = typeInfo;
ret._funcDescPtr = ptr;
ret._funcDesc = desc;

return ret;
}

public string[] GetNames()
{
return _myTypeInfo.GetNames(memid);
}

public void Dispose()
{
if ( _funcDescPtr != IntPtr.Zero ) {
_myTypeInfo.ReleaseFuncDesc(_funcDescPtr);
_funcDescPtr = IntPtr.Zero;
}
}
}

public class MyVarDesc : IDisposable
{
IntPtr _varDescPtr;
ComTypes.VARDESC _varDesc;
MyTypeInfo _myTypeInfo;

public int memid{get{return _varDesc.memid;}}

public static MyVarDesc FromIndex(MyTypeInfo typeInfo, int index)
{
if (typeInfo == null) {return null;}

IntPtr ptr = IntPtr.Zero;
try {
typeInfo.raw_ITypeInfo.GetVarDesc(index, out ptr);
}
catch ( COMException ) { return null; }

if (ptr == IntPtr.Zero) { return null; }

ComTypes.VARDESC desc;
try {
desc = Marshal.PtrToStructure<ComTypes.VARDESC>(ptr);
}
catch ( Exception e ) {
typeInfo.ReleaseVarDesc(ptr);
throw e;
}

var ret = new MyVarDesc();
ret._myTypeInfo = typeInfo;
ret._varDescPtr = ptr;
ret._varDesc = desc;

return ret;
}

public string[] GetNames()
{
return _myTypeInfo.GetNames(memid);
}

public void Dispose()
{
if ( _varDescPtr != IntPtr.Zero ) {
_myTypeInfo.ReleaseVarDesc(_varDescPtr);
_varDescPtr = IntPtr.Zero;
}
}
}

public class MyTypeInfo : IDisposable
{
const int MAX_NAME_COUNT = 100;

IntPtr _typeInfoPtr;

ITypeInfo _typeInfo;
int _cFuncs;
int _cVars;
Guid _guid;
ComTypes.TYPEKIND _typekind;
ComTypes.IDLFLAG _idlFlags;
ComTypes.TYPEFLAGS _typeFlags;
string _name;
string _docString;

public int cFuncs{get{return _cFuncs;}}
public int cVars{get{return _cVars;}}
public Guid guid{get{return _guid;}}
public ComTypes.TYPEKIND typekind{get{return _typekind;}}
public ComTypes.IDLFLAG idlFlags{get{return _idlFlags;}}
public ComTypes.TYPEFLAGS typeFlags{get{return _typeFlags;}}
public string name{get{return _name;}}
public string docString{get{return _docString;}}

// publicにしたくないがMyFuncDescから使用したいためpublicにする。GetNamesをこっちに持ってくれば公開しなくて済みそう
public ITypeInfo raw_ITypeInfo{get{return _typeInfo;}}

[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("B196B283-BAB4-101A-B69C-00AA00341D07")]
private interface IProvideClassInfo
{
[return: MarshalAs(UnmanagedType.Interface)]
System.Runtime.InteropServices.ComTypes.ITypeInfo GetClassInfo();
}

[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("00020400-0000-0000-c000-000000000046")]
private interface IDispatch
{
[PreserveSig]
int GetTypeInfoCount(out int count);
[PreserveSig]
int GetTypeInfo([In] int itinfo, [In] int lcid, out IntPtr typeinfo);
[PreserveSig]
int GetIDsOfNames();//dummy. don't call this method
[PreserveSig]
int Invoke();//dummy. don't call this method
}

public static MyTypeInfo FromComObject(object obj)
{
ITypeInfo typeInfo = null;
IntPtr ptr = IntPtr.Zero; // これを使わずに生成する場合もあるので、Dispose()にてIntPtr.Zeroの場合はRelease(ptr)しない。

// if ( obj is IProvideClassInfo ) { // ie.document.getElementsByTagName("input")[0] とかで HTMLInputElement が取れる
// typeInfo = ((IProvideClassInfo)obj).GetClassInfo();
// }
//else
if ( obj is IDispatch ) {
var idisp = (IDispatch)obj;
int count;
int hResult = idisp.GetTypeInfoCount(out count);
if ( hResult >= 0 ) { // Succeeded
if ( count < 1 ) { // no type info
return null;
}
//Console.WriteLine(count);

idisp.GetTypeInfo(0, 0, out ptr);
if ( ptr == IntPtr.Zero ) {
return null;
}

typeInfo = (ITypeInfo)(Marshal.GetTypedObjectForIUnknown(ptr, typeof(ITypeInfo)));
}
}
else { // unknown type
return null;
}

var ret = FromTypeInfo(typeInfo);
if ( ret == null ) {
if ( ptr != IntPtr.Zero ) {
Marshal.Release(ptr);
}
return null;
}
ret._typeInfoPtr = ptr;

return ret;
}

public static MyTypeInfo FromTypeInfo(ITypeInfo argTypeInfo)
{
if ( argTypeInfo == null ) {return null;}
ComTypes.TYPEATTR typeAttr;
IntPtr ptr;
MyTypeInfo ret = null;

try {
argTypeInfo.GetTypeAttr(out ptr);
}
catch ( COMException ) { return null; }

try {
typeAttr = Marshal.PtrToStructure<ComTypes.TYPEATTR>(ptr);

ret = new MyTypeInfo();
ret._typeInfo = argTypeInfo;
ret._cFuncs = typeAttr.cFuncs;
ret._cVars = typeAttr.cVars;
ret._guid = typeAttr.guid; // Guidは値型(ValueType)なので、typeattrがreleaseされた後に使用しても問題ない
ret._typekind = typeAttr.typekind;
ret._idlFlags = typeAttr.idldescType.wIDLFlags;
ret._typeFlags = typeAttr.wTypeFlags;

string strName;
string strDocString;
int dwHelpContext;
string strHelpFile;

argTypeInfo.GetDocumentation(-1, out strName, out strDocString, out dwHelpContext, out strHelpFile);

var tmp = new StringBuilder();
tmp.Append(strName); ret._name = tmp.ToString(); tmp.Clear();
tmp.Append(strDocString); ret._docString = tmp.ToString(); tmp.Clear();
}
finally {
argTypeInfo.ReleaseTypeAttr(ptr);
}

return ret;
}

public string[] GetNames(int memid)
{
string[] ret;
string[] buf = new string[MAX_NAME_COUNT];
int nbuf;
var tmp = new StringBuilder();

raw_ITypeInfo.GetNames(memid, buf, buf.Length, out nbuf); //

ret = new string[nbuf];
for ( int i=0 ; i<nbuf ; i++ ) {
// typeInfoがreleaseされても残るようにコピーする
// string.Copy()は値コピーを保証しない(Microsoft docsより)
tmp.Clear();
tmp.Append(buf[i]);
ret[i] = tmp.ToString();
}
return ret;
}

// t.vt must be VT_LPSTR
public MyTypeInfo FromTypeDesc(ComTypes.TYPEDESC t)
{
int hreftype = (int)t.lpValue;
ITypeInfo typeInfo2;

_typeInfo.GetRefTypeInfo(hreftype, out typeInfo2);

return MyTypeInfo.FromTypeInfo(typeInfo2);
}

public void ReleaseFuncDesc(IntPtr funcDescPtr)
{
_typeInfo.ReleaseFuncDesc(funcDescPtr);
}

public void ReleaseVarDesc(IntPtr varDescPtr)
{
_typeInfo.ReleaseVarDesc(varDescPtr);
}

public void Dispose()
{
if ( _typeInfoPtr != IntPtr.Zero ) {
Marshal.Release(_typeInfoPtr);
_typeInfoPtr = IntPtr.Zero;
}
}
}

public class MyDumpClass
{
// rcw must be System.__ComObject type.
public static void ShowTypeInfoOfComObject(object rcw)
{
Console.WriteLine(rcw.GetType());
using ( var typeInfo = MyTypeInfo.FromComObject(rcw) ) {
if ( typeInfo != null ) {
Console.WriteLine("GUID: " + typeInfo.guid);
Console.WriteLine("Name: " + typeInfo.name);
Console.WriteLine("Documentation: " + typeInfo.docString);
Console.WriteLine(typeInfo.typekind);
Console.WriteLine(typeInfo.idlFlags);
Console.WriteLine(typeInfo.typeFlags);

int n;
n = typeInfo.cFuncs;
Console.Write(n);
Console.WriteLine(" functions:");
for ( int i=0; i<n; i++ ) {
using ( var funcDesc = MyFuncDesc.FromIndex(typeInfo, i)) {
string[] names = funcDesc.GetNames();
Console.Write(" ");
Console.WriteLine((names.Length>0)?names[0]:"<No Name>");
for ( int k=0 ; k<funcDesc.cParams ; k++ ) {
Console.Write(" ");
Console.WriteLine(MyParamDesc.FromIndex(funcDesc, k) +" "+ ((names.Length>k+1)?names[k+1]:"<No Name>"));
}
}
}

n = typeInfo.cVars;
Console.Write(n);
Console.WriteLine(" variables:");
for ( int i=0; i<n; i++ ) {
using ( var varDesc = MyVarDesc.FromIndex(typeInfo, i)) {
string[] names = varDesc.GetNames();
Console.Write(" ");
Console.WriteLine(names[0]);
}
}
}
}
}
}

class SampleTest
{
[STAThread]
static void Main()
{
Type comType = Type.GetTypeFromProgID("Shell.Application");
Console.WriteLine("Creating Shell instance...");
dynamic shellApp = Activator.CreateInstance(comType);
Console.WriteLine("created.");

try {
dynamic windows = shellApp.Windows;
// MyDumpClass.ShowTypeInfoOfComObject(windows);

for (int i=0;i<windows.Count;i++) {
dynamic w = windows[i];
MyDumpClass.ShowTypeInfoOfComObject(w);
// MyDumpClass.ShowTypeInfoOfComObject(w.Document);
// MyDumpClass.ShowTypeInfoOfComObject(w.Document.getElementsByTagName("input")[0]);
if(i==0){break;} // 1個だけで終了させる
}
}
finally {
// リソースの解放
Marshal.ReleaseComObject(shellApp);
}
}
}


実行結果

IEを起動しておくと下記のような情報がとれる


Creating Shell instance...
created.
System.__ComObject
GUID: d30c1661-cdaf-11d0-8a3e-00c04fc9e26e
Name: IWebBrowser2
Documentation: Web Browser Interface for IE4.
TKIND_INTERFACE
IDLFLAG_NONE
TYPEFLAG_FHIDDEN, TYPEFLAG_FDUAL, TYPEFLAG_FOLEAUTOMATION, TYPEFLAG_FDISPATCHABLE
19 functions:
Navigate2
[in] VT_VARIANT* URL
[in, opt] VT_VARIANT* Flags
[in, opt] VT_VARIANT* TargetFrameName
[in, opt] VT_VARIANT* PostData
[in, opt] VT_VARIANT* Headers
QueryStatusWB
[in] OLECMDID cmdID
[out, retval] OLECMDF* pcmdf
ExecWB
[in] OLECMDID cmdID
[in] OLECMDEXECOPT cmdexecopt
[in, opt] VT_VARIANT* pvaIn
[in, out, opt] VT_VARIANT* pvaOut
ShowBrowserBar
[in] VT_VARIANT* pvaClsid
[in, opt] VT_VARIANT* pvarShow
[in, opt] VT_VARIANT* pvarSize
ReadyState
[out, retval] tagREADYSTATE* plReadyState
Offline
[out, retval] VT_BOOL* pbOffline
Offline
[in] VT_BOOL pbOffline
Silent
[out, retval] VT_BOOL* pbSilent
Silent
[in] VT_BOOL pbSilent
RegisterAsBrowser
[out, retval] VT_BOOL* pbRegister
RegisterAsBrowser
[in] VT_BOOL pbRegister
RegisterAsDropTarget
[out, retval] VT_BOOL* pbRegister
RegisterAsDropTarget
[in] VT_BOOL pbRegister
TheaterMode
[out, retval] VT_BOOL* pbRegister
TheaterMode
[in] VT_BOOL pbRegister
AddressBar
[out, retval] VT_BOOL* Value
AddressBar
[in] VT_BOOL Value
Resizable
[out, retval] VT_BOOL* Value
Resizable
[in] VT_BOOL Value
0 variables: