ITypeLib2.GetLibStatistics() always throws AccessViolationException in C#

56 views Asked by At

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);
2

There are 2 answers

11
Simon Mourier On BEST ANSWER

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

0
skataben On

The oaidl.idl file in the Windows SDK has the methods in the correct order, so who knows how it got imported into managed code incorrectly.

Anyway...


Here is the fixed ITypeLib2 interface so you can just copy/paste it into your project.

using System;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;

namespace MyNamespace
{
    using TYPEKIND = System.Runtime.InteropServices.ComTypes.TYPEKIND;

    //
    // Summary:
    //     Provides a managed definition of the ITypeLib2 interface.
    [ComImport]
    [Guid("00020411-0000-0000-C000-000000000046")]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    public interface ITypeLib2 : ITypeLib
    {
        //
        // Summary:
        //     Returns the number of type descriptions in the type library.
        //
        // Returns:
        //     The number of type descriptions in the type library.
        [PreserveSig]
        new int GetTypeInfoCount();

        //
        // Summary:
        //     Retrieves the specified type description in the library.
        //
        // Parameters:
        //   index:
        //     An index of the ITypeInfo interface to return.
        //
        //   ppTI:
        //     When this method returns, contains an ITypeInfo describing the type referenced
        //     by index. This parameter is passed uninitialized.
        new void GetTypeInfo(int index, out ITypeInfo ppTI);

        //
        // Summary:
        //     Retrieves the type of a type description.
        //
        // Parameters:
        //   index:
        //     The index of the type description within the type library.
        //
        //   pTKind:
        //     When this method returns, contains a reference to the TYPEKIND enumeration for
        //     the type description. This parameter is passed uninitialized.
        new void GetTypeInfoType(int index, out TYPEKIND pTKind);

        //
        // Summary:
        //     Retrieves the type description that corresponds to the specified GUID.
        //
        // Parameters:
        //   guid:
        //     The System.Guid, passed by reference, that represents the IID of the CLSID interface
        //     of the class whose type info is requested.
        //
        //   ppTInfo:
        //     When this method returns, contains the requested ITypeInfo interface. This parameter
        //     is passed uninitialized.
        new void GetTypeInfoOfGuid(ref Guid guid, out ITypeInfo ppTInfo);

        //
        // Summary:
        //     Retrieves the structure that contains the library's attributes.
        //
        // Parameters:
        //   ppTLibAttr:
        //     When this method returns, contains a structure that contains the library's attributes.
        //     This parameter is passed uninitialized.
        new void GetLibAttr(out IntPtr ppTLibAttr);

        //
        // Summary:
        //     Enables a client compiler to bind to a library's types, variables, constants,
        //     and global functions.
        //
        // Parameters:
        //   ppTComp:
        //     When this method returns, contains an ITypeComp instance for this ITypeLib. This
        //     parameter is passed uninitialized.
        new void GetTypeComp(out ITypeComp ppTComp);

        //
        // Summary:
        //     Retrieves the library's documentation string, the complete Help file name and
        //     path, and the context identifier for the library Help topic in the Help file.
        //
        //
        // Parameters:
        //   index:
        //     An index of the type description whose documentation is to be returned.
        //
        //   strName:
        //     When this method returns, contains a string that specifies the name of the specified
        //     item. This parameter is passed uninitialized.
        //
        //   strDocString:
        //     When this method returns, contains the documentation string for the specified
        //     item. This parameter is passed uninitialized.
        //
        //   dwHelpContext:
        //     When this method returns, contains the Help context identifier associated with
        //     the specified item. This parameter is passed uninitialized.
        //
        //   strHelpFile:
        //     When this method returns, contains a string that specifies the fully qualified
        //     name of the Help file. This parameter is passed uninitialized.
        new void GetDocumentation(int index, out string strName, out string strDocString, out int dwHelpContext, out string strHelpFile);

        //
        // Summary:
        //     Indicates whether a passed-in string contains the name of a type or member described
        //     in the library.
        //
        // Parameters:
        //   szNameBuf:
        //     The string to test.
        //
        //   lHashVal:
        //     The hash value of szNameBuf.
        //
        // Returns:
        //     true if szNameBuf was found in the type library; otherwise, false.
        [return: MarshalAs(UnmanagedType.Bool)]
        new bool IsName([MarshalAs(UnmanagedType.LPWStr)] string szNameBuf, int lHashVal);

        //
        // Summary:
        //     Finds occurrences of a type description in a type library.
        //
        // Parameters:
        //   szNameBuf:
        //     The name to search for.
        //
        //   lHashVal:
        //     A hash value to speed up the search, computed by the LHashValOfNameSys function.
        //     If lHashVal is 0, a value is computed.
        //
        //   ppTInfo:
        //     When this method returns, contains an array of pointers to the type descriptions
        //     that contain the name specified in szNameBuf. This parameter is passed uninitialized.
        //
        //
        //   rgMemId:
        //     When this method returns, contains an array of the MEMBERIDs of the found items;
        //     rgMemId [i] is the MEMBERID that indexes into the type description specified
        //     by ppTInfo [i]. This parameter cannot be null. This parameter is passed uninitialized.
        //
        //
        //   pcFound:
        //     On entry, a value, passed by reference, that indicates how many instances to
        //     look for. For example, pcFound = 1 can be called to find the first occurrence.
        //     The search stops when one instance is found. On exit, indicates the number of
        //     instances that were found. If the in and out values of pcFound are identical,
        //     there might be more type descriptions that contain the name.
        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);

        //
        // Summary:
        //     Releases the System.Runtime.InteropServices.TYPELIBATTR structure originally
        //     obtained from the System.Runtime.InteropServices.ComTypes.ITypeLib.GetLibAttr(System.IntPtr@)
        //     method.
        //
        // Parameters:
        //   pTLibAttr:
        //     The TLIBATTR structure to release.
        [PreserveSig]
        new void ReleaseTLibAttr(IntPtr pTLibAttr);

        //
        // Summary:
        //     Gets the custom data.
        //
        // Parameters:
        //   guid:
        //     A System.Guid , passed by reference, that is used to identify the data.
        //
        //   pVarVal:
        //     When this method returns, contains an object that specifies where to put the
        //     retrieved data. This parameter is passed uninitialized.
        void GetCustData(ref Guid guid, out object pVarVal);

        //
        // Summary:
        //     Returns statistics about a type library that are required for efficient sizing
        //     of hash tables.
        //
        // Parameters:
        //   pcUniqueNames:
        //     A pointer to a count of unique names. If the caller does not need this information,
        //     set to null.
        //
        //   pcchUniqueNames:
        //     When this method returns, contains a pointer to a change in the count of unique
        //     names. This parameter is passed uninitialized.
        void GetLibStatistics(IntPtr pcUniqueNames, out int pcchUniqueNames);

        //
        // Summary:
        //     Retrieves the library's documentation string, the complete Help file name and
        //     path, the localization context to use, and the context ID for the library Help
        //     topic in the Help file.
        //
        // Parameters:
        //   index:
        //     An index of the type description whose documentation is to be returned; if index
        //     is -1, the documentation for the library is returned.
        //
        //   pbstrHelpString:
        //     When this method returns, contains a BSTR that specifies the name of the specified
        //     item. If the caller does not need the item name, pbstrHelpString can be null.
        //     This parameter is passed uninitialized.
        //
        //   pdwHelpStringContext:
        //     When this method returns, contains the Help localization context. If the caller
        //     does not need the Help context, pdwHelpStringContext can be null. This parameter
        //     is passed uninitialized.
        //
        //   pbstrHelpStringDll:
        //     When this method returns, contains a BSTR that specifies the fully qualified
        //     name of the file containing the DLL used for Help file. If the caller does not
        //     need the file name, pbstrHelpStringDll can be null. This parameter is passed
        //     uninitialized.
        [LCIDConversion(1)]
        void GetDocumentation2(int index, out string pbstrHelpString, out int pdwHelpStringContext, out string pbstrHelpStringDll);

        //
        // Summary:
        //     Gets all custom data items for the library.
        //
        // Parameters:
        //   pCustData:
        //     A pointer to CUSTDATA, which holds all custom data items.
        void GetAllCustData(IntPtr pCustData);
    }
}