diff --git a/Ink Canvas/Helpers/PPTManager.cs b/Ink Canvas/Helpers/PPTManager.cs index 732409ad..de2f6acf 100644 --- a/Ink Canvas/Helpers/PPTManager.cs +++ b/Ink Canvas/Helpers/PPTManager.cs @@ -263,10 +263,8 @@ namespace Ink_Canvas.Helpers { var pptApp = (Microsoft.Office.Interop.PowerPoint.Application)Marshal.GetActiveObject("PowerPoint.Application"); - // 验证COM对象是否有效 if (pptApp != null && Marshal.IsComObject(pptApp)) { - // 尝试访问一个简单的属性来验证连接 var _ = pptApp.Name; return pptApp; } @@ -275,15 +273,46 @@ namespace Ink_Canvas.Helpers catch (COMException ex) { var hr = (uint)ex.HResult; + if (hr == 0x800401E3 || hr == 0x800401F3 || hr == 0x800401E4) + { + LogHelper.WriteLogToFile($"检测到 COM 注册损坏 (HR: 0x{hr:X8}),尝试使用 ROT 备用方法", LogHelper.LogType.Warning); + return TryConnectToPowerPointViaROT(); + } return null; } catch (InvalidCastException) { - // COM对象类型转换失败 - return null; + LogHelper.WriteLogToFile("COM 对象类型转换失败,尝试使用 ROT 备用方法", LogHelper.LogType.Warning); + return TryConnectToPowerPointViaROT(); } - catch (Exception) + catch (Exception ex) { + LogHelper.WriteLogToFile($"常规连接方法失败: {ex.Message},尝试使用 ROT 备用方法", LogHelper.LogType.Warning); + return TryConnectToPowerPointViaROT(); + } + } + + private Microsoft.Office.Interop.PowerPoint.Application TryConnectToPowerPointViaROT() + { + try + { + LogHelper.WriteLogToFile("开始使用 ROT 备用方法连接 PowerPoint", LogHelper.LogType.Trace); + var pptApp = PPTROTConnectionHelper.TryConnectViaROT(IsSupportWPS); + + if (pptApp != null) + { + LogHelper.WriteLogToFile("ROT 备用方法连接成功", LogHelper.LogType.Event); + } + else + { + LogHelper.WriteLogToFile("ROT 备用方法连接失败", LogHelper.LogType.Warning); + } + + return pptApp; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"ROT 备用方法连接异常: {ex}", LogHelper.LogType.Error); return null; } } @@ -294,10 +323,8 @@ namespace Ink_Canvas.Helpers { var wpsApp = (Microsoft.Office.Interop.PowerPoint.Application)Marshal.GetActiveObject("kwpp.Application"); - // 验证COM对象是否有效 if (wpsApp != null && Marshal.IsComObject(wpsApp)) { - // 尝试访问一个简单的属性来验证连接 var _ = wpsApp.Name; return wpsApp; } @@ -306,13 +333,12 @@ namespace Ink_Canvas.Helpers catch (COMException ex) { var hr = (uint)ex.HResult; - // 忽略常见的WPS连接错误: - // 0x800401E3: 操作无法使用 - // 0x80004005: 未指定错误 - // 0x800706B5: RPC服务器不可用 - // 0x8001010E: 应用程序调用一个已为另一线程整理的接口 - // 0x800401F3: 无效的类字符串(WPS未安装或COM组件未注册) - if (hr != 0x800401E3 && hr != 0x80004005 && hr != 0x800706B5 && hr != 0x8001010E && hr != 0x800401F3) + if (hr == 0x800401E3 || hr == 0x800401F3 || hr == 0x800401E4) + { + LogHelper.WriteLogToFile($"检测到 WPS COM 注册损坏 (HR: 0x{hr:X8}),尝试使用 ROT 备用方法", LogHelper.LogType.Warning); + return TryConnectToWPSViaROT(); + } + if (hr != 0x80004005 && hr != 0x800706B5 && hr != 0x8001010E) { LogHelper.WriteLogToFile($"连接WPS失败: {ex}", LogHelper.LogType.Warning); } @@ -320,12 +346,37 @@ namespace Ink_Canvas.Helpers } catch (InvalidCastException) { - // COM对象类型转换失败 - return null; + LogHelper.WriteLogToFile("WPS COM 对象类型转换失败,尝试使用 ROT 备用方法", LogHelper.LogType.Warning); + return TryConnectToWPSViaROT(); } catch (Exception ex) { - LogHelper.WriteLogToFile($"连接WPS时发生意外错误: {ex}", LogHelper.LogType.Warning); + LogHelper.WriteLogToFile($"连接WPS时发生意外错误: {ex},尝试使用 ROT 备用方法", LogHelper.LogType.Warning); + return TryConnectToWPSViaROT(); + } + } + + private Microsoft.Office.Interop.PowerPoint.Application TryConnectToWPSViaROT() + { + try + { + LogHelper.WriteLogToFile("开始使用 ROT 备用方法连接 WPS", LogHelper.LogType.Trace); + var wpsApp = PPTROTConnectionHelper.TryConnectViaROT(true); + + if (wpsApp != null) + { + LogHelper.WriteLogToFile("ROT 备用方法连接 WPS 成功", LogHelper.LogType.Event); + } + else + { + LogHelper.WriteLogToFile("ROT 备用方法连接 WPS 失败", LogHelper.LogType.Warning); + } + + return wpsApp; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"ROT 备用方法连接 WPS 异常: {ex}", LogHelper.LogType.Error); return null; } } @@ -429,19 +480,40 @@ namespace Ink_Canvas.Helpers LogHelper.WriteLogToFile($"取消PPT事件注册失败: {ex}", LogHelper.LogType.Warning); } - // 安全释放COM对象 SafeReleaseComObject(CurrentSlide, "CurrentSlide"); SafeReleaseComObject(CurrentSlides, "CurrentSlides"); SafeReleaseComObject(CurrentPresentation, "CurrentPresentation"); - SafeReleaseComObject(PPTApplication, "PPTApplication"); + + if (PPTApplication != null && Marshal.IsComObject(PPTApplication)) + { + try + { + int refCount = Marshal.ReleaseComObject(PPTApplication); + while (refCount > 0) + { + refCount = Marshal.ReleaseComObject(PPTApplication); + } + } + catch + { + try + { + Marshal.FinalReleaseComObject(PPTApplication); + } + catch { } + } + } - // 清理引用 PPTApplication = null; CurrentPresentation = null; CurrentSlides = null; CurrentSlide = null; SlidesCount = 0; + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + // 重新启动连接检查定时器 _connectionCheckTimer?.Start(); diff --git a/Ink Canvas/Helpers/PPTROTConnectionHelper.cs b/Ink Canvas/Helpers/PPTROTConnectionHelper.cs new file mode 100644 index 00000000..86ff267d --- /dev/null +++ b/Ink Canvas/Helpers/PPTROTConnectionHelper.cs @@ -0,0 +1,453 @@ +using Microsoft.Office.Interop.PowerPoint; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.ComTypes; +using System.Text; +using System.Threading; + +namespace Ink_Canvas.Helpers +{ + public static class PPTROTConnectionHelper + { + #region Win32 API Declarations + [DllImport("ole32.dll")] + private static extern int GetRunningObjectTable(int reserved, out IRunningObjectTable prot); + + [DllImport("ole32.dll")] + private static extern int CreateBindCtx(int reserved, out IBindCtx ppbc); + + [DllImport("user32.dll")] + private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId); + + [DllImport("user32.dll")] + private static extern IntPtr GetForegroundWindow(); + + [DllImport("user32.dll")] + private static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount); + + [DllImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool IsWindowVisible(IntPtr hWnd); + + [DllImport("user32.dll")] + private static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect); + + [StructLayout(LayoutKind.Sequential)] + private struct RECT + { + public int Left; + public int Top; + public int Right; + public int Bottom; + } + #endregion + + #region Constants + private static readonly Guid PowerPointApplicationGuid = new Guid("91493441-5A91-11CF-8700-00AA0060263B"); + + private static readonly string[] PptLikeExtensions = new[] + { + ".pptx", ".pptm", ".ppt", + ".ppsx", ".ppsm", ".pps", + ".potx", ".potm", ".pot", + ".dps", ".dpt" + }; + #endregion + + #region Public Methods + public static Microsoft.Office.Interop.PowerPoint.Application TryConnectViaROT(bool isSupportWPS = false) + { + try + { + LogHelper.WriteLogToFile("开始通过 ROT 查找 PowerPoint 应用程序", LogHelper.LogType.Trace); + + object bestApp = GetAnyActivePowerPoint(null, out int bestPriority, out _, isSupportWPS); + + if (bestApp != null && bestPriority > 0) + { + try + { + var pptApp = bestApp as Microsoft.Office.Interop.PowerPoint.Application; + if (pptApp != null) + { + var _ = pptApp.Name; + LogHelper.WriteLogToFile($"通过 ROT 成功连接到 PowerPoint (优先级: {bestPriority})", LogHelper.LogType.Event); + return pptApp; + } + else + { + dynamic dynApp = bestApp; + var name = dynApp.Name; + LogHelper.WriteLogToFile($"通过 ROT 成功连接到 PowerPoint (dynamic, 优先级: {bestPriority})", LogHelper.LogType.Event); + return bestApp as Microsoft.Office.Interop.PowerPoint.Application; + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"ROT 连接验证失败: {ex.Message}", LogHelper.LogType.Warning); + SafeReleaseComObject(bestApp); + return null; + } + } + + LogHelper.WriteLogToFile("通过 ROT 未找到可用的 PowerPoint 应用程序", LogHelper.LogType.Trace); + return null; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"ROT 连接过程发生异常: {ex}", LogHelper.LogType.Error); + return null; + } + } + #endregion + + #region Private Methods + private static object GetAnyActivePowerPoint(object targetApp, out int bestPriority, out int targetPriority, bool isSupportWPS) + { + IRunningObjectTable rot = null; + IEnumMoniker enumMoniker = null; + + object bestApp = null; + bestPriority = 0; + targetPriority = 0; + int highestPriority = 0; + + List foundAppObjects = new List(); + + try + { + int hr = GetRunningObjectTable(0, out rot); + if (hr != 0 || rot == null) + { + LogHelper.WriteLogToFile("无法获取 Running Object Table", LogHelper.LogType.Warning); + return null; + } + + rot.EnumRunning(out enumMoniker); + if (enumMoniker == null) + { + LogHelper.WriteLogToFile("无法枚举 ROT 中的对象", LogHelper.LogType.Warning); + return null; + } + + IMoniker[] moniker = new IMoniker[1]; + IntPtr fetched = IntPtr.Zero; + + while (enumMoniker.Next(1, moniker, fetched) == 0) + { + IBindCtx bindCtx = null; + object comObject = null; + dynamic candidateApp = null; + string displayName = "Unknown"; + dynamic activePres = null; + dynamic ssWindow = null; + bool keepAlive = false; + + try + { + CreateBindCtx(0, out bindCtx); + moniker[0].GetDisplayName(bindCtx, null, out displayName); + + if (LooksLikePresentationFile(displayName) || displayName == "!{91493441-5A91-11CF-8700-00AA0060263B}") + { + rot.GetObject(moniker[0], out comObject); + if (comObject != null) + { + try + { + object appObj = comObject.GetType().InvokeMember("Application", BindingFlags.GetProperty, null, comObject, null); + candidateApp = appObj; + } + catch + { + candidateApp = comObject; + } + } + } + bool isDuplicate = false; + if (candidateApp != null) + { + foreach (var processedApp in foundAppObjects) + { + if (AreComObjectsEqual((object)candidateApp, processedApp)) + { + isDuplicate = true; + break; + } + } + + if (!isDuplicate) + { + foundAppObjects.Add(candidateApp); + keepAlive = true; + } + } + + if (candidateApp != null && !isDuplicate) + { + int currentPriority = 0; + bool isTarget = false; + + if (targetApp != null && AreComObjectsEqual((object)candidateApp, targetApp)) + { + isTarget = true; + } + + try + { + try + { + activePres = candidateApp.ActivePresentation; + } + catch { } + + if (activePres != null) + { + currentPriority = 1; + + try + { + ssWindow = activePres.SlideShowWindow; + } + catch { } + + if (ssWindow != null) + { + currentPriority = 2; + + try + { + bool isActive = false; + try + { + object val = ssWindow.Active; + if (val is int && (int)val == -1) isActive = true; + else if (val is bool && (bool)val == true) isActive = true; + } + catch { } + + if (isActive) + { + currentPriority = 3; + } + else + { + if (IsSlideShowWindowActive(ssWindow, isSupportWPS)) + { + currentPriority = 3; + } + } + } + catch { } + } + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"计算优先级时出错: {ex.Message}", LogHelper.LogType.Warning); + } + + if (isTarget) + { + targetPriority = currentPriority; + } + + if (currentPriority > 0) + { + if (currentPriority > highestPriority) + { + highestPriority = currentPriority; + SafeReleaseComObject(bestApp); + bestApp = candidateApp; + candidateApp = null; + } + } + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"ROT 枚举循环中出错: {ex.Message}", LogHelper.LogType.Warning); + } + finally + { + SafeReleaseComObject(ssWindow); + SafeReleaseComObject(activePres); + + if (!keepAlive) + { + SafeReleaseComObject(candidateApp); + } + + CleanUpLoopObjects(bindCtx, moniker[0], comObject); + } + } + + bestPriority = highestPriority; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"ROT 扫描关键错误: {ex}", LogHelper.LogType.Error); + } + finally + { + if (foundAppObjects != null) + { + foreach (var cachedApp in foundAppObjects) + { + if (bestApp != null && ReferenceEquals(cachedApp, bestApp)) + continue; + + SafeReleaseComObject(cachedApp); + } + foundAppObjects.Clear(); + } + + if (enumMoniker != null) Marshal.ReleaseComObject(enumMoniker); + if (rot != null) Marshal.ReleaseComObject(rot); + } + + return bestApp; + } + + private static bool AreComObjectsEqual(object o1, object o2) + { + if (o1 == null || o2 == null) return false; + if (ReferenceEquals(o1, o2)) return true; + + IntPtr pUnk1 = IntPtr.Zero; + IntPtr pUnk2 = IntPtr.Zero; + try + { + pUnk1 = Marshal.GetIUnknownForObject(o1); + pUnk2 = Marshal.GetIUnknownForObject(o2); + return pUnk1 == pUnk2; + } + catch { return false; } + finally + { + if (pUnk1 != IntPtr.Zero) Marshal.Release(pUnk1); + if (pUnk2 != IntPtr.Zero) Marshal.Release(pUnk2); + } + } + + private static bool LooksLikePresentationFile(string displayName) + { + if (string.IsNullOrEmpty(displayName)) + return false; + + string lower = displayName.ToLowerInvariant(); + foreach (var ext in PptLikeExtensions) + { + if (lower.Contains(ext)) + return true; + } + return false; + } + + private static bool IsSlideShowWindowActive(object sswObj, bool isSupportWPS) + { + try + { + IntPtr foregroundHwnd = GetForegroundWindow(); + if (foregroundHwnd == IntPtr.Zero) return false; + + uint fgPid; + GetWindowThreadProcessId(foregroundHwnd, out fgPid); + + IntPtr sswHwnd = IntPtr.Zero; + try + { + sswHwnd = GetPptHwndFromSlideShowWindow(sswObj); + } + catch { return false; } + + if (sswHwnd == IntPtr.Zero) return false; + + uint sswPid; + GetWindowThreadProcessId(sswHwnd, out sswPid); + + if (fgPid == sswPid) return true; + if (isSupportWPS) + { + try + { + using (Process fgProc = Process.GetProcessById((int)fgPid)) + using (Process appProc = Process.GetProcessById((int)sswPid)) + { + string fgName = fgProc.ProcessName.ToLower(); + string appName = appProc.ProcessName.ToLower(); + + if (fgName.StartsWith("wps") && appName.StartsWith("wpp")) + { + return true; + } + } + } + catch { } + } + + return false; + } + catch + { + return false; + } + } + + private static IntPtr GetPptHwndFromSlideShowWindow(object pptSlideShowWindowObj) + { + if (pptSlideShowWindowObj == null) return IntPtr.Zero; + + try + { + dynamic ssw = pptSlideShowWindowObj; + object hwndObj = ssw.HWND; + + if (hwndObj is int) + { + return new IntPtr((int)hwndObj); + } + else if (hwndObj is IntPtr) + { + return (IntPtr)hwndObj; + } + + return IntPtr.Zero; + } + catch + { + return IntPtr.Zero; + } + } + + private static void SafeReleaseComObject(object comObj) + { + if (comObj == null) return; + + if (Marshal.IsComObject(comObj)) + { + try + { + Marshal.ReleaseComObject(comObj); + } + catch { } + } + } + + private static void CleanUpLoopObjects(IBindCtx bindCtx, IMoniker moniker, object comObject) + { + if (comObject != null && Marshal.IsComObject(comObject)) + Marshal.ReleaseComObject(comObject); + if (moniker != null) + Marshal.ReleaseComObject(moniker); + if (bindCtx != null) + Marshal.ReleaseComObject(bindCtx); + } + #endregion + } +} +