C#中如何将文件路径转为IDataObject指针调用IAssocHandler::Invoke
解决IAssocHandler.Invoke所需的IDataObject构建问题
你的核心问题在于直接传递字符串路径给DataObject无法被IAssocHandler正确识别——IAssocHandler::Invoke期望的是包含CF_HDROP格式数据的IDataObject,这是Shell用于传递文件列表的标准格式,而非简单的字符串。下面是兼容Windows 7和10的正确实现方案:
关键原理
CF_HDROP是Windows Shell定义的剪贴板格式,用于表示一个或多个文件路径。WinForms的DataObject内置了对该格式的支持,通过SetFileDropList方法可以自动构建符合要求的IDataObject,无需手动调用底层Win32 API构建HDROP结构。
修改后的核心代码
替换你原来的DataObject构建部分,以下是完整的修正后代码片段:
using System; using System.Collections.Specialized; using System.Runtime.InteropServices; using System.Windows.Forms; namespace AssocHandlerWithPointer { [Flags] public enum ASSOC_FILTER { ASSOC_FILTER_NONE = 0x00000000, ASSOC_FILTER_RECOMMENDED = 0x00000001 } public class Test { [DllImport("Shell32", EntryPoint = "SHAssocEnumHandlers", PreserveSig = false)] public extern static void SHAssocEnumHandlers([MarshalAs(UnmanagedType.LPWStr)] string pszExtra, ASSOC_FILTER afFilter, [Out] out IntPtr ppEnumHandler); // IEnumAssocHandlers [UnmanagedFunctionPointer(CallingConvention.Winapi, CharSet = CharSet.Unicode)] private delegate int FuncNext(IntPtr refer, int celt, [Out, MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.Interface, SizeParamIndex = 1)] IntPtr[] rgelt, [Out] out int pceltFetched); // IAssocHandler [UnmanagedFunctionPointer(CallingConvention.Winapi, CharSet = CharSet.Unicode)] private delegate int FuncGetUiName(IntPtr refer, out IntPtr ppsz); [UnmanagedFunctionPointer(CallingConvention.Winapi, CharSet = CharSet.Unicode)] private delegate int FuncInvoke(IntPtr refer, IntPtr pdo); static void Main(string[] args) { const string extension = ".html"; IntPtr pEnumAssocHandlers; SHAssocEnumHandlers(extension, ASSOC_FILTER.ASSOC_FILTER_RECOMMENDED, out pEnumAssocHandlers); IntPtr pFuncNext = Marshal.ReadIntPtr(Marshal.ReadIntPtr(pEnumAssocHandlers) + 3 * sizeof(int)); FuncNext next = (FuncNext)Marshal.GetDelegateForFunctionPointer(pFuncNext, typeof(FuncNext)); IntPtr[] pArrayAssocHandlers = new IntPtr[255]; int num; int resNext = next(pEnumAssocHandlers, 255, pArrayAssocHandlers, out num); if (resNext == 0) { const string filePath = @"c:\devtest\htmlTest.html"; // 构建包含文件路径的StringCollection StringCollection fileList = new StringCollection(); fileList.Add(filePath); // 创建符合CF_HDROP格式的DataObject DataObject dataObject = new DataObject(); dataObject.SetFileDropList(fileList); // 获取IDataObject的COM接口指针 IntPtr pDataObject = Marshal.GetComInterfaceForObject(dataObject, typeof(IDataObject)); for (int i = 0; i < num; i++) { IntPtr pAssocHandler = pArrayAssocHandlers[i]; IntPtr pFuncGetUiName = Marshal.ReadIntPtr(Marshal.ReadIntPtr(pAssocHandler) + 4 * sizeof(int)); FuncGetUiName getUiName = (FuncGetUiName)Marshal.GetDelegateForFunctionPointer(pFuncGetUiName, typeof(FuncGetUiName)); IntPtr pUiName; int resGetUiName = getUiName(pAssocHandler, out pUiName); Console.WriteLine("UI: " + Marshal.PtrToStringUni(pUiName)); // 释放字符串指针(IAssocHandler::GetUiName返回的字符串需要手动释放) Marshal.FreeCoTaskMem(pUiName); // 调用Invoke方法 IntPtr pFuncInvoke = Marshal.ReadIntPtr(Marshal.ReadIntPtr(pAssocHandler) + 5 * sizeof(int)); FuncInvoke invoke = (FuncInvoke)Marshal.GetDelegateForFunctionPointer(pFuncInvoke, typeof(FuncInvoke)); int resInvoke = invoke(pAssocHandler, pDataObject); Console.WriteLine($"Invoke返回值: {resInvoke}"); Console.WriteLine("-----"); Marshal.Release(pArrayAssocHandlers[i]); } // 释放IDataObject指针 Marshal.Release(pDataObject); } Marshal.Release(pEnumAssocHandlers); Console.ReadLine(); } } }
关键修正点说明
正确构建CF_HDROP格式数据:
使用DataObject.SetFileDropList方法传入StringCollection(可以包含多个文件路径),该方法会自动将数据打包为CF_HDROP格式,完全符合Shell接口的要求。正确获取IDataObject指针:
使用Marshal.GetComInterfaceForObject直接获取IDataObject的COM接口指针,而不是GetIUnknownForObject——虽然IUnknown可以转换为IDataObject,但直接获取目标接口指针更简洁可靠。内存泄漏修复:
IAssocHandler::GetUiName返回的字符串指针需要调用Marshal.FreeCoTaskMem手动释放(因为它是由Shell分配的内存)。- 手动释放
IDataObject的COM指针,避免内存泄漏。
兼容性说明
该方案完全兼容Windows 7和Windows 10:
SetFileDropList是.NET Framework 2.0以来的标准API,在Win7及以上系统均支持。CF_HDROP是Windows Shell的原生格式,所有版本的Shell接口都能正确识别。
内容的提问来源于stack exchange,提问作者Patrick
相关产品推荐
相关产品推荐

