ITypeLib2.GetLibStatistics() всегда вызывает исключение AccessViolationException в C#

Я пытаюсь вызвать метод GetLibStatistics интерфейса ITypeLib2. Я пробовала несколько вариантов и техник, но все бросают System.AccessViolationException: Attempted to read or write protected memory.

Мне удалось успешно выполнить этот метод в собственном COM на C++, поэтому я знаю, что используемый мной файл .tlb не виноват.

На данный момент я предполагаю, что GetLibStatistics не реализован в C#. Пожалуйста, порекомендуйте.

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);
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
0
68
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

ITypeLib2, определенный .NET, «просто» неверен. В 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;
}

И вот так в .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);
}

Итак, GetDocumentation2 и GetLibStatistics поменялись местами, и вместо этого вы вызываете GetDocumentation2 с явно неправильными параметрами (😱).

Это можно проверить в отладчике:

Поэтому вам необходимо переопределить ITypeLib2 в своем коде следующим образом:

[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);
}

И это сработает.

Для этого я создал задачу https://github.com/dotnet/runtime/issues/99946

Спасибо! В этом есть смысл. Я больше не получаю исключение AccessViolationException. Однако я получаю другое исключение: «Элемент не найден» (TYPE_E_ELEMENTNOTFOUND). Я попытался установить для pcUniqueNames значение null, используя Marshal.WriteIntPtr(pcUniqueNames, IntPtr.Zero); но это тоже не сработало. Как передать null методу? Это то, что я передаю в C++, который завершается успешно и возвращает значение.

skataben 19.03.2024 07:54

Я на самом деле полностью изменил свой ответ, он даже хуже :-)

Simon Mourier 19.03.2024 07:55

Ура! Хорошо, спасибо, я посмотрю, что можно с этим сделать... 🙄

skataben 19.03.2024 08:00

Я не знаю, доволен ли я больше тем, что все заработало, или разозлен тем, что такая ошибка могла быть допущена в основном интерфейсе. В любом случае, я надеюсь, что это поможет кому-то еще, потому что нигде в Интернете я не нашел никакой информации по этому поводу. Большое спасибо! Очень впечатлен тем, как вы так быстро это отследили!

skataben 19.03.2024 08:16

Ваш код ответа не меняется местами, как должно быть. У него та же проблема. Хотя я точно понял, что вы имели в виду.

skataben 19.03.2024 08:19

Спасибо, меня тоже поймали :-) это ошибка более 20 лет.

Simon Mourier 19.03.2024 08:26

Другие проблемы: pcUniqueNames должен был быть параметром outvoid GetLibStatistics(out int pcUniqueNames, out int pcchUniqueNames); И GetDocumentation1 отсутствует lcid параметр void GetDocumentation2(int index, int lcid, out string pbstrHelpString, out int pdwHelpStringContext, out string pbstrHelpStringDll); И другие также должны использовать параметр outvoid GetAllCustData(out CUSTDATA pCustData); и void GetLibAttr(out TLIBATTR ppTLibAttr) и

Charlieface 19.03.2024 12:54

и FindName следует использовать StringBuilder, потому что он изменен: FindName([MarshalAs(UnmanagedType.LPWStr)] StringBuilder szNameBuf, Интересно, как это так сильно испортилось.

Charlieface 19.03.2024 13:00

@Charlieface - GetLibStatistics должен использовать два IntPtr, поскольку оба аргумента являются необязательными (хотя в документе упоминается только первый как необязательный). Вы правы в отношении GetDocumentation2. arg GetLibAttr является указателем указателя, так что все в порядке. GetAllCustData тоже в порядке, хотя можно было бы быть увереннее. Но мы согласны, что это беспорядок.

Simon Mourier 19.03.2024 13:35

Для этого можно было бы пройти мимо Unsafe.NullRef (его не было, когда было написано преобразование интерфейса, примерно в 2001 году). GetLibAttr следует использовать класс для TLIBATTR, тогда вы получите двойной указатель.

Charlieface 19.03.2024 13:37

Да, но это только .NET Core, ошибка есть и в .NET Framework.

Simon Mourier 19.03.2024 13:39

Файл oaidl.idl в Windows SDK содержит методы в правильном порядке, поэтому кто знает, как он был неправильно импортирован в управляемый код.

В любом случае...


Вот фиксированный интерфейс ITypeLib2, поэтому вы можете просто скопировать/вставить его в свой проект.

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

Другие вопросы по теме