Search code examples
c#.netcomaccess-violationtypelib

ITypeLib2.GetLibStatistics() always throws AccessViolationException in C#


I'm trying to call the GetLibStatistics method of the ITypeLib2 interface. I've tried several variations and techniques, but they all throw System.AccessViolationException: Attempted to read or write protected memory.

I was able to successfully execute that method in native COM in C++, so I know the .tlb file I'm using is not at fault.

My guess at this point is that GetLibStatistics is not implemented in C#. Please advise.

public static void GetLibStatisticsTest()
{
    ITypeLib tlb;
    LoadTypeLibEx(@"C:\Sample.tlb", RegKind.None, out tlb);

    var tlb2 = tlb as ITypeLib2;
    if (tlb2 == null ) { return; }

    try
    {
        IntPtr pcUniqueNames = IntPtr.Zero;
        int pcchUniqueNames;

        // This always throws `System.AccessViolationException`. Why??
        tlb2.GetLibStatistics(pcUniqueNames, out pcchUniqueNames);;
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
    }
}

[DllImport("oleaut32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern int LoadTypeLibEx(string szFile, RegKind regKind, out ITypeLib pptlib);

Solution

  • ITypeLib2 defined by .NET is "simply" wrong. It's defined like this in C/C++:

    ITypeLib2 : public ITypeLib
    {
    public:
        virtual HRESULT STDMETHODCALLTYPE GetCustData(REFGUID guid, VARIANT *pVarVal) = 0;
        virtual HRESULT STDMETHODCALLTYPE GetLibStatistics(ULONG *pcUniqueNames, ULONG *pcchUniqueNames) = 0;
        virtual HRESULT STDMETHODCALLTYPE GetDocumentation2(INT index, LCID lcid, BSTR *pbstrHelpString, DWORD *pdwHelpStringContext, BSTR *pbstrHelpStringDll) = 0;
        virtual HRESULT STDMETHODCALLTYPE GetAllCustData(CUSTDATA *pCustData) = 0;
    }
    

    And like this in .NET

    public interface ITypeLib2 : ITypeLib
    {
        ... ITypeLib ...
    
        void GetCustData(ref Guid guid, out object pVarVal);
        void GetDocumentation2(int index, out string pbstrHelpString, out int pdwHelpStringContext, out string pbstrHelpStringDll);
        void GetLibStatistics(IntPtr pcUniqueNames, out int pcchUniqueNames);
        void GetAllCustData(IntPtr pCustData);
    }
    

    So GetDocumentation2 and GetLibStatistics have been switched and you're calling GetDocumentation2 instead with obviously wrong parameters (😱).

    This can be checked in a debugger:

    enter image description here

    So you must redefine ITypeLib2 in your code like this:

    [ComImport, Guid("00020411-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    public interface ITypeLib2 : ITypeLib
    {
        [PreserveSig]
        new int GetTypeInfoCount();
        new void GetTypeInfo(int index, out ITypeInfo ppTI);
        new void GetTypeInfoType(int index, out System.Runtime.InteropServices.ComTypes.TYPEKIND pTKind);
        new void GetTypeInfoOfGuid(ref Guid guid, out ITypeInfo ppTInfo);
        new void GetLibAttr(out IntPtr ppTLibAttr);
        new void GetTypeComp(out ITypeComp ppTComp);
        new void GetDocumentation(int index, out string strName, out string strDocString, out int dwHelpContext, out string strHelpFile);
        new bool IsName([MarshalAs(UnmanagedType.LPWStr)] string szNameBuf, int lHashVal);
        new void FindName([MarshalAs(UnmanagedType.LPWStr)] string szNameBuf, int lHashVal, [Out][MarshalAs(UnmanagedType.LPArray)] ITypeInfo[] ppTInfo, [Out][MarshalAs(UnmanagedType.LPArray)] int[] rgMemId, ref short pcFound);
        [PreserveSig]
        new void ReleaseTLibAttr(IntPtr pTLibAttr);
    
        void GetCustData(ref Guid guid, out object pVarVal);
        void GetLibStatistics(IntPtr pcUniqueNames, out int pcchUniqueNames);
        void GetDocumentation2(int index, out string pbstrHelpString, out int pdwHelpStringContext, out string pbstrHelpStringDll);
        void GetAllCustData(IntPtr pCustData);
    }
    

    And it will work.

    I've created an issue for this https://github.com/dotnet/runtime/issues/99946