Private/TypeLibInterop.ps1
|
# TypeLib reader. Uses oleaut32!LoadTypeLibEx via a C# helper because # PowerShell's COM late-binding cannot QueryInterface for ITypeLib (the RCW # comes back as System.__ComObject and refuses the cast). The C# helper # returns plain CLR objects that serialize cleanly with ConvertTo-Json. function Initialize-TypeLibInterop { if ('SysUtils.Interop.TypeLibReader' -as [type]) { return } Add-Type -TypeDefinition @' using System; using System.Collections.Generic; using System.Runtime.InteropServices; using CT = System.Runtime.InteropServices.ComTypes; namespace SysUtils.Interop { public class TypeLibInfo { public Guid LibId; public int MajorVersion; public int MinorVersion; public int Lcid; public int LibFlags; public string SysKind; public string Name; public string DocString; public int HelpContext; public string HelpFile; public TypeInfoEntry[] TypeInfos; } public class TypeInfoEntry { public string Kind; public string Name; public Guid Guid; public string DocString; public int HelpContext; public string HelpFile; public int TypeFlags; public string[] TypeFlagsDecoded; public ImplementedInterface[] Interfaces; // COCLASS public ParentRef Parent; // INTERFACE / DISPATCH public MethodEntry[] Methods; // INTERFACE / DISPATCH public EnumMember[] Members; // ENUM / RECORD / UNION public string AliasOf; // ALIAS } public class ImplementedInterface { public string Name; public Guid Iid; public bool IsDefault; public bool IsSource; public bool IsRestricted; public bool IsDefaultVtable; } public class ParentRef { public string Name; public Guid Iid; } public class MethodEntry { public string Name; public int DispId; public string InvKind; public string FuncKind; public int FuncFlags; public string ReturnType; public ParamEntry[] Params; } public class ParamEntry { public string Name; public string Type; public bool IsIn; public bool IsOut; public bool IsRetVal; public bool IsOptional; } public class EnumMember { public string Name; public object Value; public int DispId; public string Kind; } public static class TypeLibReader { // REGKIND_NONE = 2: load metadata without touching the registry. [DllImport("oleaut32.dll", CharSet=CharSet.Unicode, ExactSpelling=true, PreserveSig=false)] private static extern void LoadTypeLibEx( [MarshalAs(UnmanagedType.LPWStr)] string szFile, int regkind, [MarshalAs(UnmanagedType.Interface)] out CT.ITypeLib pptlib); public static TypeLibInfo Read(string path) { CT.ITypeLib t; try { LoadTypeLibEx(path, 2, out t); } catch { return null; } try { return ReadLib(t); } finally { Marshal.ReleaseComObject(t); } } private static TypeLibInfo ReadLib(CT.ITypeLib t) { var info = new TypeLibInfo(); IntPtr pa; t.GetLibAttr(out pa); try { var la = (CT.TYPELIBATTR)Marshal.PtrToStructure(pa, typeof(CT.TYPELIBATTR)); info.LibId = la.guid; info.MajorVersion = la.wMajorVerNum; info.MinorVersion = la.wMinorVerNum; info.Lcid = la.lcid; info.LibFlags = (int)la.wLibFlags; info.SysKind = la.syskind.ToString(); } finally { t.ReleaseTLibAttr(pa); } string n, d, hf; int hc; t.GetDocumentation(-1, out n, out d, out hc, out hf); info.Name = n; info.DocString = d; info.HelpContext = hc; info.HelpFile = hf; int count = t.GetTypeInfoCount(); var entries = new List<TypeInfoEntry>(count); for (int i = 0; i < count; i++) { CT.ITypeInfo ti; t.GetTypeInfo(i, out ti); try { entries.Add(ReadTypeInfo(ti)); } finally { Marshal.ReleaseComObject(ti); } } info.TypeInfos = entries.ToArray(); return info; } private static TypeInfoEntry ReadTypeInfo(CT.ITypeInfo ti) { var entry = new TypeInfoEntry(); string n, d, hf; int hc; ti.GetDocumentation(-1, out n, out d, out hc, out hf); entry.Name = n; entry.DocString = d; entry.HelpContext = hc; entry.HelpFile = hf; IntPtr pTA; ti.GetTypeAttr(out pTA); CT.TYPEATTR ta; try { ta = (CT.TYPEATTR)Marshal.PtrToStructure(pTA, typeof(CT.TYPEATTR)); } finally { ti.ReleaseTypeAttr(pTA); } entry.Guid = ta.guid; entry.Kind = ta.typekind.ToString(); entry.TypeFlags = (int)ta.wTypeFlags; entry.TypeFlagsDecoded = DecodeTypeFlags((int)ta.wTypeFlags); switch (ta.typekind) { case CT.TYPEKIND.TKIND_COCLASS: entry.Interfaces = ReadImpls(ti, ta.cImplTypes); break; case CT.TYPEKIND.TKIND_INTERFACE: case CT.TYPEKIND.TKIND_DISPATCH: if (ta.cImplTypes > 0) entry.Parent = ReadParent(ti); entry.Methods = ReadMethods(ti, ta.cFuncs); break; case CT.TYPEKIND.TKIND_ENUM: case CT.TYPEKIND.TKIND_RECORD: case CT.TYPEKIND.TKIND_UNION: entry.Members = ReadMembers(ti, ta.cVars); break; case CT.TYPEKIND.TKIND_ALIAS: entry.AliasOf = TypeDescToString(ta.tdescAlias, ti, 0); break; } return entry; } private static ImplementedInterface[] ReadImpls(CT.ITypeInfo ti, int count) { var arr = new ImplementedInterface[count]; for (int k = 0; k < count; k++) { int hRef; ti.GetRefTypeOfImplType(k, out hRef); CT.ITypeInfo tiImpl; ti.GetRefTypeInfo(hRef, out tiImpl); CT.IMPLTYPEFLAGS flags; ti.GetImplTypeFlags(k, out flags); try { string n, d, hf; int hc; tiImpl.GetDocumentation(-1, out n, out d, out hc, out hf); IntPtr pIA; tiImpl.GetTypeAttr(out pIA); Guid iid; try { var ia = (CT.TYPEATTR)Marshal.PtrToStructure(pIA, typeof(CT.TYPEATTR)); iid = ia.guid; } finally { tiImpl.ReleaseTypeAttr(pIA); } int f = (int)flags; arr[k] = new ImplementedInterface { Name = n, Iid = iid, IsDefault = (f & 0x01) != 0, IsSource = (f & 0x02) != 0, IsRestricted = (f & 0x04) != 0, IsDefaultVtable = (f & 0x08) != 0 }; } finally { Marshal.ReleaseComObject(tiImpl); } } return arr; } private static ParentRef ReadParent(CT.ITypeInfo ti) { int hRef; ti.GetRefTypeOfImplType(0, out hRef); CT.ITypeInfo tiP; ti.GetRefTypeInfo(hRef, out tiP); try { string n, d, hf; int hc; tiP.GetDocumentation(-1, out n, out d, out hc, out hf); IntPtr pPA; tiP.GetTypeAttr(out pPA); try { var pa = (CT.TYPEATTR)Marshal.PtrToStructure(pPA, typeof(CT.TYPEATTR)); return new ParentRef { Name = n, Iid = pa.guid }; } finally { tiP.ReleaseTypeAttr(pPA); } } finally { Marshal.ReleaseComObject(tiP); } } private static MethodEntry[] ReadMethods(CT.ITypeInfo ti, int count) { int elemSize = Marshal.SizeOf(typeof(CT.ELEMDESC)); var arr = new MethodEntry[count]; for (int m = 0; m < count; m++) { IntPtr pFD; ti.GetFuncDesc(m, out pFD); try { var fd = (CT.FUNCDESC)Marshal.PtrToStructure(pFD, typeof(CT.FUNCDESC)); var entry = new MethodEntry { DispId = fd.memid, InvKind = fd.invkind.ToString(), FuncKind = fd.funckind.ToString(), FuncFlags = (int)fd.wFuncFlags }; var names = new string[fd.cParams + 1]; int cNames; ti.GetNames(fd.memid, names, names.Length, out cNames); entry.Name = names[0]; entry.ReturnType = TypeDescToString(fd.elemdescFunc.tdesc, ti, 0); var pars = new ParamEntry[fd.cParams]; for (int p = 0; p < fd.cParams; p++) { IntPtr edPtr = new IntPtr(fd.lprgelemdescParam.ToInt64() + p * elemSize); var ed = (CT.ELEMDESC)Marshal.PtrToStructure(edPtr, typeof(CT.ELEMDESC)); int pf = (int)ed.desc.paramdesc.wParamFlags; pars[p] = new ParamEntry { Name = (p + 1 < names.Length) ? names[p + 1] : ("p" + p), Type = TypeDescToString(ed.tdesc, ti, 0), IsIn = (pf & 0x01) != 0, IsOut = (pf & 0x02) != 0, IsRetVal = (pf & 0x08) != 0, IsOptional = (pf & 0x10) != 0 }; } entry.Params = pars; arr[m] = entry; } finally { ti.ReleaseFuncDesc(pFD); } } return arr; } private static EnumMember[] ReadMembers(CT.ITypeInfo ti, int count) { var arr = new EnumMember[count]; for (int v = 0; v < count; v++) { IntPtr pVD; ti.GetVarDesc(v, out pVD); try { var vd = (CT.VARDESC)Marshal.PtrToStructure(pVD, typeof(CT.VARDESC)); var names = new string[1]; int cNames; ti.GetNames(vd.memid, names, 1, out cNames); object val = null; if (vd.varkind == CT.VARKIND.VAR_CONST && vd.desc.lpvarValue != IntPtr.Zero) { try { val = Marshal.GetObjectForNativeVariant(vd.desc.lpvarValue); } catch { } } arr[v] = new EnumMember { Name = names[0], Value = val, DispId = vd.memid, Kind = vd.varkind.ToString() }; } finally { ti.ReleaseVarDesc(pVD); } } return arr; } private static string TypeDescToString(CT.TYPEDESC td, CT.ITypeInfo ti, int depth) { if (depth > 8) return "..."; int vt = (int)td.vt; switch (vt) { case 2: return "short"; case 3: return "long"; case 4: return "float"; case 5: return "double"; case 6: return "currency"; case 7: return "DATE"; case 8: return "BSTR"; case 9: return "IDispatch*"; case 10: return "SCODE"; case 11: return "VARIANT_BOOL"; case 12: return "VARIANT"; case 13: return "IUnknown*"; case 14: return "DECIMAL"; case 16: return "char"; case 17: return "byte"; case 18: return "ushort"; case 19: return "ulong"; case 20: return "int64"; case 21: return "uint64"; case 22: return "INT"; case 23: return "UINT"; case 24: return "void"; case 25: return "HRESULT"; case 26: { if (td.lpValue != IntPtr.Zero) { var inner = (CT.TYPEDESC)Marshal.PtrToStructure(td.lpValue, typeof(CT.TYPEDESC)); return TypeDescToString(inner, ti, depth + 1) + "*"; } return "void*"; } case 27: return "SAFEARRAY"; case 28: return "CARRAY"; case 29: { // VT_USERDEFINED: lpValue is the HREFTYPE union member, only // the low 32 bits are meaningful — on x64 the high 4 bytes // come from uninitialized union storage. Mask explicitly. int hRef = unchecked((int)(td.lpValue.ToInt64() & 0xFFFFFFFFL)); try { CT.ITypeInfo tiU; ti.GetRefTypeInfo(hRef, out tiU); try { string n, d, hf; int hc; tiU.GetDocumentation(-1, out n, out d, out hc, out hf); return n; } finally { Marshal.ReleaseComObject(tiU); } } catch { return "UserDefined"; } } case 30: return "LPSTR"; case 31: return "LPWSTR"; case 36: return "RECORD"; case 37: return "INT_PTR"; case 38: return "UINT_PTR"; default: return "VT_" + vt; } } private static string[] DecodeTypeFlags(int v) { var list = new List<string>(); if ((v & 0x0001) != 0) list.Add("AppObject"); if ((v & 0x0002) != 0) list.Add("CanCreate"); if ((v & 0x0004) != 0) list.Add("Licensed"); if ((v & 0x0008) != 0) list.Add("PreDeclId"); if ((v & 0x0010) != 0) list.Add("Hidden"); if ((v & 0x0020) != 0) list.Add("Control"); if ((v & 0x0040) != 0) list.Add("Dual"); if ((v & 0x0080) != 0) list.Add("NonExtensible"); if ((v & 0x0100) != 0) list.Add("OleAutomation"); if ((v & 0x0200) != 0) list.Add("Restricted"); if ((v & 0x0400) != 0) list.Add("Aggregatable"); if ((v & 0x0800) != 0) list.Add("Replaceable"); if ((v & 0x1000) != 0) list.Add("Dispatchable"); if ((v & 0x2000) != 0) list.Add("ReverseBind"); if ((v & 0x4000) != 0) list.Add("Proxy"); return list.ToArray(); } } } '@ } function Get-TypeLibInfoSafe { param([string]$FilePath) Initialize-TypeLibInterop try { return [SysUtils.Interop.TypeLibReader]::Read($FilePath) } catch { return $null } } |