调用IDesktopWallpaper处理ShellItemArray时遇MissingMethodException求助
读取和设置Windows壁纸幻灯片ShellItemArray的问题解决
问题描述
尝试通过IDesktopWallpaper::GetSlideshow获取壁纸幻灯片的IShellItemArray,再用SetSlideshow修改,但执行以下代码时抛出异常:
var x = (NativeMethods.IShellItemArray)Marshal.PtrToStructure(slideshow, typeof(NativeMethods.IShellItemArray));
异常信息:System.MissingMethodException: 无法创建接口实例。需要实现读取当前幻灯片的IShellItem列表,并用自定义逻辑设置新的幻灯片内容。
错误原因
Marshal.PtrToStructure仅适用于值类型结构体的内存转换,而IShellItemArray是COM接口指针,不能用该方法转换。正确做法是使用Marshal.GetObjectForIUnknown将原始接口指针转换为托管的COM接口实例。
解决方法
一、读取当前幻灯片的所有项目
修改GetSlideShow方法,正确转换COM指针并遍历IShellItemArray获取图片路径:
public List<string> GetSlideShowItems() { var itemsList = new List<string>(); IntPtr slideshowPtr = iDesktopWallpaper.GetSlideshow(); if (slideshowPtr == IntPtr.Zero) return itemsList; try { // 将非托管COM指针转换为IShellItemArray实例 NativeMethods.IShellItemArray shellArray = (NativeMethods.IShellItemArray)Marshal.GetObjectForIUnknown(slideshowPtr); // 获取数组内项目总数 shellArray.GetCount(out uint itemCount); // 遍历每个IShellItem获取路径 for (uint i = 0; i < itemCount; i++) { shellArray.GetItemAt(i, out NativeMethods.IShellItem shellItem); if (shellItem == null) continue; // SIGDN_FILESYSPATH:获取文件系统路径(值为0x80058000) uint sigdnType = 0x80058000; shellItem.GetDisplayName(sigdnType, out IntPtr pathPtr); string imagePath = Marshal.PtrToStringUni(pathPtr); itemsList.Add(imagePath); // 释放非托管内存和COM对象 Marshal.FreeCoTaskMem(pathPtr); Marshal.ReleaseComObject(shellItem); } } finally { // 释放IShellItemArray的COM引用 Marshal.Release(slideshowPtr); } return itemsList; }
二、创建新的ShellItemArray并设置幻灯片
将图片路径列表转换为IShellItemArray,再传入SetSlideshow方法:
public void SetSlideShowItems(List<string> imagePaths) { if (imagePaths == null || imagePaths.Count == 0) { // 传入空指针关闭幻灯片 iDesktopWallpaper.SetSlideshow(IntPtr.Zero); return; } var shellItems = new List<NativeMethods.IShellItem>(); Guid shellItemGuid = typeof(NativeMethods.IShellItem).GUID; // 将每个路径转换为IShellItem foreach (string path in imagePaths) { int result = NativeMethods.SHCreateItemFromParsingName(path, IntPtr.Zero, ref shellItemGuid, out NativeMethods.IShellItem shellItem); if (result == NativeMethods.S_OK && shellItem != null) { shellItems.Add(shellItem); } } if (shellItems.Count == 0) return; // 准备IShellItem指针数组,用于创建IShellItemArray IntPtr[] itemPtrs = shellItems.Select(si => Marshal.GetIUnknownForObject(si)).ToArray(); try { Guid shellArrayGuid = typeof(NativeMethods.IShellItemArray).GUID; IntPtr shellArrayPtr = IntPtr.Zero; // 通过SHCreateShellItemArrayFromIDLists创建IShellItemArray NativeMethods.SHCreateShellItemArrayFromIDLists((uint)itemPtrs.Length, itemPtrs, ref shellArrayGuid, out shellArrayPtr); if (shellArrayPtr != IntPtr.Zero) { iDesktopWallpaper.SetSlideshow(shellArrayPtr); Marshal.Release(shellArrayPtr); } } finally { // 释放所有临时COM指针和对象 foreach (IntPtr ptr in itemPtrs) { Marshal.Release(ptr); } foreach (var si in shellItems) { Marshal.ReleaseComObject(si); } } } // 在NativeMethods类中添加以下DllImport声明 [DllImport("shell32.dll", CharSet = CharSet.Unicode, PreserveSig = false)] internal static extern void SHCreateShellItemArrayFromIDLists( uint cidl, [In] IntPtr[] apidl, [In] ref Guid riid, out IntPtr ppv);
关键注意事项
- COM对象内存管理:所有从非托管环境获取的COM接口实例,必须调用
Marshal.ReleaseComObject或Marshal.Release释放,避免内存泄漏。 - 指针转换规则:COM接口指针只能用
Marshal.GetObjectForIUnknown转换,禁止使用Marshal.PtrToStructure处理接口类型。 - 路径获取参数:调用
IShellItem.GetDisplayName时,使用SIGDN_FILESYSPATH(值0x80058000)可确保获取本地文件系统的绝对路径。
内容的提问来源于stack exchange,提问作者Dr. Best
相关产品推荐
相关产品推荐

