From c76021194ac5893e17690b2bd445f92d93a270ac Mon Sep 17 00:00:00 2001 From: CJKmkp <2564608840@qq.com> Date: Tue, 24 Feb 2026 12:21:46 +0800 Subject: [PATCH] =?UTF-8?q?improve:ROT=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Ink Canvas/Helpers/PPTManager.cs | 19 +- Ink Canvas/Helpers/ROTPPTManager.cs | 3920 +++++++++++++++++++++------ Ink Canvas/MainWindow_cs/MW_PPT.cs | 95 +- 3 files changed, 3214 insertions(+), 820 deletions(-) diff --git a/Ink Canvas/Helpers/PPTManager.cs b/Ink Canvas/Helpers/PPTManager.cs index 6579551f..68e47cb9 100644 --- a/Ink Canvas/Helpers/PPTManager.cs +++ b/Ink Canvas/Helpers/PPTManager.cs @@ -459,6 +459,12 @@ namespace Ink_Canvas.Helpers try { Thread.Sleep(2000); + + if (_disposed) + { + _isModuleUnloading = false; + return; + } GC.Collect(); GC.WaitForPendingFinalizers(); @@ -467,7 +473,18 @@ namespace Ink_Canvas.Helpers Thread.Sleep(1000); _isModuleUnloading = false; - _unifiedPptTimer?.Start(); + + try + { + if (!_disposed) + { + _unifiedPptTimer?.Start(); + } + } + catch (ObjectDisposedException) + { + LogHelper.WriteLogToFile("PPT联动模块重载时计时器已释放,跳过重启", LogHelper.LogType.Trace); + } LogHelper.WriteLogToFile("PPT联动模块已重新加载", LogHelper.LogType.Trace); } diff --git a/Ink Canvas/Helpers/ROTPPTManager.cs b/Ink Canvas/Helpers/ROTPPTManager.cs index cd4a9f7e..96aa1ba6 100644 --- a/Ink Canvas/Helpers/ROTPPTManager.cs +++ b/Ink Canvas/Helpers/ROTPPTManager.cs @@ -1,6 +1,11 @@ using Microsoft.Office.Interop.PowerPoint; using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Reflection; using System.Runtime.InteropServices; +using System.Text; using System.Threading; using System.Timers; using System.Windows.Threading; @@ -9,9 +14,6 @@ using Timer = System.Timers.Timer; namespace Ink_Canvas.Helpers { - /// - /// 基于 ROT 的 PPT 联动管理器实现。 - /// public class ROTPPTManager : IPPTLinkManager { #region Events @@ -25,36 +27,33 @@ namespace Ink_Canvas.Helpers #endregion #region Properties - /// - /// 通过 ROT 获取到的 PowerPoint.Application 实例。 - /// - public object PPTApplication => _pptApplication; - + public dynamic PPTApplication { get; private set; } + public dynamic CurrentPresentation { get; private set; } + public dynamic CurrentSlides { get; private set; } + public dynamic CurrentSlide { get; private set; } + public int SlidesCount { get; private set; } public bool IsConnected { get { try { - if (_pptApplication == null) return false; - if (!Marshal.IsComObject(_pptApplication)) return false; + if (PPTApplication == null) return false; + if (!Marshal.IsComObject(PPTApplication)) return false; - try + // 尝试访问一个简单的属性来验证连接是否有效 + var _ = PPTApplication.Name; + return true; + } + catch (COMException comEx) + { + var hr = (uint)comEx.HResult; + // 如果COM对象已失效,返回false + if (hr == 0x8001010E || hr == 0x80004005 || hr == 0x800706B5) { - dynamic app = _pptApplication; - var _ = app.Name; - return true; - } - catch (COMException comEx) - { - var hr = (uint)comEx.HResult; - if (hr == 0x8001010E || hr == 0x80004005) - { - return true; - } - if (hr == 0x800706B5) return false; return false; } + return false; } catch { @@ -62,7 +61,6 @@ namespace Ink_Canvas.Helpers } } } - public bool IsInSlideShow { get @@ -72,12 +70,11 @@ namespace Ink_Canvas.Helpers object view = null; try { - if (_pptApplication == null || !Marshal.IsComObject(_pptApplication)) return false; + if (PPTApplication == null || !Marshal.IsComObject(PPTApplication)) return false; - dynamic app = _pptApplication; - slideShowWindows = app.SlideShowWindows; + slideShowWindows = PPTApplication.SlideShowWindows; if (slideShowWindows == null) return false; - + dynamic ssw = slideShowWindows; if (ssw.Count == 0) return false; @@ -89,7 +86,7 @@ namespace Ink_Canvas.Helpers dynamic sswObj = slideShowWindow; view = sswObj.View; if (view == null) return false; - + return true; } catch (COMException comEx) @@ -97,6 +94,7 @@ namespace Ink_Canvas.Helpers var hr = (uint)comEx.HResult; if (hr == 0x8001010E || hr == 0x80004005) { + DisconnectFromPPT(); } return false; } @@ -106,16 +104,14 @@ namespace Ink_Canvas.Helpers var hr = (uint)comEx.HResult; if (hr == 0x8001010E || hr == 0x80004005) { + DisconnectFromPPT(); } - else - { - LogHelper.WriteLogToFile($"[ROT] 检查 PPT 放映状态失败: {comEx.Message} (HR: 0x{hr:X8})", LogHelper.LogType.Warning); - } + LogHelper.WriteLogToFile($"检查PPT放映状态失败: {comEx.Message} (HR: 0x{hr:X8})", LogHelper.LogType.Warning); return false; } catch (Exception ex) { - LogHelper.WriteLogToFile($"[ROT] 检查 PPT 放映状态时发生意外错误: {ex}", LogHelper.LogType.Warning); + LogHelper.WriteLogToFile($"检查PPT放映状态时发生意外错误: {ex}", LogHelper.LogType.Warning); return false; } finally @@ -126,211 +122,660 @@ namespace Ink_Canvas.Helpers } } } - public bool IsSupportWPS { get; set; } = false; public bool SkipAnimationsWhenNavigating { get; set; } = false; - - /// - /// 当前演示文稿的总页数(每次按需计算,不缓存 COM 对象)。 - /// - public int SlidesCount - { - get - { - object pres = null; - object slides = null; - try - { - pres = GetCurrentActivePresentation(); - if (pres == null) return 0; - - dynamic dp = pres; - slides = dp.Slides; - if (slides == null) return 0; - - dynamic ds = slides; - return (int)ds.Count; - } - catch - { - return 0; - } - finally - { - SafeReleaseComObject(slides); - SafeReleaseComObject(pres); - } - } - } #endregion #region Private Fields - // 唯一持久化的 COM 对象字段:PPT 应用程序实例 - private object _pptApplication; - - private Timer _unifiedRotTimer; - private int _rotTickCount; - - private bool _isModuleUnloading; + private Thread _monitoringThread; + private bool _shouldStop = false; + private readonly object _monitoringLock = new object(); + private Timer _wpsProcessCheckTimer; + private Process _wpsProcess; + private bool _isModuleUnloading = false; + private bool _hasWpsProcessId; + private DateTime _wpsProcessRecordTime = DateTime.MinValue; + private int _wpsProcessCheckCount; + private WpsWindowInfo _lastForegroundWpsWindow; + private DateTime _lastWindowCheckTime = DateTime.MinValue; private bool _lastSlideShowState; private readonly object _lockObject = new object(); private bool _disposed; + + private dynamic _pptActivePresentation; + private dynamic _pptSlideShowWindow; + private int _polling = 0; + private bool _forcePolling = true; + private bool _bindingEvents = false; + private DateTime _updateTime; + private int _lastPolledSlideNumber = -1; #endregion - #region Lifecycle + #region Constructor & Initialization public ROTPPTManager() { - InitializeTimers(); } - private void InitializeTimers() - { - _unifiedRotTimer = new Timer(500); - _unifiedRotTimer.Elapsed += OnUnifiedRotTimerElapsed; - _unifiedRotTimer.AutoReset = true; - } - - private void OnUnifiedRotTimerElapsed(object sender, ElapsedEventArgs e) - { - var tick = Interlocked.Increment(ref _rotTickCount); - - OnConnectionCheckTimerElapsed(sender, e); - - if (tick % 2 == 0) - OnSlideShowStateCheckTimerElapsed(sender, e); - } - - /// - /// 启动内部轮询计时器以开始监测 ROT/PPT 连接状态和幻灯片放映状态。 - /// - /// - /// 如果实例已被释放(Dispose 已调用)或计时器未初始化,则此方法不会执行任何操作。 - /// public void StartMonitoring() { if (_disposed) return; - _unifiedRotTimer?.Start(); + lock (_monitoringLock) + { + if (_monitoringThread != null && _monitoringThread.IsAlive) + { + return; + } + + _shouldStop = false; + _monitoringThread = new Thread(PptComService) + { + IsBackground = true, + Name = "PPTMonitoringThread" + }; + _monitoringThread.Start(); + LogHelper.WriteLogToFile("PPT监控已启动", LogHelper.LogType.Trace); + } } - /// - /// 停止内部的 ROT 监视计时器并断开与当前 PowerPoint 实例的连接。 - /// public void StopMonitoring() { - _unifiedRotTimer?.Stop(); - DisconnectFromPPT(restartMonitoring: false); + lock (_monitoringLock) + { + _shouldStop = true; + + if (_monitoringThread != null && _monitoringThread.IsAlive) + { + // 等待线程退出,最多等待2秒 + if (!_monitoringThread.Join(2000)) + { + LogHelper.WriteLogToFile("等待监控线程退出超时", LogHelper.LogType.Warning); + } + } + + DisconnectFromPPT(); + LogHelper.WriteLogToFile("PPT监控已停止", LogHelper.LogType.Trace); + } } - /// - /// 强制断开当前与 PowerPoint 的 ROT 连接并触发后续的重连流程。 - /// public void ReloadConnection() { if (_disposed) return; + LogHelper.WriteLogToFile("[ROT] 执行热重载:强制断开并重新连接", LogHelper.LogType.Event); - DisconnectFromPPT(restartMonitoring: true); + + lock (_monitoringLock) + { + _shouldStop = true; + + if (_monitoringThread != null && _monitoringThread.IsAlive) + { + if (!_monitoringThread.Join(2000)) + { + LogHelper.WriteLogToFile("等待监控线程退出超时(热重载)", LogHelper.LogType.Warning); + } + } + + DisconnectFromPPT(); + _monitoringThread = null; + _shouldStop = false; + _isModuleUnloading = false; + } + + StartMonitoring(); } #endregion #region Connection Management - /// - /// 在计时器触发时检查与 PowerPoint 的 ROT 连接,并在尚未处于已释放或模块卸载期间时尝试通过 ROT 建立连接。 - /// - /// - /// 如果在检查或连接过程中发生异常,异常信息会记录到日志并被吞掉,不会向上抛出。 - /// - private void OnConnectionCheckTimerElapsed(object sender, ElapsedEventArgs e) + private void PptComService() { + LogHelper.WriteLogToFile("PPT Monitor ReStarted", LogHelper.LogType.Trace); + + _bindingEvents = false; + _lastPolledSlideNumber = -1; + _polling = 0; + + int tempTotalPage = -1; + try { - if (_disposed || _isModuleUnloading) - return; - CheckAndConnectToPPTViaRot(); + while (!_shouldStop && !_isModuleUnloading) + { + object bestApp = PPTROTConnectionHelper.GetAnyActivePowerPoint(PPTApplication, out int bestPriority, out int targetPriority); + bool needRebind = false; + + if (bestApp == null) + { + var appFallback = TryConnectToPowerPoint(); + if (appFallback != null) + { + bestApp = appFallback; + bestPriority = 1; + } + } + + if (PPTApplication == null && bestApp != null) + { + needRebind = true; + } + else if (PPTApplication != null && bestApp != null && bestPriority > targetPriority) + { + if (!PPTROTConnectionHelper.AreComObjectsEqual(PPTApplication, bestApp)) + { + needRebind = true; + } + } + + if (needRebind) + { + bool wait = (PPTApplication != null); + DisconnectFromPPT(); + + if (bestApp != null) + { + if (wait) Thread.Sleep(1000); + + PPTApplication = bestApp; + + try + { + _pptActivePresentation = PPTApplication.ActivePresentation; + _updateTime = DateTime.Now; + + try + { + _pptSlideShowWindow = _pptActivePresentation.SlideShowWindow; + tempTotalPage = GetTotalSlideIndex(_pptActivePresentation); + } + catch + { + tempTotalPage = -1; + } + + if (tempTotalPage == -1) + { + _lastPolledSlideNumber = -1; + _polling = 0; + } + else + { + try + { + _lastPolledSlideNumber = GetCurrentSlideIndex(_pptSlideShowWindow); + + if (GetCurrentSlideIndex(_pptSlideShowWindow) >= GetTotalSlideIndex(_pptActivePresentation)) _polling = 1; + else _polling = 0; + } + catch + { + _lastPolledSlideNumber = -1; + _polling = 1; + } + } + + ConnectToPPT(null); + + try + { + dynamic pptAppDynamic = PPTApplication; + LogHelper.WriteLogToFile($"成功绑定! {pptAppDynamic.Name}", LogHelper.LogType.Trace); + } + catch + { + LogHelper.WriteLogToFile("成功绑定!", LogHelper.LogType.Trace); + } } catch (Exception ex) { - LogHelper.WriteLogToFile($"[ROT] PPT 连接检查失败: {ex}", LogHelper.LogType.Error); - } - } + LogHelper.WriteLogToFile($"绑定失败: {ex.Message}", LogHelper.LogType.Warning); + DisconnectFromPPT(); + } + } + } + else + { + if (bestApp != null && (PPTApplication == null || !PPTROTConnectionHelper.AreComObjectsEqual(PPTApplication, bestApp))) + { + PPTROTConnectionHelper.SafeReleaseComObject(bestApp); + bestApp = null; + } + else if (bestApp == null && PPTApplication != null) + { + LogHelper.WriteLogToFile("检测到PPT已关闭,断开连接", LogHelper.LogType.Trace); + DisconnectFromPPT(); + } + } - /// - /// 在定时器触发时检查并在必要时更新 PPT 的放映状态。 - /// - /// - /// 当实例未被释放、模块未在卸载过程中且与 PowerPoint 仍保持连接时会调用内部的放映状态检查逻辑。若检查过程中发生异常,会将错误写入日志并吞并异常以保证定时器循环继续运行。 - /// - private void OnSlideShowStateCheckTimerElapsed(object sender, ElapsedEventArgs e) - { - try - { - if (_disposed || _isModuleUnloading || !IsConnected) - return; - CheckSlideShowState(); + if (PPTApplication != null && _pptActivePresentation != null) + { + dynamic activePresentation = null; + dynamic slideShowWindow = null; + + try + { + // 检查COM对象是否仍然有效 + if (!System.Runtime.InteropServices.Marshal.IsComObject(PPTApplication)) + { + DisconnectFromPPT(); + continue; + } + + try + { + var _ = System.Runtime.InteropServices.Marshal.GetIUnknownForObject(PPTApplication); + System.Runtime.InteropServices.Marshal.Release(_); + } + catch (System.Runtime.InteropServices.InvalidComObjectException) + { + // COM对象已失效 + DisconnectFromPPT(); + continue; + } + + activePresentation = PPTApplication.ActivePresentation; + + if (!PPTROTConnectionHelper.AreComObjectsEqual(_pptActivePresentation, activePresentation)) + { + LogHelper.WriteLogToFile("检测到演示文稿切换,断开连接", LogHelper.LogType.Trace); + DisconnectFromPPT(); + continue; + } + } + catch (System.Runtime.InteropServices.InvalidComObjectException) + { + // COM对象已失效 + LogHelper.WriteLogToFile("检测到COM对象失效,断开连接", LogHelper.LogType.Trace); + DisconnectFromPPT(); + continue; + } + catch (COMException ex) when ((uint)ex.ErrorCode == 0x8001010A) + { + LogHelper.WriteLogToFile("PowerPoint 忙,稍后重试", LogHelper.LogType.Trace); + } + catch (COMException comEx) + { + // COM异常,对象可能已失效 + var hr = (uint)comEx.HResult; + LogHelper.WriteLogToFile($"检查演示文稿状态COM异常: {comEx.Message} (HR: 0x{hr:X8})", LogHelper.LogType.Warning); + DisconnectFromPPT(); + continue; } catch (Exception ex) { - LogHelper.WriteLogToFile($"[ROT] PPT 放映状态检查失败: {ex}", LogHelper.LogType.Error); + LogHelper.WriteLogToFile($"检查演示文稿状态失败: {ex.Message}", LogHelper.LogType.Warning); + DisconnectFromPPT(); + continue; + } + finally + { + PPTROTConnectionHelper.SafeReleaseComObject(activePresentation); + activePresentation = null; + } + + bool isSlideShowActive = false; + try + { + activePresentation = PPTApplication.ActivePresentation; + + dynamic slideShowWindows = PPTApplication.SlideShowWindows; + int count = 0; + if (slideShowWindows != null) + { + count = slideShowWindows.Count; + } + + if (activePresentation != null && count > 0) + { + isSlideShowActive = true; + + dynamic activeSlideShowWindow = null; + + try + { + for (int i = 1; i <= count; i++) + { + try + { + dynamic ssw = slideShowWindows[i]; + if (PPTROTConnectionHelper.IsSlideShowWindowActive(ssw)) + { + activeSlideShowWindow = ssw; + break; + } + } + catch { } + } + } + catch { } + + if (activeSlideShowWindow == null) + { + try + { + activeSlideShowWindow = activePresentation.SlideShowWindow; + } + catch { } + } + + if (activeSlideShowWindow != null) + { + slideShowWindow = activeSlideShowWindow; + if (_pptSlideShowWindow == null || !PPTROTConnectionHelper.IsValidSlideShowWindow(_pptSlideShowWindow)) + { + if (!PPTROTConnectionHelper.AreComObjectsEqual(_pptSlideShowWindow, slideShowWindow)) + { + bool isNewWindow = _pptSlideShowWindow == null; + PPTROTConnectionHelper.SafeReleaseComObject(_pptSlideShowWindow); + _pptSlideShowWindow = slideShowWindow; + + if (isNewWindow) + { + try + { + OnSlideShowBegin(_pptSlideShowWindow); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"手动触发SlideShowBegin失败: {ex.Message}", LogHelper.LogType.Warning); + } + } + } + } + } + + PPTROTConnectionHelper.SafeReleaseComObject(slideShowWindows); + } + else + { + PPTROTConnectionHelper.SafeReleaseComObject(slideShowWindows); + } + } + catch (COMException ex) when ((uint)ex.ErrorCode == 0x8001010A) + { + LogHelper.WriteLogToFile("PowerPoint 忙,稍后重试", LogHelper.LogType.Trace); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"发现窗口失败: {ex.Message}", LogHelper.LogType.Warning); + } + finally + { + PPTROTConnectionHelper.SafeReleaseComObject(activePresentation); + activePresentation = null; + + if (!PPTROTConnectionHelper.AreComObjectsEqual(_pptSlideShowWindow, slideShowWindow)) + { + PPTROTConnectionHelper.SafeReleaseComObject(slideShowWindow); + slideShowWindow = null; + } + } + + if (isSlideShowActive) + { + if ((DateTime.Now - _updateTime).TotalMilliseconds > 3000 || _forcePolling) + { + + try + { + slideShowWindow = _pptActivePresentation.SlideShowWindow; + + if (slideShowWindow != null) + { + tempTotalPage = GetTotalSlideIndex(_pptActivePresentation); + } + else + { + tempTotalPage = -1; + } + } + catch (Exception ex) + { + tempTotalPage = -1; + LogHelper.WriteLogToFile($"获取总页数失败: {ex.Message}", LogHelper.LogType.Warning); + } + finally + { + PPTROTConnectionHelper.SafeReleaseComObject(slideShowWindow); + slideShowWindow = null; + } + + if (tempTotalPage == -1) + { + _lastPolledSlideNumber = -1; + _polling = 0; + } + else + { + try + { + int currentPage = GetCurrentSlideIndex(_pptSlideShowWindow); + int lastPage = _lastPolledSlideNumber; + + if (currentPage >= GetTotalSlideIndex(_pptActivePresentation)) _polling = 1; + else _polling = 0; + + if (lastPage != -1 && currentPage != lastPage) + { + try + { + SlideShowNextSlide?.Invoke(_pptSlideShowWindow); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"触发轮询模式幻灯片切换事件失败: {ex.Message}", LogHelper.LogType.Warning); + } + } + + _lastPolledSlideNumber = currentPage; + } + catch (Exception ex) + { + _lastPolledSlideNumber = -1; + _polling = 1; + LogHelper.WriteLogToFile($"获取当前页数失败: {ex}", LogHelper.LogType.Warning); + } + } + + _updateTime = DateTime.Now; + } + + if (_polling != 0) + { + try + { + int currentPage = GetCurrentSlideIndex(_pptSlideShowWindow); + + if (_lastPolledSlideNumber != -1 && currentPage != _lastPolledSlideNumber) + { + try + { + SlideShowNextSlide?.Invoke(_pptSlideShowWindow); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"触发轮询模式幻灯片切换事件失败: {ex.Message}", LogHelper.LogType.Warning); + } + } + + _lastPolledSlideNumber = currentPage; + UpdateCurrentPresentationInfo(); + _polling = 2; + } + catch + { + _lastPolledSlideNumber = -1; + } + } + } + else + { + if (_lastSlideShowState) + { + LogHelper.WriteLogToFile("轮询检测到放映已结束", LogHelper.LogType.Trace); + + PPTROTConnectionHelper.SafeReleaseComObject(_pptSlideShowWindow); + _pptSlideShowWindow = null; + _lastPolledSlideNumber = -1; + _polling = 1; + SlidesCount = 0; + + _lastSlideShowState = false; + SlideShowStateChanged?.Invoke(false); + + if (_pptActivePresentation != null) + { + try + { + OnSlideShowEnd(_pptActivePresentation); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"触发SlideShowEnd事件失败: {ex.Message}", LogHelper.LogType.Warning); + } + } + } + else + { + _lastPolledSlideNumber = -1; + SlidesCount = 0; + } + } + } + else + { + _lastPolledSlideNumber = -1; + SlidesCount = 0; + } + + if (_shouldStop || _isModuleUnloading) + { + LogHelper.WriteLogToFile("收到停止信号,退出循环", LogHelper.LogType.Trace); + break; + } + + Thread.Sleep(500); + } + } + catch (InvalidComObjectException ex) + { + LogHelper.WriteLogToFile($"PptComService异常: COM对象已失效 - {ex.Message}", LogHelper.LogType.Error); + DisconnectFromPPT(); + } + catch (COMException comEx) + { + var hr = (uint)comEx.HResult; + LogHelper.WriteLogToFile($"PptComService异常: COM异常 (HR: 0x{hr:X8}) - {comEx.Message}", LogHelper.LogType.Error); + DisconnectFromPPT(); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"PptComService异常: {ex.Message}", LogHelper.LogType.Error); + DisconnectFromPPT(); } } - /// - /// 使用 ROT 检查并在必要时建立、切换或断开与 PowerPoint 的 COM 连接。 - /// - /// - /// 当管理器已释放或模块正在卸载时不执行任何操作。此方法在内部加锁以防止并发切换;会通过 ROT 获取当前可用的 PowerPoint 实例并: - /// - 若尚未连接则建立连接; - /// - 若当前无可用实例则断开现有连接; - /// - 若检测到已连接的实例发生变化则先断开再重新连接。 - /// 遇到异常时会记录错误日志,并在存在活动连接时尝试断开以保证状态一致性。 - /// - private void CheckAndConnectToPPTViaRot() + private void CheckAndConnectToPPT() { - if (_disposed || _isModuleUnloading) return; - + if (_isModuleUnloading) return; + lock (_lockObject) { try { if (_isModuleUnloading) return; + + object bestApp = PPTROTConnectionHelper.GetAnyActivePowerPoint(PPTApplication, out int bestPriority, out int targetPriority); + bool needRebind = false; - if (_pptApplication != null && !IsConnected) - { - DisconnectFromPPT(); - return; - } + LogHelper.WriteLogToFile($"ROT扫描结果: now={targetPriority}, best={bestPriority}, bestApp={(bestApp != null ? "found" : "null")}", LogHelper.LogType.Trace); - // 使用 ROT 获取当前最佳 PPT 实例 - var bestApp = PPTROTConnectionHelper.TryConnectViaROT(IsSupportWPS); - - if (bestApp != null && _pptApplication == null) + if (PPTApplication == null && bestApp != null) { - // 从未连接 -> 连接 - ConnectToPPT(bestApp); + needRebind = true; } - else if (bestApp == null && _pptApplication != null) + else if (PPTApplication != null && bestApp != null && bestPriority > targetPriority) { - // 原来有,现在没有 -> 断开 - DisconnectFromPPT(); - } - else if (bestApp != null && _pptApplication != null) - { - // 已连接,检查是否切换到了另一份 PPT - if (!PPTROTConnectionHelper.AreComObjectsEqual(_pptApplication, bestApp)) + if (!PPTROTConnectionHelper.AreComObjectsEqual(PPTApplication, bestApp)) { - DisconnectFromPPT(); - ConnectToPPT(bestApp); + needRebind = true; } - else + } + + if (needRebind) + { + LogHelper.WriteLogToFile($"需要重新绑定: bestPriority={bestPriority}, targetPriority={targetPriority}", LogHelper.LogType.Trace); + + bool wait = (PPTApplication != null); + DisconnectFromPPT(); + + if (bestApp != null) + { + if (wait) Thread.Sleep(1000); + + try + { + LogHelper.WriteLogToFile("使用dynamic类型连接", LogHelper.LogType.Trace); + PPTApplication = bestApp; + ConnectToPPT(null); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"连接失败: {ex.Message}", LogHelper.LogType.Warning); + PPTROTConnectionHelper.SafeReleaseComObject(bestApp); + } + } + } + else + { + if (bestApp != null && (PPTApplication == null || !PPTROTConnectionHelper.AreComObjectsEqual(PPTApplication, bestApp))) { PPTROTConnectionHelper.SafeReleaseComObject(bestApp); + bestApp = null; } } + + if (PPTApplication != null) + { + // 即使 _pptActivePresentation 为 null,也要检查(可能在轮询模式下需要初始化) + if (_pptActivePresentation == null) + { + try + { + _pptActivePresentation = PPTApplication.ActivePresentation; + if (_pptActivePresentation != null) + { + LogHelper.WriteLogToFile("轮询模式:初始化_pptActivePresentation", LogHelper.LogType.Trace); + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"轮询模式:无法获取ActivePresentation: {ex.Message}", LogHelper.LogType.Trace); + } + } + + if (_pptActivePresentation != null) + { + CheckPresentationAndSlideShowState(); + } + } + } + catch (InvalidComObjectException) + { + // COM对象已失效,断开连接 + LogHelper.WriteLogToFile("检测到COM对象失效,断开连接", LogHelper.LogType.Trace); + DisconnectFromPPT(); + } + catch (COMException comEx) + { + // COM异常,记录并断开连接 + var hr = (uint)comEx.HResult; + LogHelper.WriteLogToFile($"PPT连接检查COM异常: {comEx.Message} (HR: 0x{hr:X8})", LogHelper.LogType.Warning); + DisconnectFromPPT(); } catch (Exception ex) { - LogHelper.WriteLogToFile($"[ROT] PPT 连接检查异常: {ex}", LogHelper.LogType.Error); - if (_pptApplication != null) + LogHelper.WriteLogToFile($"PptComService异常: {ex.Message}", LogHelper.LogType.Error); + if (PPTApplication != null) { DisconnectFromPPT(); } @@ -338,6 +783,290 @@ namespace Ink_Canvas.Helpers } } + private void CheckPresentationAndSlideShowState() + { + try + { + // 检查COM对象是否仍然有效 + if (PPTApplication == null || !System.Runtime.InteropServices.Marshal.IsComObject(PPTApplication)) + { + return; + } + + try + { + var _ = System.Runtime.InteropServices.Marshal.GetIUnknownForObject(PPTApplication); + System.Runtime.InteropServices.Marshal.Release(_); + } + catch (System.Runtime.InteropServices.InvalidComObjectException) + { + // COM对象已失效 + DisconnectFromPPT(); + return; + } + + dynamic activePresentation = null; + dynamic slideShowWindow = null; + + try + { + activePresentation = PPTApplication.ActivePresentation; + + if (activePresentation != null && _pptActivePresentation != null && !PPTROTConnectionHelper.AreComObjectsEqual(_pptActivePresentation, activePresentation)) + { + LogHelper.WriteLogToFile("检测到演示文稿切换,断开连接", LogHelper.LogType.Trace); + DisconnectFromPPT(); + return; + } + } + catch (System.Runtime.InteropServices.InvalidComObjectException) + { + // COM对象已失效 + LogHelper.WriteLogToFile("检测到COM对象失效,断开连接", LogHelper.LogType.Trace); + DisconnectFromPPT(); + return; + } + catch (COMException ex) when ((uint)ex.HResult == 0x8001010A) + { + LogHelper.WriteLogToFile("PowerPoint 忙,稍后重试", LogHelper.LogType.Trace); + return; + } + catch (COMException comEx) + { + // COM异常,对象可能已失效 + var hr = (uint)comEx.HResult; + LogHelper.WriteLogToFile($"检查演示文稿状态COM异常: {comEx.Message} (HR: 0x{hr:X8})", LogHelper.LogType.Warning); + DisconnectFromPPT(); + return; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"检查演示文稿状态失败: {ex.Message},继续使用轮询模式", LogHelper.LogType.Warning); + activePresentation = null; + } + finally + { + if (activePresentation != null && (_pptActivePresentation == null || !PPTROTConnectionHelper.AreComObjectsEqual(_pptActivePresentation, activePresentation))) + { + SafeReleaseComObject(activePresentation); + } + } + + bool isSlideShowActive = false; + try + { + if (activePresentation == null) + { + try + { + activePresentation = PPTApplication.ActivePresentation; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"访问ActivePresentation失败: {ex.Message},继续使用轮询模式", LogHelper.LogType.Warning); + } + } + + if (activePresentation != null) + { + try + { + dynamic slideShowWindows = PPTApplication.SlideShowWindows; + if (slideShowWindows != null && slideShowWindows.Count > 0) + { + isSlideShowActive = true; + LogHelper.WriteLogToFile($"检测到放映模式,轮询模式={_forcePolling}", LogHelper.LogType.Trace); + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"检查SlideShowWindows失败: {ex.Message}", LogHelper.LogType.Trace); + } + } + + if (isSlideShowActive && activePresentation != null) + { + dynamic pres = activePresentation; + slideShowWindow = pres.SlideShowWindow; + if (_pptSlideShowWindow == null || !PPTROTConnectionHelper.IsValidSlideShowWindow(_pptSlideShowWindow)) + { + if (!PPTROTConnectionHelper.AreComObjectsEqual(_pptSlideShowWindow, slideShowWindow)) + { + SafeReleaseComObject(_pptSlideShowWindow); + _pptSlideShowWindow = slideShowWindow; + LogHelper.WriteLogToFile("发现窗口,成功设置 slideshowwindow", LogHelper.LogType.Trace); + } + } + } + } + catch (COMException ex) when ((uint)ex.HResult == 0x8001010A) + { + LogHelper.WriteLogToFile("PowerPoint 忙,稍后重试", LogHelper.LogType.Trace); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"发现窗口失败: {ex}", LogHelper.LogType.Warning); + } + finally + { + if (activePresentation != null && (_pptActivePresentation == null || !PPTROTConnectionHelper.AreComObjectsEqual(_pptActivePresentation, activePresentation))) + { + SafeReleaseComObject(activePresentation); + } + if (slideShowWindow != null && !PPTROTConnectionHelper.AreComObjectsEqual(_pptSlideShowWindow, slideShowWindow)) + { + SafeReleaseComObject(slideShowWindow); + } + } + + if (isSlideShowActive) + { + if ((DateTime.Now - _updateTime).TotalMilliseconds > 3000 || _forcePolling) + { + LogHelper.WriteLogToFile($"轮询", LogHelper.LogType.Trace); + + try + { + dynamic pres = _pptActivePresentation; + if (pres == null) + { + LogHelper.WriteLogToFile("_pptActivePresentation为null,无法轮询", LogHelper.LogType.Warning); + } + else + { + slideShowWindow = pres.SlideShowWindow; + + int tempTotalPage = -1; + if (slideShowWindow != null) + { + tempTotalPage = GetTotalSlideIndex(_pptActivePresentation); + } + + if (tempTotalPage == -1) + { + SlidesCount = 0; + _polling = 0; + } + else + { + try + { + if (_pptSlideShowWindow == null) + { + LogHelper.WriteLogToFile("轮询: _pptSlideShowWindow为null", LogHelper.LogType.Warning); + } + else + { + int currentPage = GetCurrentSlideIndex(_pptSlideShowWindow); + SlidesCount = tempTotalPage; + if (currentPage >= tempTotalPage) _polling = 1; + else _polling = 0; + + if (_lastPolledSlideNumber != -1 && currentPage != _lastPolledSlideNumber) + { + try + { + LogHelper.WriteLogToFile($"轮询模式检测到页码变化: {_lastPolledSlideNumber} -> {currentPage},触发事件", LogHelper.LogType.Trace); + SlideShowNextSlide?.Invoke(_pptSlideShowWindow); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"触发轮询模式幻灯片切换事件失败: {ex.Message}", LogHelper.LogType.Warning); + } + } + + _lastPolledSlideNumber = currentPage; + } + } + catch (Exception ex) + { + SlidesCount = 0; + _polling = 1; + LogHelper.WriteLogToFile($"获取当前页数失败: {ex}", LogHelper.LogType.Warning); + } + } + } + } + catch (Exception ex) + { + SlidesCount = 0; + LogHelper.WriteLogToFile($"获取总页数失败: {ex.Message}", LogHelper.LogType.Warning); + } + finally + { + SafeReleaseComObject(slideShowWindow); + } + + _updateTime = DateTime.Now; + } + + if (_polling != 0) + { + try + { + int currentPage = GetCurrentSlideIndex(_pptSlideShowWindow); + + if (_lastPolledSlideNumber != -1 && currentPage != _lastPolledSlideNumber) + { + try + { + LogHelper.WriteLogToFile($"轮询模式检测到页码变化: {_lastPolledSlideNumber} -> {currentPage},触发事件", LogHelper.LogType.Trace); + SlideShowNextSlide?.Invoke(_pptSlideShowWindow); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"触发轮询模式幻灯片切换事件失败: {ex.Message}", LogHelper.LogType.Warning); + } + } + + _lastPolledSlideNumber = currentPage; + UpdateCurrentPresentationInfo(); + _polling = 2; + } + catch + { + } + } + } + else + { + if (_lastSlideShowState) + { + LogHelper.WriteLogToFile("轮询检测到放映已结束", LogHelper.LogType.Trace); + + PPTROTConnectionHelper.SafeReleaseComObject(_pptSlideShowWindow); + _pptSlideShowWindow = null; + _lastPolledSlideNumber = -1; + _polling = 1; + SlidesCount = 0; + + _lastSlideShowState = false; + SlideShowStateChanged?.Invoke(false); + + if (_pptActivePresentation != null) + { + try + { + OnSlideShowEnd(_pptActivePresentation); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"触发SlideShowEnd事件失败: {ex.Message}", LogHelper.LogType.Warning); + } + } + } + else + { + SlidesCount = 0; + } + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"检查演示文稿和放映状态失败: {ex}", LogHelper.LogType.Warning); + } + } + private void CheckSlideShowState() { try @@ -347,304 +1076,890 @@ namespace Ink_Canvas.Helpers var currentSlideShowState = IsInSlideShow; if (currentSlideShowState != _lastSlideShowState) { - var wasInSlideShow = _lastSlideShowState; _lastSlideShowState = currentSlideShowState; SlideShowStateChanged?.Invoke(currentSlideShowState); - if (currentSlideShowState && !wasInSlideShow) - { - try - { - var app = _pptApplication as Microsoft.Office.Interop.PowerPoint.Application; - if (app != null && app.SlideShowWindows.Count >= 1) - { - var wn = app.SlideShowWindows[1]; - if (wn != null) - { - OnSlideShowBeginInternal(wn); - } - } - } - catch (COMException) - { - } - } - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"[ROT] 检查 PPT 放映状态异常: {ex}", LogHelper.LogType.Error); - } - } - - /// - /// 将指定的 PowerPoint COM 应用对象作为当前连接并在 UI 线程上注册必要的演示文稿与幻灯片放映事件,触发连接通知并在已处于放映时尝试触发一次放映开始回调。 - /// - /// 要绑定的 PowerPoint COM 应用对象(通常来自 ROT)。 - /// - /// 如果事件注册或后续处理失败,会记录错误并在异常路径上清除内部引用(将内部应用对象置空)。方法会触发 PPTConnectionChanged(true) 并记录连接成功的事件日志;若检测到当前正处于幻灯片放映,则尝试调用一次放映开始的内部处理以同步状态。异常信息会写入日志,但方法不会向外抛出未捕获的异常(内部会捕获并记录错误后清理状态)。 - /// - private void ConnectToPPT(object appObj) - { - try - { - _pptApplication = appObj; - - // 在 UI 线程上注册事件(无打开文档时可能失败,不因此断开连接) - Application.Current?.Dispatcher?.Invoke(() => - { - try - { - var app = _pptApplication as Microsoft.Office.Interop.PowerPoint.Application; - if (app == null) return; - - app.PresentationOpen += OnPresentationOpenInternal; - app.PresentationClose += OnPresentationCloseInternal; - app.SlideShowBegin += OnSlideShowBeginInternal; - app.SlideShowNextSlide += OnSlideShowNextSlideInternal; - app.SlideShowEnd += OnSlideShowEndInternal; - LogHelper.WriteLogToFile("[ROT] PPT 事件注册成功", LogHelper.LogType.Trace); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"[ROT] PPT 事件注册失败(将依赖轮询检测放映状态): {ex}", LogHelper.LogType.Warning); - } - }, DispatcherPriority.Normal); - - PPTConnectionChanged?.Invoke(true); - LogHelper.WriteLogToFile("[ROT] 成功连接到 PPT 应用程序", LogHelper.LogType.Event); - - // 如果已在放映中,根据当前状态触发一次开始事件 - if (IsInSlideShow) - { - try - { - var app = _pptApplication as Microsoft.Office.Interop.PowerPoint.Application; - if (app != null && app.SlideShowWindows.Count > 0) - { - OnSlideShowBeginInternal(app.SlideShowWindows[1]); - } - } - catch (COMException) + if (!currentSlideShowState) { } } } catch (Exception ex) { - LogHelper.WriteLogToFile($"[ROT] 连接 PPT 应用程序失败: {ex}", LogHelper.LogType.Error); - _pptApplication = null; + LogHelper.WriteLogToFile($"检查PPT放映状态异常: {ex}", LogHelper.LogType.Error); } } - private void DisconnectFromPPT(bool restartMonitoring = true) + private Microsoft.Office.Interop.PowerPoint.Application TryConnectToPowerPoint() { - object appToRelease = null; try { - PPTConnectionChanged?.Invoke(false); - LogHelper.WriteLogToFile("[ROT] 准备断开 PPT 连接,先卸载监控模块", LogHelper.LogType.Event); + var pptApp = PPTROTConnectionHelper.TryConnectViaROT(IsSupportWPS); + return pptApp; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"ROT连接PowerPoint异常: {ex}", LogHelper.LogType.Error); + return null; + } + } - lock (_lockObject) - { - _isModuleUnloading = true; - _unifiedRotTimer?.Stop(); - appToRelease = _pptApplication; - _pptApplication = null; + private Microsoft.Office.Interop.PowerPoint.Application TryConnectToWPS() + { + try + { + var wpsApp = PPTROTConnectionHelper.TryConnectViaROT(true); + return wpsApp; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"ROT连接WPS异常: {ex}", LogHelper.LogType.Error); + return null; + } + } + + private void ConnectToPPT(object pptApp) + { + try + { + if (pptApp != null) + { + PPTApplication = pptApp; } - if (appToRelease != null) + try { + dynamic pptAppDynamic = PPTApplication; try { - if (Marshal.IsComObject(appToRelease)) - { - Application.Current?.Dispatcher?.Invoke(() => - { - try - { - var app = appToRelease as Microsoft.Office.Interop.PowerPoint.Application; - if (app != null) - { - app.PresentationOpen -= OnPresentationOpenInternal; - app.PresentationClose -= OnPresentationCloseInternal; - app.SlideShowBegin -= OnSlideShowBeginInternal; - app.SlideShowNextSlide -= OnSlideShowNextSlideInternal; - app.SlideShowEnd -= OnSlideShowEndInternal; - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"[ROT] 取消 PPT 事件注册异常: {ex}", LogHelper.LogType.Warning); - } - }, DispatcherPriority.Normal); - } + _pptActivePresentation = pptAppDynamic.ActivePresentation; + _updateTime = DateTime.Now; + _lastPolledSlideNumber = -1; } catch (Exception ex) { - LogHelper.WriteLogToFile($"[ROT] 取消 PPT 事件注册失败: {ex}", LogHelper.LogType.Warning); + LogHelper.WriteLogToFile($"访问ActivePresentation失败: {ex.Message},继续使用轮询模式", LogHelper.LogType.Warning); + _pptActivePresentation = null; + _updateTime = DateTime.Now; + _lastPolledSlideNumber = -1; } - if (Marshal.IsComObject(appToRelease)) + int tempTotalPage = -1; + if (_pptActivePresentation != null) { try { - Marshal.FinalReleaseComObject(appToRelease); + _pptSlideShowWindow = _pptActivePresentation.SlideShowWindow; + tempTotalPage = GetTotalSlideIndex(_pptActivePresentation); } catch { - try - { - int refCount = Marshal.ReleaseComObject(appToRelease); - while (refCount > 0) - { - refCount = Marshal.ReleaseComObject(appToRelease); - } - } - catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); } + tempTotalPage = -1; } } - } + else + { + tempTotalPage = -1; + } - GC.Collect(); - GC.WaitForPendingFinalizers(); - GC.Collect(); + if (tempTotalPage == -1) + { + SlidesCount = 0; + _polling = 0; + } + else + { + try + { + if (_pptSlideShowWindow != null) + { + int currentPage = GetCurrentSlideIndex(_pptSlideShowWindow); + SlidesCount = tempTotalPage; + if (currentPage >= tempTotalPage) _polling = 1; + else _polling = 0; + _lastPolledSlideNumber = currentPage; + } + else + { + SlidesCount = tempTotalPage; + _polling = 0; + _lastPolledSlideNumber = -1; + } + } + catch + { + SlidesCount = 0; + _polling = 1; + } + } - LogHelper.WriteLogToFile("[ROT] 已断开 PPT 连接并尝试释放所有 COM 对象", LogHelper.LogType.Event); - - System.Threading.ThreadPool.QueueUserWorkItem(_ => - { try { - System.Threading.Thread.Sleep(300); - - GC.Collect(); - GC.WaitForPendingFinalizers(); - GC.Collect(); - - System.Threading.Thread.Sleep(200); - - lock (_lockObject) + if (PPTApplication != null) { - _isModuleUnloading = false; - if (restartMonitoring && !_disposed) - _unifiedRotTimer?.Start(); + try + { + Microsoft.Office.Interop.PowerPoint.Application pptAppForEvents = PPTApplication as Microsoft.Office.Interop.PowerPoint.Application; + + if (pptAppForEvents != null) + { + pptAppForEvents.SlideShowNextSlide += new EApplication_SlideShowNextSlideEventHandler(OnSlideShowNextSlide); + pptAppForEvents.SlideShowBegin += new EApplication_SlideShowBeginEventHandler(OnSlideShowBegin); + pptAppForEvents.SlideShowEnd += new EApplication_SlideShowEndEventHandler(OnSlideShowEndForComEvent); + + try + { + pptAppForEvents.PresentationBeforeClose += new EApplication_PresentationBeforeCloseEventHandler(OnPresentationBeforeClose); + } + catch + { + LogHelper.WriteLogToFile("无法注册PresentationBeforeClose事件", LogHelper.LogType.Warning); + } + + _bindingEvents = true; + + LogHelper.WriteLogToFile("PPT事件注册成功", LogHelper.LogType.Trace); + } + else + { + _bindingEvents = false; + _forcePolling = true; + LogHelper.WriteLogToFile("无法转换为强类型Application,使用轮询模式", LogHelper.LogType.Trace); + } + } + catch (Exception ex) + { + _bindingEvents = false; + _forcePolling = true; + LogHelper.WriteLogToFile($"事件注册失败: {ex.Message},使用轮询模式", LogHelper.LogType.Trace); + } + } + else + { + _bindingEvents = false; + _forcePolling = true; } } catch (Exception ex) { - LogHelper.WriteLogToFile($"[ROT] 重新进入监控状态失败: {ex}", LogHelper.LogType.Error); - lock (_lockObject) + _bindingEvents = false; + _forcePolling = true; + LogHelper.WriteLogToFile($"无法注册事件: {ex.Message}", LogHelper.LogType.Warning); + } + + _bindingEvents = false; + _forcePolling = true; + + if (_pptActivePresentation != null) + { + UpdateCurrentPresentationInfo(); + } + + PPTConnectionChanged?.Invoke(true); + + try + { + dynamic pptAppDynamic2 = PPTApplication; + LogHelper.WriteLogToFile($"成功绑定! {pptAppDynamic2.Name}", LogHelper.LogType.Event); + } + catch + { + LogHelper.WriteLogToFile("成功绑定!", LogHelper.LogType.Event); + } + + TryRaiseSlideShowBeginOnConnect(); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"ConnectToPPT内部异常: {ex.Message}", LogHelper.LogType.Warning); + if (_pptActivePresentation == null && PPTApplication == null) + { + DisconnectFromPPT(); + } + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"连接PPT应用程序失败: {ex}", LogHelper.LogType.Error); + PPTApplication = null; + } + } + + private void TryRaiseSlideShowBeginOnConnect() + { + try + { + if (!IsConnected || !IsInSlideShow || PPTApplication == null) + return; + + if (!Marshal.IsComObject(PPTApplication)) + return; + + dynamic app = PPTApplication; + dynamic slideShowWindows = app.SlideShowWindows; + if (slideShowWindows == null || slideShowWindows.Count == 0) + return; + + dynamic slideShowWindow = slideShowWindows[1]; + if (slideShowWindow == null) + return; + + SlideShowBegin?.Invoke(slideShowWindow); + LogHelper.WriteLogToFile("检测到放映已在进行中,热重载", LogHelper.LogType.Trace); + } + catch (COMException comEx) + { + var hr = (uint)comEx.HResult; + LogHelper.WriteLogToFile($"TryRaiseSlideShowBeginOnConnect COM 异常: {comEx.Message} (HR: 0x{hr:X8})", LogHelper.LogType.Trace); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"TryRaiseSlideShowBeginOnConnect 异常: {ex}", LogHelper.LogType.Trace); + } + } + + private void UnbindEvents() + { + try + { + if (_bindingEvents && PPTApplication != null) + { + try + { + Microsoft.Office.Interop.PowerPoint.Application app = PPTApplication as Microsoft.Office.Interop.PowerPoint.Application; + + if (app != null) { - _isModuleUnloading = false; + app.SlideShowNextSlide -= new EApplication_SlideShowNextSlideEventHandler(OnSlideShowNextSlide); + app.SlideShowBegin -= new EApplication_SlideShowBeginEventHandler(OnSlideShowBegin); + app.SlideShowEnd -= new EApplication_SlideShowEndEventHandler(OnSlideShowEndForComEvent); + app.PresentationBeforeClose -= new EApplication_PresentationBeforeCloseEventHandler(OnPresentationBeforeClose); } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"取消PPT事件注册失败: {ex.Message}", LogHelper.LogType.Trace); + } + + _bindingEvents = false; + } + } + catch { } + } + + private void DisconnectFromPPT() + { + if (PPTApplication == null && _pptActivePresentation == null && _pptSlideShowWindow == null && + CurrentPresentation == null && CurrentSlides == null && CurrentSlide == null) + { + return; + } + + _isModuleUnloading = true; + + try + { + UnbindEvents(); + + if (_pptActivePresentation != null) + { + try + { + PresentationClose?.Invoke(_pptActivePresentation); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"触发PresentationClose事件失败: {ex.Message}", LogHelper.LogType.Warning); + } + } + + SafeReleaseComObject(_pptSlideShowWindow, "_pptSlideShowWindow"); + SafeReleaseComObject(_pptActivePresentation, "_pptActivePresentation"); + SafeReleaseComObject(CurrentSlide, "CurrentSlide"); + SafeReleaseComObject(CurrentSlides, "CurrentSlides"); + SafeReleaseComObject(CurrentPresentation, "CurrentPresentation"); + + if (PPTApplication != null) + { + try + { + if (Marshal.IsComObject(PPTApplication)) + { + try + { + var unk = Marshal.GetIUnknownForObject(PPTApplication); + Marshal.Release(unk); + + try + { + Marshal.FinalReleaseComObject(PPTApplication); + } + catch + { + try + { + int refCount = Marshal.ReleaseComObject(PPTApplication); + while (refCount > 0) + { + refCount = Marshal.ReleaseComObject(PPTApplication); + } + } + catch + { + } + } + } + catch (InvalidComObjectException) + { + LogHelper.WriteLogToFile("PPTApplication COM对象已失效,跳过释放", LogHelper.LogType.Trace); + } + } + } + catch (InvalidComObjectException) + { + LogHelper.WriteLogToFile("PPTApplication COM对象已失效,跳过释放", LogHelper.LogType.Trace); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"释放PPTApplication时发生异常: {ex.Message}", LogHelper.LogType.Warning); + } + } + + PPTApplication = null; + _pptActivePresentation = null; + _pptSlideShowWindow = null; + CurrentPresentation = null; + CurrentSlides = null; + CurrentSlide = null; + SlidesCount = 0; + _polling = 0; + _forcePolling = true; + _bindingEvents = false; + + PPTConnectionChanged?.Invoke(false); + + LogHelper.WriteLogToFile("已断开PPT连接,并显式释放所有COM对象", LogHelper.LogType.Event); + + ThreadPool.QueueUserWorkItem(_ => + { + try + { + Thread.Sleep(2000); + _isModuleUnloading = false; + + LogHelper.WriteLogToFile("PPT联动模块已允许重新连接", LogHelper.LogType.Trace); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"重新启用PPT联动模块失败: {ex}", LogHelper.LogType.Error); + _isModuleUnloading = false; } }); } catch (Exception ex) { - LogHelper.WriteLogToFile($"[ROT] 断开 PPT 连接失败: {ex}", LogHelper.LogType.Error); - lock (_lockObject) + LogHelper.WriteLogToFile($"断开PPT连接失败: {ex}", LogHelper.LogType.Error); + _isModuleUnloading = false; + } + } + + /// + /// 安全释放COM对象 + /// + private void SafeReleaseComObject(object comObject) + { + try + { + if (comObject != null && Marshal.IsComObject(comObject)) { - _isModuleUnloading = false; + Marshal.ReleaseComObject(comObject); } } + catch { } } - #endregion - #region PPT Event Handlers (internal, strong-typed) - private void OnPresentationOpenInternal(Presentation pres) + private void SafeReleaseComObject(object comObject, string objectName) { try { - PresentationOpen?.Invoke(pres); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"[ROT] 处理演示文稿打开事件失败: {ex}", LogHelper.LogType.Error); - } - } + if (comObject != null) + { + // 检查是否为有效的COM对象 + if (!Marshal.IsComObject(comObject)) + { + return; // 不是COM对象,无需释放 + } - private void OnPresentationCloseInternal(Presentation pres) - { - try + // 检查COM对象是否仍然有效 + try + { + // 尝试访问对象以验证其有效性 + var _ = Marshal.GetIUnknownForObject(comObject); + Marshal.Release(_); + } + catch (InvalidComObjectException) + { + // COM对象已失效,直接返回 + LogHelper.WriteLogToFile($"COM对象 {objectName} 已失效,跳过释放", LogHelper.LogType.Trace); + return; + } + + int refCount = Marshal.ReleaseComObject(comObject); + LogHelper.WriteLogToFile($"已释放COM对象 {objectName},引用计数: {refCount}", LogHelper.LogType.Trace); + } + } + catch (InvalidComObjectException) { - PresentationClose?.Invoke(pres); + // COM对象已失效,这是正常的,无需记录错误 + LogHelper.WriteLogToFile($"COM对象 {objectName} 已失效,跳过释放", LogHelper.LogType.Trace); } catch (COMException comEx) { var hr = (uint)comEx.HResult; - if (hr == 0x8001010E || hr == 0x80004005 || hr == 0x800706BA || hr == 0x800706BE || hr == 0x80048010) + LogHelper.WriteLogToFile($"释放COM对象 {objectName} 时COM异常: {comEx.Message} (HR: 0x{hr:X8})", LogHelper.LogType.Warning); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"释放COM对象 {objectName} 时发生异常: {ex}", LogHelper.LogType.Warning); + } + } + + private void UpdateCurrentPresentationInfo() + { + object activeWindow = null; + object view = null; + object selection = null; + object slideRange = null; + + try + { + if (PPTApplication != null && Marshal.IsComObject(PPTApplication)) { - // COM 对象已失效,静默忽略 + try + { + if (_pptActivePresentation != null) + { + try + { + // 检查COM对象是否仍然有效 + if (!Marshal.IsComObject(_pptActivePresentation)) + { + CurrentPresentation = null; + CurrentSlides = null; + } + else + { + try + { + var _ = Marshal.GetIUnknownForObject(_pptActivePresentation); + Marshal.Release(_); + } + catch (InvalidComObjectException) + { + CurrentPresentation = null; + CurrentSlides = null; + return; + } + + dynamic pres = _pptActivePresentation; + CurrentPresentation = pres; + CurrentSlides = pres.Slides; + } + } + catch (InvalidComObjectException) + { + LogHelper.WriteLogToFile($"访问演示文稿属性失败: COM对象已失效", LogHelper.LogType.Warning); + CurrentPresentation = null; + CurrentSlides = null; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"访问演示文稿属性失败: {ex.Message}", LogHelper.LogType.Warning); + CurrentPresentation = null; + CurrentSlides = null; + } + + if (CurrentSlides != null) + { + try + { + var slideCount = CurrentSlides.Count; + if (slideCount > 0) + { + SlidesCount = slideCount; + } + else + { + SlidesCount = 0; + LogHelper.WriteLogToFile("PPT演示文稿页数为0,可能为空演示文稿", LogHelper.LogType.Warning); + } + } + catch (COMException comEx) + { + var hr = (uint)comEx.HResult; + SlidesCount = 0; + LogHelper.WriteLogToFile($"读取PPT页数失败: {comEx.Message} (HR: 0x{hr:X8})", LogHelper.LogType.Warning); + } + + try + { + if (IsInSlideShow && _pptSlideShowWindow != null) + { + try + { + dynamic ssw = _pptSlideShowWindow; + view = ssw.View; + if (view != null) + { + dynamic viewObj = view; + CurrentSlide = viewObj.Slide; + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"获取SlideShowWindow的Slide失败: {ex.Message}", LogHelper.LogType.Trace); + } + } + else + { + activeWindow = PPTApplication.ActiveWindow; + if (activeWindow != null) + { + dynamic aw = activeWindow; + selection = aw.Selection; + if (selection != null) + { + dynamic sel = selection; + slideRange = sel.SlideRange; + if (slideRange != null) + { + dynamic sr = slideRange; + int slideNumber = sr.SlideNumber; + if (slideNumber > 0 && slideNumber <= SlidesCount) + { + CurrentSlide = CurrentSlides[slideNumber]; + } + } + } + } + + if (CurrentSlide == null && SlidesCount > 0) + { + CurrentSlide = CurrentSlides[1]; + } + } + } + catch (COMException comEx) + { + var hr = (uint)comEx.HResult; + if (hr != 0x8001010E && hr != 0x80004005) + { + LogHelper.WriteLogToFile($"获取当前幻灯片失败: {comEx.Message}", LogHelper.LogType.Warning); + } + + if (SlidesCount > 0) + { + CurrentSlide = CurrentSlides[1]; + } + } + } + else + { + CurrentPresentation = null; + CurrentSlides = null; + CurrentSlide = null; + SlidesCount = 0; + } + } + } + catch (COMException comEx) + { + var hr = (uint)comEx.HResult; + if (hr == 0x8001010E || hr == 0x80004005) + { + CurrentPresentation = null; + CurrentSlides = null; + CurrentSlide = null; + SlidesCount = 0; + } + else + { + LogHelper.WriteLogToFile($"访问活动演示文稿失败: {comEx}", LogHelper.LogType.Warning); + } + } + } + else + { + CurrentPresentation = null; + CurrentSlides = null; + CurrentSlide = null; + SlidesCount = 0; } } - catch (Exception) + catch (InvalidComObjectException) { - } - } - - private void OnSlideShowBeginInternal(SlideShowWindow wn) - { - try - { - SlideShowBegin?.Invoke(wn); + LogHelper.WriteLogToFile($"更新演示文稿信息失败: COM对象已失效", LogHelper.LogType.Warning); + CurrentPresentation = null; + CurrentSlides = null; + CurrentSlide = null; + SlidesCount = 0; } catch (Exception ex) { - LogHelper.WriteLogToFile($"[ROT] 处理放映开始事件失败: {ex}", LogHelper.LogType.Error); + LogHelper.WriteLogToFile($"更新演示文稿信息失败: {ex}", LogHelper.LogType.Error); + CurrentPresentation = null; + CurrentSlides = null; + CurrentSlide = null; + SlidesCount = 0; } - } - - private void OnSlideShowNextSlideInternal(SlideShowWindow wn) - { - try + finally { - SlideShowNextSlide?.Invoke(wn); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"[ROT] 处理放映翻页事件失败: {ex}", LogHelper.LogType.Error); - } - } - - private void OnSlideShowEndInternal(Presentation pres) - { - try - { - SlideShowEnd?.Invoke(pres); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"[ROT] 处理放映结束事件失败: {ex}", LogHelper.LogType.Error); + SafeReleaseComObject(slideRange); + SafeReleaseComObject(selection); + SafeReleaseComObject(view); + SafeReleaseComObject(activeWindow); } } #endregion - #region IPPTLinkManager Methods - public bool TryStartSlideShow() + #region Event Handlers + private void OnPresentationOpen(Presentation pres) { - object pres = null; try { - if (!IsConnected || _pptApplication == null) return false; - if (!Marshal.IsComObject(_pptApplication)) return false; + if (PPTApplication != null && pres != null) + { + try + { + _pptActivePresentation = PPTApplication.ActivePresentation; + _updateTime = DateTime.Now; + } + catch { } + } + + UpdateCurrentPresentationInfo(); + PresentationOpen?.Invoke(pres); + LogHelper.WriteLogToFile($"演示文稿已打开: {pres?.Name}", LogHelper.LogType.Event); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"处理演示文稿打开事件失败: {ex}", LogHelper.LogType.Error); + } + } + + private void OnPresentationBeforeClose(Presentation pres, ref bool cancel) + { + try + { + LogHelper.WriteLogToFile("PresentationBeforeClose事件触发", LogHelper.LogType.Trace); + + if (_bindingEvents && PPTApplication != null) + { + try + { + Microsoft.Office.Interop.PowerPoint.Application app = PPTApplication as Microsoft.Office.Interop.PowerPoint.Application; + + if (app != null) + { + app.SlideShowNextSlide -= new EApplication_SlideShowNextSlideEventHandler(OnSlideShowNextSlide); + app.SlideShowBegin -= new EApplication_SlideShowBeginEventHandler(OnSlideShowBegin); + app.SlideShowEnd -= new EApplication_SlideShowEndEventHandler(OnSlideShowEndForComEvent); + app.PresentationBeforeClose -= new EApplication_PresentationBeforeCloseEventHandler(OnPresentationBeforeClose); + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"取消PPT事件注册失败: {ex.Message}", LogHelper.LogType.Trace); + } + + _bindingEvents = false; + } + + DisconnectFromPPT(); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"处理演示文稿关闭前事件失败: {ex}", LogHelper.LogType.Error); + } + } + + private void OnSlideShowBegin(object wn) + { + try + { + _updateTime = DateTime.Now; + _pptSlideShowWindow = wn; + _lastPolledSlideNumber = -1; // 重置页码跟踪 - dynamic app = _pptApplication; try { - pres = app.ActivePresentation; + if (_pptActivePresentation != null) + { + int currentPage = GetCurrentSlideIndex(_pptSlideShowWindow); + int totalPage = GetTotalSlideIndex(_pptActivePresentation); + + if (currentPage >= totalPage) _polling = 1; + else _polling = 0; + + SlidesCount = totalPage; + _lastPolledSlideNumber = currentPage; // 初始化页码跟踪 + } } catch { - pres = null; + _polling = 1; + _lastPolledSlideNumber = -1; } - if (pres == null || !Marshal.IsComObject(pres)) return false; + UpdateCurrentPresentationInfo(); - dynamic dp = pres; - dp.SlideShowSettings.Run(); - return true; + if (!_lastSlideShowState) + { + _lastSlideShowState = true; + SlideShowStateChanged?.Invoke(true); + } + + SlideShowBegin?.Invoke(wn); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"处理幻灯片放映开始事件失败: {ex}", LogHelper.LogType.Error); + } + } + + private void OnSlideShowNextSlide(SlideShowWindow wn) + { + try + { + _updateTime = DateTime.Now; + + try + { + if (_pptActivePresentation != null && _pptSlideShowWindow != null) + { + int currentPage = GetCurrentSlideIndex(_pptSlideShowWindow); + int totalPage = GetTotalSlideIndex(_pptActivePresentation); + + if (currentPage >= totalPage) _polling = 1; + else _polling = 0; + + _lastPolledSlideNumber = currentPage; // 更新页码跟踪 + } + } + catch + { + _polling = 1; + } + + UpdateCurrentPresentationInfo(); + SlideShowNextSlide?.Invoke(wn); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"处理幻灯片切换事件失败: {ex}", LogHelper.LogType.Error); + } + } + + private void OnSlideShowEnd(object pres) + { + try + { + _updateTime = DateTime.Now; + SlidesCount = 0; + _lastPolledSlideNumber = -1; + _polling = 1; + + PPTROTConnectionHelper.SafeReleaseComObject(_pptSlideShowWindow); + _pptSlideShowWindow = null; + + if (IsSupportWPS && PPTApplication != null) + { + RecordWpsProcessForManagement(); + } + + UpdateCurrentPresentationInfo(); + + if (_lastSlideShowState) + { + _lastSlideShowState = false; + SlideShowStateChanged?.Invoke(false); + } + + SlideShowEnd?.Invoke(pres); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"处理幻灯片放映结束事件失败: {ex}", LogHelper.LogType.Error); + } + } + + private void OnSlideShowEndForComEvent(Presentation pres) + { + OnSlideShowEnd(pres); + } + #endregion + + #region Public Methods + public bool TryNavigateToSlide(int slideNumber) + { + object slideShowWindows = null; + object slideShowWindow = null; + object view = null; + object windows = null; + object window = null; + object windowView = null; + try + { + if (!IsConnected || PPTApplication == null) return false; + if (!Marshal.IsComObject(PPTApplication)) return false; + + if (IsInSlideShow) + { + slideShowWindows = PPTApplication.SlideShowWindows; + if (slideShowWindows != null) + { + dynamic ssw = slideShowWindows; + if (ssw.Count >= 1) + { + slideShowWindow = ssw[1]; + if (slideShowWindow != null) + { + dynamic sswObj = slideShowWindow; + view = sswObj.View; + if (view != null) + { + dynamic viewObj = view; + viewObj.GotoSlide(slideNumber); + return true; + } + } + } + } + } + else if (CurrentPresentation != null) + { + windows = CurrentPresentation.Windows; + if (windows != null) + { + dynamic win = windows; + if (win.Count >= 1) + { + window = win[1]; + if (window != null) + { + dynamic winObj = window; + windowView = winObj.View; + if (windowView != null) + { + dynamic viewObj = windowView; + viewObj.GotoSlide(slideNumber); + return true; + } + } + } + } + } + return false; } catch (COMException comEx) { @@ -653,17 +1968,148 @@ namespace Ink_Canvas.Helpers { DisconnectFromPPT(); } - LogHelper.WriteLogToFile($"[ROT] 开始幻灯片放映失败: {comEx.Message}", LogHelper.LogType.Error); + LogHelper.WriteLogToFile($"跳转到幻灯片{slideNumber}失败: {comEx.Message}", LogHelper.LogType.Error); return false; } catch (Exception ex) { - LogHelper.WriteLogToFile($"[ROT] 开始幻灯片放映失败: {ex}", LogHelper.LogType.Error); + LogHelper.WriteLogToFile($"跳转到幻灯片{slideNumber}失败: {ex}", LogHelper.LogType.Error); return false; } finally { - SafeReleaseComObject(pres); + SafeReleaseComObject(windowView); + SafeReleaseComObject(window); + SafeReleaseComObject(windows); + SafeReleaseComObject(view); + SafeReleaseComObject(slideShowWindow); + SafeReleaseComObject(slideShowWindows); + } + } + + public bool TryNavigateNext() + { + try + { + if (!IsConnected || !IsInSlideShow || PPTApplication == null) return false; + if (!Marshal.IsComObject(PPTApplication)) return false; + + // 在新线程中执行翻页操作,避免等待动画完成 + new Thread(() => + { + try + { + object slideShowWindows = PPTApplication.SlideShowWindows; + if (slideShowWindows != null) + { + dynamic ssw = slideShowWindows; + object slideShowWindow = ssw[1]; + if (slideShowWindow != null) + { + dynamic sswObj = slideShowWindow; + try + { + sswObj.Activate(); + } + catch { } + try + { + object view = sswObj.View; + if (view != null) + { + dynamic viewObj = view; + viewObj.Next(); + } + } + catch { } + SafeReleaseComObject(slideShowWindow); + } + SafeReleaseComObject(slideShowWindows); + } + } + catch (COMException comEx) + { + var hr = (uint)comEx.HResult; + if (hr == 0x8001010E || hr == 0x80004005) + { + DisconnectFromPPT(); + } + LogHelper.WriteLogToFile($"切换到下一页失败: {comEx.Message}", LogHelper.LogType.Error); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"切换到下一页失败: {ex}", LogHelper.LogType.Error); + } + }).Start(); + return true; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"启动下一页线程失败: {ex}", LogHelper.LogType.Error); + return false; + } + } + + public bool TryNavigatePrevious() + { + try + { + if (!IsConnected || !IsInSlideShow || PPTApplication == null) return false; + if (!Marshal.IsComObject(PPTApplication)) return false; + + // 在新线程中执行翻页操作,避免等待动画完成 + new Thread(() => + { + try + { + object slideShowWindows = PPTApplication.SlideShowWindows; + if (slideShowWindows != null) + { + dynamic ssw = slideShowWindows; + object slideShowWindow = ssw[1]; + if (slideShowWindow != null) + { + dynamic sswObj = slideShowWindow; + try + { + sswObj.Activate(); + } + catch { } + try + { + object view = sswObj.View; + if (view != null) + { + dynamic viewObj = view; + viewObj.Previous(); + } + } + catch { } + SafeReleaseComObject(slideShowWindow); + } + SafeReleaseComObject(slideShowWindows); + } + } + catch (COMException comEx) + { + var hr = (uint)comEx.HResult; + if (hr == 0x8001010E || hr == 0x80004005) + { + DisconnectFromPPT(); + } + LogHelper.WriteLogToFile($"切换到上一页失败: {comEx.Message}", LogHelper.LogType.Error); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"切换到上一页失败: {ex}", LogHelper.LogType.Error); + } + }).Start(); + return true; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"启动上一页线程失败: {ex}", LogHelper.LogType.Error); + return false; } } @@ -674,47 +2120,59 @@ namespace Ink_Canvas.Helpers object view = null; try { - if (!IsConnected || _pptApplication == null) return false; - if (!Marshal.IsComObject(_pptApplication)) return false; + if (!IsConnected || !IsInSlideShow || PPTApplication == null) return false; + if (!Marshal.IsComObject(PPTApplication)) return false; - dynamic app = _pptApplication; - slideShowWindows = app.SlideShowWindows; + slideShowWindows = PPTApplication.SlideShowWindows; if (slideShowWindows != null) { dynamic ssw = slideShowWindows; - int count = 0; - try { count = ssw.Count; } catch { count = 0; } - - for (int i = 1; i <= count; i++) + slideShowWindow = ssw[1]; + if (slideShowWindow != null) { - try + dynamic sswObj = slideShowWindow; + view = sswObj.View; + if (view != null) { - slideShowWindow = ssw[i]; - if (slideShowWindow != null) - { - dynamic sswObj = slideShowWindow; - view = sswObj.View; - if (view != null) - { - dynamic viewObj = view; - viewObj.Exit(); - } - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"[ROT] 结束第 {i} 个放映窗口失败: {ex}", LogHelper.LogType.Warning); - } - finally - { - SafeReleaseComObject(view); - SafeReleaseComObject(slideShowWindow); - view = null; - slideShowWindow = null; + dynamic viewObj = view; + viewObj.Exit(); + return true; } } } + return false; + } + catch (COMException comEx) + { + var hr = (uint)comEx.HResult; + if (hr == 0x8001010E || hr == 0x80004005) + { + DisconnectFromPPT(); + } + LogHelper.WriteLogToFile($"结束幻灯片放映失败: {comEx.Message}", LogHelper.LogType.Error); + return false; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"结束幻灯片放映失败: {ex}", LogHelper.LogType.Error); + return false; + } + finally + { + SafeReleaseComObject(view); + SafeReleaseComObject(slideShowWindow); + SafeReleaseComObject(slideShowWindows); + } + } + public bool TryStartSlideShow() + { + try + { + if (!IsConnected || CurrentPresentation == null || PPTApplication == null) return false; + if (!Marshal.IsComObject(PPTApplication) || !Marshal.IsComObject(CurrentPresentation)) return false; + + CurrentPresentation.SlideShowSettings.Run(); return true; } catch (COMException comEx) @@ -722,365 +2180,51 @@ namespace Ink_Canvas.Helpers var hr = (uint)comEx.HResult; if (hr == 0x8001010E || hr == 0x80004005) { + // COM对象已失效,触发断开连接 DisconnectFromPPT(); } - LogHelper.WriteLogToFile($"[ROT] 结束幻灯片放映失败: {comEx.Message}", LogHelper.LogType.Error); + LogHelper.WriteLogToFile($"开始幻灯片放映失败: {comEx.Message}", LogHelper.LogType.Error); return false; } catch (Exception ex) { - LogHelper.WriteLogToFile($"[ROT] 结束幻灯片放映失败: {ex}", LogHelper.LogType.Error); + LogHelper.WriteLogToFile($"开始幻灯片放映失败: {ex}", LogHelper.LogType.Error); return false; } - finally - { - SafeReleaseComObject(view); - SafeReleaseComObject(slideShowWindow); - SafeReleaseComObject(slideShowWindows); - } - } - - public bool TryNavigateToSlide(int slideNumber) - { - object slideShowWindows = null; - object slideShowWindow = null; - object view = null; - try - { - if (!IsConnected || !IsInSlideShow || _pptApplication == null) return false; - if (!Marshal.IsComObject(_pptApplication)) return false; - - dynamic app = _pptApplication; - slideShowWindows = app.SlideShowWindows; - if (slideShowWindows != null) - { - dynamic ssw = slideShowWindows; - slideShowWindow = ssw[1]; - if (slideShowWindow != null) - { - dynamic sswObj = slideShowWindow; - if (!SkipAnimationsWhenNavigating) - sswObj.Activate(); - view = sswObj.View; - if (view != null) - { - dynamic viewObj = view; - viewObj.GotoSlide(slideNumber); - return true; - } - } - } - return false; - } - catch (COMException comEx) - { - var hr = (uint)comEx.HResult; - if (hr == 0x8001010E || hr == 0x80004005) - { - DisconnectFromPPT(); - } - LogHelper.WriteLogToFile($"[ROT] 跳转到第 {slideNumber} 页失败: {comEx.Message}", LogHelper.LogType.Error); - return false; - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"[ROT] 跳转到第 {slideNumber} 页失败: {ex}", LogHelper.LogType.Error); - return false; - } - finally - { - SafeReleaseComObject(view); - SafeReleaseComObject(slideShowWindow); - SafeReleaseComObject(slideShowWindows); - } - } - - public bool TryNavigateNext() - { - object slideShowWindows = null; - object slideShowWindow = null; - object view = null; - try - { - if (!IsConnected || !IsInSlideShow || _pptApplication == null) return false; - if (!Marshal.IsComObject(_pptApplication)) return false; - - dynamic app = _pptApplication; - slideShowWindows = app.SlideShowWindows; - if (slideShowWindows != null) - { - dynamic ssw = slideShowWindows; - slideShowWindow = ssw[1]; - if (slideShowWindow != null) - { - dynamic sswObj = slideShowWindow; - if (!SkipAnimationsWhenNavigating) - sswObj.Activate(); - view = sswObj.View; - if (view != null) - { - dynamic viewObj = view; - viewObj.Next(); - return true; - } - } - } - return false; - } - catch (COMException comEx) - { - var hr = (uint)comEx.HResult; - if (hr == 0x8001010E || hr == 0x80004005) - { - DisconnectFromPPT(); - } - LogHelper.WriteLogToFile($"[ROT] 切换到下一页失败: {comEx.Message}", LogHelper.LogType.Error); - return false; - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"[ROT] 切换到下一页失败: {ex}", LogHelper.LogType.Error); - return false; - } - finally - { - SafeReleaseComObject(view); - SafeReleaseComObject(slideShowWindow); - SafeReleaseComObject(slideShowWindows); - } - } - - public bool TryNavigatePrevious() - { - object slideShowWindows = null; - object slideShowWindow = null; - object view = null; - try - { - if (!IsConnected || !IsInSlideShow || _pptApplication == null) return false; - if (!Marshal.IsComObject(_pptApplication)) return false; - - dynamic app = _pptApplication; - slideShowWindows = app.SlideShowWindows; - if (slideShowWindows != null) - { - dynamic ssw = slideShowWindows; - slideShowWindow = ssw[1]; - if (slideShowWindow != null) - { - dynamic sswObj = slideShowWindow; - if (!SkipAnimationsWhenNavigating) - sswObj.Activate(); - view = sswObj.View; - if (view != null) - { - dynamic viewObj = view; - viewObj.Previous(); - return true; - } - } - } - return false; - } - catch (COMException comEx) - { - var hr = (uint)comEx.HResult; - if (hr == 0x8001010E || hr == 0x80004005) - { - DisconnectFromPPT(); - } - LogHelper.WriteLogToFile($"[ROT] 切换到上一页失败: {comEx.Message}", LogHelper.LogType.Error); - return false; - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"[ROT] 切换到上一页失败: {ex}", LogHelper.LogType.Error); - return false; - } - finally - { - SafeReleaseComObject(view); - SafeReleaseComObject(slideShowWindow); - SafeReleaseComObject(slideShowWindows); - } - } - - public int GetCurrentSlideNumber() - { - object slideShowWindows = null; - object slideShowWindow = null; - object view = null; - try - { - if (!IsConnected || _pptApplication == null) return 0; - if (!Marshal.IsComObject(_pptApplication)) return 0; - - dynamic app = _pptApplication; - slideShowWindows = app.SlideShowWindows; - if (slideShowWindows != null) - { - dynamic ssw = slideShowWindows; - slideShowWindow = ssw[1]; - if (slideShowWindow != null) - { - dynamic sswObj = slideShowWindow; - view = sswObj.View; - if (view != null) - { - dynamic viewObj = view; - return (int)viewObj.CurrentShowPosition; - } - } - } - return 0; - } - catch (COMException comEx) - { - var hr = (uint)comEx.HResult; - if (hr == 0x8001010E || hr == 0x80004005) - { - DisconnectFromPPT(); - } - LogHelper.WriteLogToFile($"[ROT] 获取当前页码失败: {comEx.Message}", LogHelper.LogType.Error); - return 0; - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"[ROT] 获取当前页码失败: {ex}", LogHelper.LogType.Error); - return 0; - } - finally - { - SafeReleaseComObject(view); - SafeReleaseComObject(slideShowWindow); - SafeReleaseComObject(slideShowWindows); - } - } - - public string GetPresentationName() - { - object pres = null; - try - { - pres = GetCurrentActivePresentation(); - if (pres == null) return ""; - - dynamic dp = pres; - return (string)dp.Name; - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"[ROT] 获取演示文稿名称失败: {ex}", LogHelper.LogType.Error); - return ""; - } - finally - { - SafeReleaseComObject(pres); - } - } - - public bool TryShowSlideNavigation() - { - object slideShowWindows = null; - object slideShowWindow = null; - object slideNavigation = null; - try - { - LogHelper.WriteLogToFile($"[ROT] 尝试显示幻灯片导航 - 连接状态: {IsConnected}, 放映状态: {IsInSlideShow}", LogHelper.LogType.Trace); - - if (!IsConnected || !IsInSlideShow || _pptApplication == null) - { - LogHelper.WriteLogToFile("[ROT] PPT 未连接或未在放映状态", LogHelper.LogType.Warning); - return false; - } - - if (!Marshal.IsComObject(_pptApplication)) - { - LogHelper.WriteLogToFile("[ROT] PPT 应用程序 COM 对象无效", LogHelper.LogType.Warning); - return false; - } - - dynamic app = _pptApplication; - slideShowWindows = app.SlideShowWindows; - if (slideShowWindows != null) - { - dynamic ssw = slideShowWindows; - slideShowWindow = ssw[1]; - if (slideShowWindow == null) - { - LogHelper.WriteLogToFile("[ROT] 幻灯片放映窗口为空", LogHelper.LogType.Warning); - return false; - } - - try - { - dynamic sswObj = slideShowWindow; - slideNavigation = sswObj.SlideNavigation; - if (slideNavigation != null) - { - dynamic sn = slideNavigation; - sn.Visible = true; - LogHelper.WriteLogToFile("[ROT] 成功显示幻灯片导航", LogHelper.LogType.Event); - return true; - } - - LogHelper.WriteLogToFile("[ROT] SlideNavigation 对象为空,可能当前环境不支持", LogHelper.LogType.Warning); - return false; - } - catch (COMException comEx) - { - var hr = (uint)comEx.HResult; - if (hr == 0x80020006) - { - LogHelper.WriteLogToFile("[ROT] 当前 PPT 实例不支持 SlideNavigation 功能", LogHelper.LogType.Warning); - return false; - } - throw; - } - } - return false; - } - catch (COMException comEx) - { - var hr = (uint)comEx.HResult; - if (hr == 0x8001010E || hr == 0x80004005) - { - DisconnectFromPPT(); - } - LogHelper.WriteLogToFile($"[ROT] 显示幻灯片导航失败: {comEx.Message}", LogHelper.LogType.Error); - return false; - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"[ROT] 显示幻灯片导航失败: {ex}", LogHelper.LogType.Error); - return false; - } - finally - { - SafeReleaseComObject(slideNavigation); - SafeReleaseComObject(slideShowWindow); - SafeReleaseComObject(slideShowWindows); - } } + /// + /// 获取当前活跃的演示文稿 + /// public object GetCurrentActivePresentation() { - object pres = null; object slideShowWindows = null; object slideShowWindow = null; object view = null; object slide = null; + object activeWindow = null; + object presentation = null; try { - if (!IsConnected || _pptApplication == null) return null; - if (!Marshal.IsComObject(_pptApplication)) return null; + if (!IsConnected || PPTApplication == null) return null; + if (!Marshal.IsComObject(PPTApplication)) return null; - dynamic app = _pptApplication; + // 检查COM对象是否仍然有效 + try + { + var _ = Marshal.GetIUnknownForObject(PPTApplication); + Marshal.Release(_); + } + catch (InvalidComObjectException) + { + // COM对象已失效 + return null; + } - // 优先使用放映窗口里的当前演示文稿 if (IsInSlideShow) { - slideShowWindows = app.SlideShowWindows; + dynamic pptAppForSSW = PPTApplication; + slideShowWindows = pptAppForSSW.SlideShowWindows; if (slideShowWindows != null) { dynamic ssw = slideShowWindows; @@ -1097,9 +2241,9 @@ namespace Ink_Canvas.Helpers slide = viewObj.Slide; if (slide != null) { - dynamic ds = slide; - pres = ds.Parent; - return pres; + dynamic slideObj = slide; + presentation = slideObj.Parent; + return presentation; } } } @@ -1107,82 +2251,1272 @@ namespace Ink_Canvas.Helpers } } - // 其次尝试 ActiveWindow.Presentation - try + activeWindow = PPTApplication.ActiveWindow; + if (activeWindow != null) { - var aw = app.ActiveWindow; - if (aw != null) + dynamic aw = activeWindow; + presentation = aw.Presentation; + if (presentation != null) { - dynamic daw = aw; - pres = daw.Presentation; - if (pres != null) return pres; + return presentation; } } - catch - { - } - return null; + return CurrentPresentation; } catch (COMException comEx) { var hr = (uint)comEx.HResult; - if (hr != 0x8001010E && hr != 0x80004005) + if (hr == 0x8001010E || hr == 0x80004005) { - LogHelper.WriteLogToFile($"[ROT] 获取当前演示文稿失败: {comEx.Message}", LogHelper.LogType.Error); + DisconnectFromPPT(); } - return null; + if (hr == 0x80048240) + { + return null; + } + LogHelper.WriteLogToFile($"获取当前活跃演示文稿失败: {comEx.Message}", LogHelper.LogType.Warning); + return CurrentPresentation; } catch (Exception ex) { - LogHelper.WriteLogToFile($"[ROT] 获取当前演示文稿失败: {ex}", LogHelper.LogType.Error); - return null; + LogHelper.WriteLogToFile($"获取当前活跃演示文稿失败: {ex}", LogHelper.LogType.Error); + return CurrentPresentation; + } + finally + { + if (presentation != null && !ReferenceEquals(presentation, CurrentPresentation)) + { + SafeReleaseComObject(presentation); + } + SafeReleaseComObject(slide); + SafeReleaseComObject(view); + SafeReleaseComObject(slideShowWindow); + SafeReleaseComObject(slideShowWindows); + SafeReleaseComObject(activeWindow); + } + } + + /// + /// 获取当前幻灯片编号 + /// + private int GetCurrentSlideIndex(dynamic slideShowWindow) + { + object view = null; + object slide = null; + + try + { + if (slideShowWindow == null) return 0; + + // 检查COM对象是否有效 + if (!Marshal.IsComObject(slideShowWindow)) + { + return 0; + } + + try + { + var _ = Marshal.GetIUnknownForObject(slideShowWindow); + Marshal.Release(_); + } + catch (InvalidComObjectException) + { + return 0; + } + + dynamic ssw = slideShowWindow; + view = ssw.View; + if (view != null) + { + dynamic viewObj = view; + slide = viewObj.Slide; + if (slide != null) + { + dynamic slideObj = slide; + return slideObj.SlideIndex; + } + } + return 0; + } + catch (COMException comEx) + { + // 处理 0x80048240: SlideShowView.Slide : Invalid request. No slide is currently in view. + var hr = (uint)comEx.HResult; + if (hr == 0x80048240) + { + return 0; + } + throw; + } + catch (InvalidComObjectException) + { + return 0; } finally { SafeReleaseComObject(slide); SafeReleaseComObject(view); - SafeReleaseComObject(slideShowWindow); - SafeReleaseComObject(slideShowWindows); - // 注意:pres 作为返回值时不在这里释放,由调用方负责(如有需要) } } - #endregion - #region Helpers - /// - /// 安全释放 COM 对象。 - /// - private void SafeReleaseComObject(object comObject) + private int GetTotalSlideIndex(dynamic presentation) { try { - if (comObject != null && Marshal.IsComObject(comObject)) - { - Marshal.ReleaseComObject(comObject); - } + if (presentation == null) return 0; + dynamic pres = presentation; + return pres.Slides.Count; } catch { + return 0; + } + } + + public int GetCurrentSlideNumber() + { + object activeWindow = null; + object selection = null; + object slideRange = null; + try + { + if (!IsConnected || PPTApplication == null) return 0; + if (!Marshal.IsComObject(PPTApplication)) return 0; + + if (IsInSlideShow && _pptSlideShowWindow != null) + { + try + { + return GetCurrentSlideIndex(_pptSlideShowWindow); + } + catch + { + return 0; + } + } + + activeWindow = PPTApplication.ActiveWindow; + if (activeWindow != null) + { + dynamic aw = activeWindow; + selection = aw.Selection; + if (selection != null) + { + dynamic sel = selection; + slideRange = sel.SlideRange; + if (slideRange != null) + { + dynamic sr = slideRange; + int slideNumber = sr.SlideNumber; + if (slideNumber > 0) + { + return slideNumber; + } + } + } + } + + if (CurrentSlide != null && Marshal.IsComObject(CurrentSlide)) + { + return CurrentSlide.SlideNumber; + } + + return 0; + } + catch (COMException comEx) + { + var hr = (uint)comEx.HResult; + if (hr == 0x8001010E || hr == 0x80004005) + { + DisconnectFromPPT(); + } + return 0; + } + catch (Exception) + { + return 0; + } + finally + { + SafeReleaseComObject(slideRange); + SafeReleaseComObject(selection); + SafeReleaseComObject(activeWindow); + } + } + + public string GetPresentationName() + { + try + { + if (CurrentPresentation == null || !Marshal.IsComObject(CurrentPresentation)) return ""; + + return CurrentPresentation.Name ?? ""; + } + catch (COMException comEx) + { + var hr = (uint)comEx.HResult; + if (hr == 0x8001010E || hr == 0x80004005) + { + // COM对象已失效,触发断开连接 + DisconnectFromPPT(); + } + LogHelper.WriteLogToFile($"获取演示文稿名称失败: {comEx.Message}", LogHelper.LogType.Warning); + return ""; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"获取演示文稿名称失败: {ex}", LogHelper.LogType.Error); + return ""; + } + } + + public string GetPresentationPath() + { + try + { + if (CurrentPresentation == null || !Marshal.IsComObject(CurrentPresentation)) return ""; + + return CurrentPresentation.FullName ?? ""; + } + catch (COMException comEx) + { + var hr = (uint)comEx.HResult; + if (hr == 0x8001010E || hr == 0x80004005) + { + // COM对象已失效,触发断开连接 + DisconnectFromPPT(); + } + LogHelper.WriteLogToFile($"获取演示文稿路径失败: {comEx.Message}", LogHelper.LogType.Warning); + return ""; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"获取演示文稿路径失败: {ex}", LogHelper.LogType.Error); + return ""; + } + } + + public bool TryShowSlideNavigation() + { + object slideShowWindows = null; + object slideShowWindow = null; + object slideNavigation = null; + try + { + LogHelper.WriteLogToFile($"尝试显示幻灯片导航 - 连接状态: {IsConnected}, 放映状态: {IsInSlideShow}", LogHelper.LogType.Trace); + + if (!IsConnected || !IsInSlideShow || PPTApplication == null) + { + LogHelper.WriteLogToFile("PPT未连接或未在放映状态", LogHelper.LogType.Warning); + return false; + } + + if (!Marshal.IsComObject(PPTApplication)) + { + LogHelper.WriteLogToFile("PPT应用程序COM对象无效", LogHelper.LogType.Warning); + return false; + } + + slideShowWindows = PPTApplication.SlideShowWindows; + if (slideShowWindows != null) + { + dynamic ssw = slideShowWindows; + slideShowWindow = ssw[1]; + if (slideShowWindow == null) + { + LogHelper.WriteLogToFile("幻灯片放映窗口为空", LogHelper.LogType.Warning); + return false; + } + + try + { + dynamic sswObj = slideShowWindow; + slideNavigation = sswObj.SlideNavigation; + if (slideNavigation != null) + { + dynamic sn = slideNavigation; + sn.Visible = true; + LogHelper.WriteLogToFile("成功显示幻灯片导航(PowerPoint模式)", LogHelper.LogType.Event); + return true; + } + + LogHelper.WriteLogToFile("SlideNavigation对象为空,可能是WPS不支持此功能", LogHelper.LogType.Warning); + return false; + } + catch (COMException comEx) + { + var hr = (uint)comEx.HResult; + if (hr == 0x80020006) + { + LogHelper.WriteLogToFile("WPS不支持SlideNavigation功能", LogHelper.LogType.Warning); + return false; + } + throw; + } + } + return false; + } + catch (COMException comEx) + { + var hr = (uint)comEx.HResult; + if (hr == 0x8001010E || hr == 0x80004005) + { + DisconnectFromPPT(); + } + LogHelper.WriteLogToFile($"显示幻灯片导航COM异常: {comEx.Message} (HRESULT: 0x{hr:X8})", LogHelper.LogType.Error); + return false; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"显示幻灯片导航失败: {ex}", LogHelper.LogType.Error); + return false; + } + finally + { + SafeReleaseComObject(slideNavigation); + SafeReleaseComObject(slideShowWindow); + SafeReleaseComObject(slideShowWindows); } } #endregion - #region IDisposable - public void Dispose() + #region WPS Process Management + private void RecordWpsProcessForManagement() { - if (_disposed) return; - _disposed = true; + if (!IsSupportWPS || PPTApplication == null) return; try { - StopMonitoring(); + Process wpsProcess = null; + + // 方法1:通过应用程序路径检测 + if (PPTApplication.Path.Contains("Kingsoft\\WPS Office\\") || + PPTApplication.Path.Contains("WPS Office\\")) + { + uint processId; + GetWindowThreadProcessId((IntPtr)PPTApplication.HWND, out processId); + wpsProcess = Process.GetProcessById((int)processId); + } + + // 方法2:通过前台窗口检测 + if (wpsProcess == null) + { + var foregroundWpsWindow = GetForegroundWpsWindow(); + if (foregroundWpsWindow != null) + { + wpsProcess = Process.GetProcessById((int)foregroundWpsWindow.ProcessId); + } + } + + // 方法3:通过进程名检测 + if (wpsProcess == null) + { + var wpsProcesses = GetWpsProcesses(); + if (wpsProcesses.Count > 0) + { + wpsProcess = wpsProcesses.First(); + } + } + + if (wpsProcess != null) + { + _wpsProcess = wpsProcess; + _hasWpsProcessId = true; + _wpsProcessRecordTime = DateTime.Now; + _wpsProcessCheckCount = 0; + LogHelper.WriteLogToFile($"成功记录 WPS 进程 ID: {wpsProcess.Id}", LogHelper.LogType.Trace); + + StartWpsProcessCheckTimer(); + } + else + { + LogHelper.WriteLogToFile("未能检测到WPS进程", LogHelper.LogType.Warning); + } } catch (Exception ex) { - LogHelper.WriteLogToFile($"[ROT] Dispose 异常: {ex}", LogHelper.LogType.Error); + LogHelper.WriteLogToFile($"记录WPS进程失败: {ex}", LogHelper.LogType.Error); + } + } + + private void StartWpsProcessCheckTimer() + { + if (!IsSupportWPS) return; + + if (_wpsProcessCheckTimer != null) + { + _wpsProcessCheckTimer.Stop(); + _wpsProcessCheckTimer.Dispose(); + } + + // 增加检查间隔到2秒,减少性能开销 + _wpsProcessCheckTimer = new Timer(2000); + _wpsProcessCheckTimer.Elapsed += OnWpsProcessCheckTimerElapsed; + _wpsProcessCheckTimer.Start(); + LogHelper.WriteLogToFile("启动 WPS 进程检测定时器", LogHelper.LogType.Trace); + } + + private void OnWpsProcessCheckTimerElapsed(object sender, ElapsedEventArgs e) + { + if (!IsSupportWPS) + { + StopWpsProcessCheckTimer(); + return; + } + + try + { + if (_wpsProcess == null || !_hasWpsProcessId) + { + StopWpsProcessCheckTimer(); + return; + } + + _wpsProcess.Refresh(); + _wpsProcessCheckCount++; + + if (_wpsProcess.HasExited) + { + LogHelper.WriteLogToFile("WPS 进程已正常关闭", LogHelper.LogType.Trace); + StopWpsProcessCheckTimer(); + return; + } + + + // 检查前台WPS窗口是否存在 + bool isForegroundWpsWindowActive = IsForegroundWpsWindowStillActiveOptimized(); + + if (isForegroundWpsWindowActive) + { + if (_wpsProcessCheckCount % 5 == 0) // 每10秒记录一次日志 + { + LogHelper.WriteLogToFile($"WPS窗口仍然活跃,继续监控(已检查{_wpsProcessCheckCount}次)", LogHelper.LogType.Trace); + } + return; + } + + // 多重验证确保准确性 + if (!PerformMultipleWpsWindowChecks()) + { + LogHelper.WriteLogToFile("多重验证显示WPS窗口仍然存在,跳过查杀", LogHelper.LogType.Trace); + return; + } + + // 前台窗口已消失,准备结束WPS进程 + LogHelper.WriteLogToFile("多重验证确认WPS窗口已消失,准备结束WPS进程", LogHelper.LogType.Event); + + // 安全结束WPS进程 + SafeTerminateWpsProcess(); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"WPS 进程检测失败: {ex}", LogHelper.LogType.Error); + StopWpsProcessCheckTimer(); + } + } + + /// + /// 前台WPS窗口检测 + /// + private bool IsForegroundWpsWindowStillActiveOptimized() + { + try + { + // 快速检查:直接检查前台窗口 + var foregroundWindow = GetForegroundWindow(); + if (foregroundWindow == IntPtr.Zero) return false; + + // 获取前台窗口的进程ID + uint processId; + GetWindowThreadProcessId(foregroundWindow, out processId); + + // 如果前台窗口就是我们监控的WPS进程,则认为仍然活跃 + if (processId == _wpsProcess?.Id) + { + return true; + } + + // 检查是否为WPS相关窗口 + var windowInfo = GetWindowInfo(foregroundWindow); + return IsWpsWindow(windowInfo); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"WPS窗口检测失败: {ex}", LogHelper.LogType.Error); + return false; + } + } + + /// + /// 多重验证WPS窗口状态,确保查杀准确性 + /// + private bool PerformMultipleWpsWindowChecks() + { + try + { + // 第一重验证:等待1秒后再次检查 + Thread.Sleep(1000); + if (IsForegroundWpsWindowStillActiveOptimized()) + { + LogHelper.WriteLogToFile("第一重验证:WPS窗口仍然存在", LogHelper.LogType.Trace); + return false; + } + + // 第二重验证:检查所有WPS进程的窗口 + var wpsProcesses = GetWpsProcesses(); + foreach (var process in wpsProcesses) + { + if (process.Id == _wpsProcess?.Id) continue; // 跳过当前监控的进程 + + var windows = GetWpsWindowsByProcess(process.Id); + if (windows.Any(w => w.IsVisible && !w.IsMinimized)) + { + LogHelper.WriteLogToFile($"第二重验证:发现其他WPS进程{process.Id}有活跃窗口", LogHelper.LogType.Trace); + return false; + } + } + + // 第三重验证:检查任务栏中的WPS窗口 + if (HasWpsWindowInTaskbar()) + { + LogHelper.WriteLogToFile("第三重验证:任务栏中仍有WPS窗口", LogHelper.LogType.Trace); + return false; + } + + LogHelper.WriteLogToFile("多重验证完成:确认WPS窗口已全部消失", LogHelper.LogType.Event); + return true; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"多重验证失败: {ex}", LogHelper.LogType.Error); + return false; // 出错时保守处理,不进行查杀 + } + } + + /// + /// 检查任务栏中是否有WPS窗口 + /// + private bool HasWpsWindowInTaskbar() + { + try + { + var allWindows = new List(); + + EnumWindows((hWnd, lParam) => + { + try + { + if (IsWindow(hWnd) && IsWindowVisible(hWnd)) + { + var windowInfo = GetWindowInfo(hWnd); + if (IsWpsWindow(windowInfo) && !string.IsNullOrEmpty(windowInfo.Title)) + { + allWindows.Add(windowInfo); + } + } + } + catch { } + return true; + }, IntPtr.Zero); + + return allWindows.Count > 0; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"检查任务栏WPS窗口失败: {ex}", LogHelper.LogType.Error); + return true; // 出错时保守处理,认为仍有窗口 + } + } + + /// + /// 安全地结束WPS进程 - 通过释放PPTCOM对象 + /// + private void SafeTerminateWpsProcess() + { + try + { + if (_wpsProcess == null || _wpsProcess.HasExited) + { + LogHelper.WriteLogToFile("WPS进程已经结束,无需查杀", LogHelper.LogType.Trace); + StopWpsProcessCheckTimer(); + return; + } + + LogHelper.WriteLogToFile($"开始通过释放PPTCOM对象安全结束WPS进程 (PID: {_wpsProcess.Id})", LogHelper.LogType.Event); + + // 第一步:释放 pptActWindow 对象(SlideShowWindow) + SlideShowWindow pptActWindow = null; + try + { + if (PPTApplication != null && Marshal.IsComObject(PPTApplication)) + { + if (PPTApplication.SlideShowWindows?.Count > 0) + { + pptActWindow = PPTApplication.SlideShowWindows[1]; + } + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"获取SlideShowWindow对象时发生异常: {ex}", LogHelper.LogType.Warning); + } + + if (pptActWindow != null) + { + Marshal.ReleaseComObject(pptActWindow); + pptActWindow = null; + LogHelper.WriteLogToFile("已释放pptActWindow对象", LogHelper.LogType.Trace); + } + + // 第二步:释放 pptActDoc 对象(CurrentPresentation) + Presentation pptActDoc = CurrentPresentation; + if (pptActDoc != null) + { + Marshal.ReleaseComObject(pptActDoc); + pptActDoc = null; + CurrentPresentation = null; + LogHelper.WriteLogToFile("已释放pptActDoc对象", LogHelper.LogType.Trace); + } + + // 第三步:释放 pptApp 对象(PPTApplication) + if (PPTApplication != null) + { + try + { + Marshal.ReleaseComObject(PPTApplication); + PPTApplication = null; + LogHelper.WriteLogToFile("已释放pptApp对象", LogHelper.LogType.Trace); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"释放pptApp对象失败: {ex.Message}", LogHelper.LogType.Trace); + PPTApplication = null; + } + } + + // 第四步:强制垃圾回收及等待终结器执行 + LogHelper.WriteLogToFile("执行强制垃圾回收", LogHelper.LogType.Trace); + GC.Collect(); + GC.WaitForPendingFinalizers(); + + // 等待一段时间让COM对象完全释放 + Thread.Sleep(1000); + + // 检查进程是否已经结束 + try + { + _wpsProcess.Refresh(); + if (_wpsProcess.HasExited) + { + LogHelper.WriteLogToFile("WPS进程已通过COM对象释放成功结束", LogHelper.LogType.Event); + StopWpsProcessCheckTimer(); + return; + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"检查WPS进程状态失败: {ex}", LogHelper.LogType.Warning); + } + + // 备用方案:如果COM对象释放后进程仍未结束,尝试关闭 + try + { + LogHelper.WriteLogToFile("COM对象释放后进程仍在运行,尝试关闭", LogHelper.LogType.Warning); + _wpsProcess.CloseMainWindow(); + if (_wpsProcess.WaitForExit(3000)) // 等待3秒 + { + LogHelper.WriteLogToFile("WPS进程已关闭", LogHelper.LogType.Event); + StopWpsProcessCheckTimer(); + return; + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"关闭WPS进程失败: {ex}", LogHelper.LogType.Warning); + } + + // 最后备用方案:强制结束进程 + try + { + if (!_wpsProcess.HasExited) + { + LogHelper.WriteLogToFile("所有方法都失败,强制结束WPS进程", LogHelper.LogType.Warning); + _wpsProcess.Kill(); + LogHelper.WriteLogToFile("WPS进程已强制结束", LogHelper.LogType.Event); + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"强制结束WPS进程失败: {ex}", LogHelper.LogType.Error); + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"安全结束WPS进程时发生异常: {ex}", LogHelper.LogType.Error); + } + finally + { + // 确保清理状态 + if (CurrentSlide != null && Marshal.IsComObject(CurrentSlide)) + { + try { Marshal.ReleaseComObject(CurrentSlide); } catch { } + } + if (CurrentSlides != null && Marshal.IsComObject(CurrentSlides)) + { + try { Marshal.ReleaseComObject(CurrentSlides); } catch { } + } + if (CurrentPresentation != null && Marshal.IsComObject(CurrentPresentation)) + { + try { Marshal.ReleaseComObject(CurrentPresentation); } catch { } + } + if (PPTApplication != null && Marshal.IsComObject(PPTApplication)) + { + try { Marshal.ReleaseComObject(PPTApplication); } catch { } + } + + CurrentSlide = null; + CurrentSlides = null; + CurrentPresentation = null; + PPTApplication = null; + SlidesCount = 0; + StopWpsProcessCheckTimer(); + + PPTConnectionChanged?.Invoke(false); + + LogHelper.WriteLogToFile("WPS进程结束后已清理所有COM对象并重启连接检查", LogHelper.LogType.Event); + } + } + + + + private void StopWpsProcessCheckTimer() + { + if (_wpsProcessCheckTimer != null) + { + _wpsProcessCheckTimer.Stop(); + _wpsProcessCheckTimer.Dispose(); + _wpsProcessCheckTimer = null; + } + + _wpsProcess = null; + _hasWpsProcessId = false; + _wpsProcessRecordTime = DateTime.MinValue; + _wpsProcessCheckCount = 0; + _lastForegroundWpsWindow = null; + _lastWindowCheckTime = DateTime.MinValue; + LogHelper.WriteLogToFile("停止 WPS 进程检测定时器", LogHelper.LogType.Trace); + } + #endregion + + #region WPS Window Detection + [DllImport("user32.dll")] + private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId); + + [DllImport("user32.dll")] + private static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam); + + [DllImport("user32.dll")] + private static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount); + + [DllImport("user32.dll")] + private static extern int GetWindowTextLength(IntPtr hWnd); + + [DllImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool IsWindowVisible(IntPtr hWnd); + + [DllImport("user32.dll")] + private static extern bool IsIconic(IntPtr hWnd); + + [DllImport("user32.dll")] + private static extern bool IsZoomed(IntPtr hWnd); + + [DllImport("user32.dll")] + private static extern IntPtr GetForegroundWindow(); + + [DllImport("user32.dll")] + private static extern bool IsWindow(IntPtr hWnd); + + [DllImport("user32.dll")] + private static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect); + + [StructLayout(LayoutKind.Sequential)] + public struct RECT + { + public int Left; + public int Top; + public int Right; + public int Bottom; + + public int Width => Right - Left; + public int Height => Bottom - Top; + } + + [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] + private static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount); + + private delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam); + + private class WpsWindowInfo + { + public IntPtr Handle { get; set; } + public string Title { get; set; } + public string ClassName { get; set; } + public bool IsVisible { get; set; } + public bool IsMinimized { get; set; } + public bool IsMaximized { get; set; } + public RECT Rect { get; set; } + public uint ProcessId { get; set; } + public string ProcessName { get; set; } + } + + private WpsWindowInfo GetForegroundWpsWindow() + { + try + { + var foregroundHwnd = GetForegroundWindow(); + if (foregroundHwnd != IntPtr.Zero && IsWindow(foregroundHwnd)) + { + var windowInfo = GetWindowInfo(foregroundHwnd); + if (IsWpsWindow(windowInfo)) + { + return windowInfo; + } + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"获取前台WPS窗口失败: {ex}", LogHelper.LogType.Error); + } + return null; + } + + private WpsWindowInfo GetWindowInfo(IntPtr hWnd) + { + var windowInfo = new WpsWindowInfo + { + Handle = hWnd, + IsVisible = IsWindowVisible(hWnd), + IsMinimized = IsIconic(hWnd), + IsMaximized = IsZoomed(hWnd) + }; + + // 获取窗口标题 + var windowTitle = new StringBuilder(256); + GetWindowText(hWnd, windowTitle, 256); + windowInfo.Title = windowTitle.ToString().Trim(); + + // 获取窗口类名 + var className = new StringBuilder(256); + GetClassName(hWnd, className, 256); + windowInfo.ClassName = className.ToString().Trim(); + + // 获取窗口位置 + GetWindowRect(hWnd, out RECT rect); + windowInfo.Rect = rect; + + // 获取进程ID + uint processId; + GetWindowThreadProcessId(hWnd, out processId); + windowInfo.ProcessId = processId; + + // 获取进程名 + windowInfo.ProcessName = ""; + try + { + var proc = Process.GetProcessById((int)processId); + windowInfo.ProcessName = proc.ProcessName.ToLower(); + } + catch { } + + return windowInfo; + } + + private bool IsWpsWindow(WpsWindowInfo windowInfo) + { + if (string.IsNullOrEmpty(windowInfo.Title) && string.IsNullOrEmpty(windowInfo.ClassName)) + return false; + + var title = windowInfo.Title.ToLower(); + var className = windowInfo.ClassName.ToLower(); + var processName = windowInfo.ProcessName ?? ""; + + // WPS相关关键词 + var wpsKeywords = new[] { "wps", "wpp", "kingsoft", "金山", "wps演示", "wps presentation", "wps office", "kingsoft office" }; + // 微软Office相关进程名 + var msOfficeProcess = new[] { "powerpnt", "excel", "word", "onenote", "outlook", "microsoftoffice", "office" }; + + // 只要进程名是微软Office,直接排除 + if (msOfficeProcess.Any(keyword => processName.Contains(keyword))) + return false; + + // 只要进程名是WPS/WPP/Kingsoft,直接通过 + if (wpsKeywords.Any(keyword => processName.Contains(keyword))) + return true; + + // 标题或类名包含WPS相关关键词 + bool hasWpsTitle = wpsKeywords.Any(keyword => title.Contains(keyword)); + bool hasWpsClass = wpsKeywords.Any(keyword => className.Contains(keyword)); + bool isWpsClass = className.Contains("wps") || className.Contains("kingsoft") || className.Contains("wpp"); + bool hasValidSize = (windowInfo.Rect.Right - windowInfo.Rect.Left) > 0 && (windowInfo.Rect.Bottom - windowInfo.Rect.Top) > 0; + + return (hasWpsTitle || hasWpsClass || isWpsClass) && hasValidSize; + } + + private List GetWpsProcesses() + { + var wpsProcesses = new List(); + try + { + var allProcesses = Process.GetProcesses(); + foreach (var process in allProcesses) + { + try + { + var pname = process.ProcessName.ToLower(); + + // 精确的WPS进程名匹配,避免误杀 + var exactWpsNames = new[] { "wps", "wpp", "et", "wpspdf", "wpsoffice" }; + var microsoftOfficeNames = new[] { "powerpnt", "excel", "word", "onenote", "outlook", "winword", "msaccess" }; + + // 排除微软Office进程 + if (microsoftOfficeNames.Any(name => pname.Contains(name))) + { + continue; + } + + // 精确匹配WPS进程名 + bool isWpsProcess = exactWpsNames.Any(name => pname.Equals(name) || pname.StartsWith(name + ".")); + + // 额外验证:检查进程路径 + if (isWpsProcess) + { + try + { + var processPath = process.MainModule?.FileName?.ToLower() ?? ""; + if (processPath.Contains("kingsoft") || processPath.Contains("wps office")) + { + wpsProcesses.Add(process); + LogHelper.WriteLogToFile($"检测到WPS进程: {process.ProcessName} (PID: {process.Id})", LogHelper.LogType.Trace); + } + } + catch + { + // 无法访问进程路径时,基于进程名判断 + if (exactWpsNames.Contains(pname)) + { + wpsProcesses.Add(process); + LogHelper.WriteLogToFile($"基于进程名检测到WPS进程: {process.ProcessName} (PID: {process.Id})", LogHelper.LogType.Trace); + } + } + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"检查进程{process.ProcessName}失败: {ex}", LogHelper.LogType.Error); + } + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"获取WPS进程失败: {ex}", LogHelper.LogType.Error); + } + + LogHelper.WriteLogToFile($"共检测到{wpsProcesses.Count}个WPS进程", LogHelper.LogType.Trace); + return wpsProcesses; + } + + private bool IsForegroundWpsWindowStillActive() + { + try + { + var currentTime = DateTime.Now; + var currentForegroundWindow = GetForegroundWpsWindow(); + + // 检查窗口状态是否发生变化 + if (_lastForegroundWpsWindow != null && currentForegroundWindow != null) + { + if (_lastForegroundWpsWindow.Handle != currentForegroundWindow.Handle || + _lastForegroundWpsWindow.Title != currentForegroundWindow.Title) + { + LogHelper.WriteLogToFile($"前台WPS窗口发生变化: {_lastForegroundWpsWindow.Title} -> {currentForegroundWindow.Title}", LogHelper.LogType.Trace); + } + } + else if (_lastForegroundWpsWindow == null && currentForegroundWindow != null) + { + LogHelper.WriteLogToFile($"检测到新的前台WPS窗口: {currentForegroundWindow.Title}", LogHelper.LogType.Trace); + } + else if (_lastForegroundWpsWindow != null && currentForegroundWindow == null) + { + LogHelper.WriteLogToFile($"前台WPS窗口已消失: {_lastForegroundWpsWindow.Title}", LogHelper.LogType.Trace); + } + + // 更新记录 + _lastForegroundWpsWindow = currentForegroundWindow; + _lastWindowCheckTime = currentTime; + + if (currentForegroundWindow != null) + { + if (IsWindow(currentForegroundWindow.Handle) && IsWindowVisible(currentForegroundWindow.Handle)) + { + return true; + } + } + + // 检查所有WPS进程的活跃窗口 + var wpsProcesses = GetWpsProcesses(); + foreach (var process in wpsProcesses) + { + var windows = GetWpsWindowsByProcess(process.Id); + if (windows.Any(w => w.IsVisible && !w.IsMinimized && w.Handle == GetForegroundWindow())) + { + return true; + } + } + + return false; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"检查前台WPS窗口状态失败: {ex}", LogHelper.LogType.Error); + return false; + } + } + + private List GetWpsWindowsByProcess(int processId) + { + var wpsWindows = new List(); + + try + { + EnumWindows((hWnd, lParam) => + { + try + { + if (!IsWindow(hWnd)) return true; + + uint windowProcessId; + GetWindowThreadProcessId(hWnd, out windowProcessId); + + if ((int)windowProcessId == processId) + { + var windowInfo = GetWindowInfo(hWnd); + if (IsWpsWindow(windowInfo)) + { + wpsWindows.Add(windowInfo); + } + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"枚举窗口时出错: {ex}", LogHelper.LogType.Error); + } + return true; + }, IntPtr.Zero); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"获取WPS窗口失败: {ex}", LogHelper.LogType.Error); + } + + return wpsWindows; + } + #endregion + + #region Window Handle Methods + /// + /// 获取PPT窗口句柄 + /// + /// 窗口句柄,如果获取失败返回 IntPtr.Zero + public IntPtr GetPptHwnd() + { + IntPtr ret = IntPtr.Zero; + + // 方法1: 尝试从 SlideShowWindow 获取 + ret = GetPptHwndFromSlideShowWindow(_pptSlideShowWindow); + + if (ret == IntPtr.Zero) + { + // 方法2: 通过窗口标题匹配获取(备用方法) + try + { + if (_pptActivePresentation != null && PPTApplication != null) + { + dynamic pres = _pptActivePresentation; + string fullName = pres.FullName; + string appName = PPTApplication.Name; + ret = GetPptHwndWin32(fullName, appName); + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"获取PPT窗口句柄失败: {ex.Message}", LogHelper.LogType.Trace); + } + } + + return ret; + } + + /// + /// 从 SlideShowWindow 对象获取窗口句柄 + /// + private IntPtr GetPptHwndFromSlideShowWindow(object pptSlideShowWindowObj) + { + IntPtr hwnd = IntPtr.Zero; + if (pptSlideShowWindowObj == null) return IntPtr.Zero; + + try + { + // 尝试强类型转换 + Microsoft.Office.Interop.PowerPoint.SlideShowWindow slideWindow = + pptSlideShowWindowObj as Microsoft.Office.Interop.PowerPoint.SlideShowWindow; + + if (slideWindow != null) + { + int hwndVal = slideWindow.HWND; + hwnd = new IntPtr(hwndVal); + LogHelper.WriteLogToFile($"从SlideShowWindow获取窗口句柄成功: {hwnd}", LogHelper.LogType.Trace); + } + else + { + // 如果强类型转换失败,尝试使用dynamic + try + { + dynamic ssw = pptSlideShowWindowObj; + int hwndVal = ssw.HWND; + hwnd = new IntPtr(hwndVal); + LogHelper.WriteLogToFile($"从SlideShowWindow获取窗口句柄成功(dynamic): {hwnd}", LogHelper.LogType.Trace); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"从SlideShowWindow获取窗口句柄失败: {ex.Message}", LogHelper.LogType.Trace); + } + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"从SlideShowWindow获取窗口句柄异常: {ex.Message}", LogHelper.LogType.Trace); + } + + return hwnd; + } + + /// + /// 通过窗口标题匹配获取PPT窗口句柄(备用方法) + /// + private IntPtr GetPptHwndWin32(string presFullName, string appName) + { + try + { + // 步骤 A: 基础参数校验 + if (string.IsNullOrWhiteSpace(presFullName) || string.IsNullOrWhiteSpace(appName)) + { + return IntPtr.Zero; + } + + // 步骤 B: 提取关键信息 (应用类型 & 文件名) + string targetAppKeyword; + if (appName.IndexOf("WPS", StringComparison.OrdinalIgnoreCase) >= 0) + { + targetAppKeyword = "WPS"; + } + else if (appName.IndexOf("PowerPoint", StringComparison.OrdinalIgnoreCase) >= 0) + { + targetAppKeyword = "PowerPoint"; + } + else + { + // 既不是 WPS 也不是 PowerPoint,视为不支持 + return IntPtr.Zero; + } + + // 从路径中安全提取文件名(包含扩展名),如 "myppt.pptx" + string targetFileName = System.IO.Path.GetFileName(presFullName); + if (string.IsNullOrWhiteSpace(targetFileName)) + { + return IntPtr.Zero; + } + + // 步骤 C: 枚举窗口并查找匹配项 + List candidates = new List(); + + // 调用 EnumWindows,使用 Lambda 表达式直接嵌入回调逻辑 + EnumWindows((hWnd, lParam) => + { + try + { + // [安全过滤] 1. 忽略不可见窗口 + if (!IsWindowVisible(hWnd)) return true; + + // [安全获取] 2. 获取窗口标题长度 + int length = GetWindowTextLength(hWnd); + if (length == 0) return true; + + // [安全获取] 3. 获取窗口标题文本 + StringBuilder sb = new StringBuilder(length + 1); + GetWindowText(hWnd, sb, sb.Capacity); + string title = sb.ToString(); + + if (string.IsNullOrWhiteSpace(title)) return true; + + // [核心匹配] 4. 判断标题是否同时包含 "文件名" 和 "应用关键字" + bool hasFileName = title.IndexOf(targetFileName, StringComparison.OrdinalIgnoreCase) >= 0; + bool hasAppKey = title.IndexOf(targetAppKeyword, StringComparison.OrdinalIgnoreCase) >= 0; + + if (hasFileName && hasAppKey) + { + candidates.Add(hWnd); + } + + // 继续枚举其他窗口 + return true; + } + catch + { + // 回调内部容错,忽略单个窗口获取信息的错误,继续枚举 + return true; + } + }, IntPtr.Zero); + + // 步骤 D: 结果判定 + // 只有当匹配到的窗口数量 唯一 (Count == 1) 时才返回句柄 + // 0 个表示没找到,>1 个表示有歧义(无法确定是哪一个),均视为失败 + if (candidates.Count == 1) + { + LogHelper.WriteLogToFile($"通过窗口标题匹配获取窗口句柄成功: {candidates[0]}", LogHelper.LogType.Trace); + return candidates[0]; + } + else if (candidates.Count > 1) + { + LogHelper.WriteLogToFile($"通过窗口标题匹配找到多个候选窗口({candidates.Count}个),无法确定唯一窗口", LogHelper.LogType.Trace); + } + + return IntPtr.Zero; + } + catch (Exception ex) + { + // 发生任何不可预知的异常(如Path解析错误等),返回安全值 + LogHelper.WriteLogToFile($"GetPptHwndWin32异常: {ex.Message}", LogHelper.LogType.Trace); + return IntPtr.Zero; + } + } + #endregion + + #region Dispose + public void Dispose() + { + if (!_disposed) + { + StopMonitoring(); + StopWpsProcessCheckTimer(); + + _disposed = true; } } #endregion } -} \ No newline at end of file +} + + + diff --git a/Ink Canvas/MainWindow_cs/MW_PPT.cs b/Ink Canvas/MainWindow_cs/MW_PPT.cs index 595cd5bb..73e62c9f 100644 --- a/Ink Canvas/MainWindow_cs/MW_PPT.cs +++ b/Ink Canvas/MainWindow_cs/MW_PPT.cs @@ -724,23 +724,13 @@ namespace Ink_Canvas LogHelper.WriteLogToFile("PPT连接已断开", LogHelper.LogType.Event); _singlePPTInkManager?.ClearAllStrokes(); _exitPPTModeAfterDisconnectTimer?.Stop(); - _exitPPTModeAfterDisconnectTimer = new DispatcherTimer - { - Interval = TimeSpan.FromMilliseconds(ExitPPTModeAfterDisconnectDelayMs) - }; - _exitPPTModeAfterDisconnectTimer.Tick += (s, e) => - { - _exitPPTModeAfterDisconnectTimer?.Stop(); - _exitPPTModeAfterDisconnectTimer = null; - if (_pptManager?.IsConnected != true) - { - _pptUIManager?.UpdateSlideShowStatus(false); - _pptUIManager?.UpdateSidebarExitButtons(false); - ResetPPTStateVariables(); - _ = HandleManualSlideShowEnd(); - } - }; - _exitPPTModeAfterDisconnectTimer.Start(); + _exitPPTModeAfterDisconnectTimer = null; + _pptUIManager?.UpdateSlideShowStatus(false); + _pptUIManager?.UpdateSidebarExitButtons(false); + ResetPPTStateVariables(); + _ = HandleManualSlideShowEnd(); + if (Settings.PowerPointSettings.UseRotPptLink) + _pptManager?.ReloadConnection(); } }); } @@ -921,14 +911,36 @@ namespace Ink_Canvas string presentationName = null; Presentation activePresentation = null; - if (wn?.View != null && wn.Presentation != null) + if (wn != null) { - activePresentation = wn.Presentation; - currentSlide = wn.View.CurrentShowPosition; - totalSlides = activePresentation.Slides.Count; - presentationName = activePresentation.Name; + try + { + if (wn.View != null && wn.Presentation != null) + { + activePresentation = wn.Presentation; + currentSlide = wn.View.CurrentShowPosition; + totalSlides = activePresentation.Slides.Count; + presentationName = activePresentation.Name; + } + } + catch (COMException comEx) + { + var hr = (uint)comEx.HResult; + activePresentation = null; + currentSlide = 0; + totalSlides = 0; + presentationName = null; + } + catch (Exception ex) + { + activePresentation = null; + currentSlide = 0; + totalSlides = 0; + presentationName = null; + } } - else + + if (activePresentation == null) { activePresentation = _pptManager?.GetCurrentActivePresentation() as Presentation; currentSlide = _pptManager?.GetCurrentSlideNumber() ?? 0; @@ -1134,10 +1146,41 @@ namespace Ink_Canvas { try { - if (wn?.View == null || wn.Presentation == null) return; + int currentSlide = 0; + int totalSlides = 0; - int currentSlide = wn.View.CurrentShowPosition; - int totalSlides = wn.Presentation.Slides.Count; + if (wn != null) + { + try + { + if (wn.View != null) + { + currentSlide = wn.View.CurrentShowPosition; + } + } + catch (COMException comEx) + { + var hr = (uint)comEx.HResult; + LogHelper.WriteLogToFile( + $"通过 SlideShowWindow.View 获取当前页失败: {comEx.Message} (HR: 0x{hr:X8}),将回退到 PPT 管理器获取", + LogHelper.LogType.Warning); + currentSlide = 0; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile( + $"通过 SlideShowWindow.View 获取当前页时发生异常,将回退到 PPT 管理器获取: {ex}", + LogHelper.LogType.Warning); + currentSlide = 0; + } + } + + if (currentSlide <= 0) + { + currentSlide = _pptManager?.GetCurrentSlideNumber() ?? 0; + } + + totalSlides = _pptManager?.SlidesCount ?? 0; if (currentSlide == _previousSlideID) return;