VS2022扩展调用IVsObjectList2.GetText崩溃,如何阻止CLR自动释放内存?
IVsObjectList2.GetText调用致VS崩溃:封送错误分析与修复方案
问题背景
在C#开发Visual Studio 2022扩展时,调用COM接口IVsObjectList2.GetText后出现堆损坏导致的VS崩溃,根源在于官方互操作代码的封送定义与原生COM接口规范冲突。
接口定义对比
原生COM接口规范
根据官方文档,GetText的第三个参数ppszText由接口实现方分配内存,调用者仅需保留引用,绝对不能释放,原生定义如下:
HRESULT IVsObjectList2::GetText( [in] ULONG Index, [in] VSTREETEXTOPTIONS tto, [out] const WCHAR **ppszText );
官方C#互操作定义问题
反编译Visual Studio提供的互操作代码后,发现GetText的定义存在错误:
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)] int GetText( [In][ComAliasName("Microsoft.VisualStudio.OLE.Interop.ULONG")] uint index, [In][ComAliasName("Microsoft.VisualStudio.Shell.Interop.VSTREETEXTOPTIONS")] VSTREETEXTOPTIONS tto, [MarshalAs(UnmanagedType.LPWStr)] out string ppszText);
使用[MarshalAs(UnmanagedType.LPWStr)]标记out string参数时,CLR会自动调用CoTaskMemFree释放该字符串内存,这完全违反了原生接口“不能释放”的要求,最终导致堆损坏崩溃——你的调用栈中RtlValidateHeap和RtlFreeHeap的报错也验证了这一点。
修复方案:自定义接口封送
可以通过重新定义接口并修改参数封送方式解决问题,具体步骤如下:
1. 自定义IVsObjectList2接口
在你的代码中重新定义接口,将第三个参数改为IntPtr,避免CLR自动释放内存。注意必须保证接口GUID和方法顺序与原生接口完全一致(COM接口依赖VTable顺序,不能出错):
[ComImport] [Guid("B64D79E0-243B-4B0B-B97A-5E5D0A13434A")] // IVsObjectList2的官方GUID [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] public interface ICustomIVsObjectList2 { // 按原生接口顺序实现所有方法,此处仅示例GetText int GetItemCount(out uint pCount); // ... 其他方法省略,需与原生IVsObjectList2方法顺序完全匹配 [MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)] int GetText( [In] uint index, [In] VSTREETEXTOPTIONS tto, out IntPtr ppszText); // ... 其他方法省略 }
2. 转换接口并调用
将获取到的IVsObjectList2实例转换为自定义接口,手动将IntPtr转换为字符串,避免自动释放:
IVsObjectManager2 objectManager2 = MyExtensionPackage.GetGlobalService(typeof(SVsObjectManager)) as IVsObjectManager2; if (objectManager2.FindLibrary(new Guid(BrowseLibraryGuids80.VC), out IVsLibrary2 lib) == VSConstants.S_OK) { var criteria = new VSOBSEARCHCRITERIA2(); if (lib.GetList2( (uint)_LIB_LISTTYPE.LLT_CLASSES, (uint)_LIB_LISTFLAGS.LLF_DONTUPDATELIST, new[] { criteria }, out IVsObjectList2 list) == VSConstants.S_OK && list != null) { if (list.GetItemCount(out uint count) == VSConstants.S_OK && count > 0) { var customList = list as ICustomIVsObjectList2; if (customList != null) { for (uint idx = 0; idx < count; ++idx) { if (customList.GetText(idx, VSTREETEXTOPTIONS.TTO_PREFIX, out IntPtr textPtr) == VSConstants.S_OK) { // 手动转换字符串,不触发自动释放 string prefix = Marshal.PtrToStringUni(textPtr); // 使用prefix } } } } } }
此方案完全遵循原生COM接口的内存管理规则,可彻底解决堆损坏导致的VS崩溃问题。
内容的提问来源于stack exchange,提问作者Sedenion
相关产品推荐
相关产品推荐

