Я пытаюсь вызвать метод 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);





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
Я на самом деле полностью изменил свой ответ, он даже хуже :-)
Ура! Хорошо, спасибо, я посмотрю, что можно с этим сделать... 🙄
Я не знаю, доволен ли я больше тем, что все заработало, или разозлен тем, что такая ошибка могла быть допущена в основном интерфейсе. В любом случае, я надеюсь, что это поможет кому-то еще, потому что нигде в Интернете я не нашел никакой информации по этому поводу. Большое спасибо! Очень впечатлен тем, как вы так быстро это отследили!
Ваш код ответа не меняется местами, как должно быть. У него та же проблема. Хотя я точно понял, что вы имели в виду.
Спасибо, меня тоже поймали :-) это ошибка более 20 лет.
Другие проблемы: 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) и
и FindName следует использовать StringBuilder, потому что он изменен: FindName([MarshalAs(UnmanagedType.LPWStr)] StringBuilder szNameBuf, Интересно, как это так сильно испортилось.
@Charlieface - GetLibStatistics должен использовать два IntPtr, поскольку оба аргумента являются необязательными (хотя в документе упоминается только первый как необязательный). Вы правы в отношении GetDocumentation2. arg GetLibAttr является указателем указателя, так что все в порядке. GetAllCustData тоже в порядке, хотя можно было бы быть увереннее. Но мы согласны, что это беспорядок.
Для этого можно было бы пройти мимо Unsafe.NullRef (его не было, когда было написано преобразование интерфейса, примерно в 2001 году). GetLibAttr следует использовать класс для TLIBATTR, тогда вы получите двойной указатель.
Да, но это только .NET Core, ошибка есть и в .NET Framework.
Файл oaidl.idl в Windows SDK содержит методы в правильном порядке, поэтому кто знает, как он был неправильно импортирован в управляемый код.
В любом случае...
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);
}
}
Спасибо! В этом есть смысл. Я больше не получаю исключение AccessViolationException. Однако я получаю другое исключение: «Элемент не найден» (TYPE_E_ELEMENTNOTFOUND). Я попытался установить для pcUniqueNames значение null, используя Marshal.WriteIntPtr(pcUniqueNames, IntPtr.Zero); но это тоже не сработало. Как передать null методу? Это то, что я передаю в C++, который завершается успешно и возвращает значение.