using Microsoft.Office.Interop.PowerPoint; using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Timers; using Timer = System.Timers.Timer; namespace Ink_Canvas.Helpers { public class ROTPPTManager : IPPTLinkManager { #region Events public event Action SlideShowBegin; public event Action SlideShowNextSlide; public event Action SlideShowEnd; public event Action PresentationOpen; public event Action PresentationClose; public event Action PPTConnectionChanged; public event Action SlideShowStateChanged; #endregion #region Properties 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; } private volatile bool _cachedIsConnected; private volatile bool _cachedIsInSlideShow; public bool IsConnected => _cachedIsConnected && PPTApplication != null; public bool IsInSlideShow => _cachedIsInSlideShow && _pptSlideShowWindow != null; public bool IsSupportWPS { get; set; } = false; public bool SkipAnimationsWhenNavigating { get; set; } = false; #endregion #region Private Fields 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; private int _reconnectFailureCount = 0; private DateTime _nextReconnectAttemptUtc = DateTime.MinValue; #endregion #region Constructor & Initialization public ROTPPTManager() { } public void StartMonitoring() { if (_disposed) return; lock (_monitoringLock) { if (_monitoringThread != null && _monitoringThread.IsAlive) { return; } _shouldStop = false; _monitoringThread = new Thread(PptComService) { IsBackground = true, Name = "PPTMonitoringThread" }; _monitoringThread.Start(); } } public void StopMonitoring() { lock (_monitoringLock) { _shouldStop = true; if (_monitoringThread != null && _monitoringThread.IsAlive) { // 等待线程退出,最多等待2秒 if (!_monitoringThread.Join(2000)) { LogHelper.WriteLogToFile("等待监控线程退出超时", LogHelper.LogType.Warning); } } DisconnectFromPPT(); } } private int _isReloading; // 0 = idle, 1 = in reload private volatile bool _suppressDisconnectEvent; public void ReloadConnection() { if (_disposed) return; if (Interlocked.CompareExchange(ref _isReloading, 1, 0) != 0) { return; } try { LogHelper.WriteLogToFile("[ROT] 执行热重载:强制断开并重新连接", LogHelper.LogType.Event); lock (_monitoringLock) { _shouldStop = true; if (_monitoringThread != null && _monitoringThread.IsAlive) { if (!_monitoringThread.Join(2000)) { LogHelper.WriteLogToFile("等待监控线程退出超时(热重载)", LogHelper.LogType.Warning); } } _suppressDisconnectEvent = true; try { DisconnectFromPPT(); } finally { _suppressDisconnectEvent = false; } _monitoringThread = null; _shouldStop = false; _isModuleUnloading = false; } StartMonitoring(); } finally { Interlocked.Exchange(ref _isReloading, 0); } } #endregion #region Connection Management private void PptComService() { _bindingEvents = false; _lastPolledSlideNumber = -1; _polling = 0; int tempTotalPage = -1; try { 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); try { ConnectToPPT(bestApp); } catch (Exception ex) { 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) { DisconnectFromPPT(); } } 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)) { DisconnectFromPPT(); continue; } } catch (System.Runtime.InteropServices.InvalidComObjectException) { // COM对象已失效 DisconnectFromPPT(); continue; } catch (COMException ex) when ((uint)ex.ErrorCode == 0x8001010A) { } 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($"检查演示文稿状态失败: {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 { 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); } catch (COMException ex) when ((uint)ex.ErrorCode == 0x8001010A) { } 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) { PPTROTConnectionHelper.SafeReleaseComObject(_pptSlideShowWindow); _pptSlideShowWindow = null; _lastPolledSlideNumber = -1; _polling = 1; SlidesCount = 0; _lastSlideShowState = false; _cachedIsInSlideShow = 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) { 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(); } } private Microsoft.Office.Interop.PowerPoint.Application TryConnectToPowerPoint() { try { var pptApp = PPTROTConnectionHelper.TryConnectViaROT(IsSupportWPS); return pptApp; } catch (Exception ex) { LogHelper.WriteLogToFile($"ROT连接PowerPoint异常: {ex}", LogHelper.LogType.Error); return null; } } private void ConnectToPPT(object pptApp) { try { if (pptApp != null) { PPTApplication = pptApp; } try { dynamic pptAppDynamic = PPTApplication; try { _pptActivePresentation = pptAppDynamic.ActivePresentation; _updateTime = DateTime.Now; _lastPolledSlideNumber = -1; } catch (Exception ex) { LogHelper.WriteLogToFile($"访问ActivePresentation失败: {ex.Message},继续使用轮询模式", LogHelper.LogType.Warning); _pptActivePresentation = null; _updateTime = DateTime.Now; _lastPolledSlideNumber = -1; } int tempTotalPage = -1; if (_pptActivePresentation != null) { try { _pptSlideShowWindow = _pptActivePresentation.SlideShowWindow; tempTotalPage = GetTotalSlideIndex(_pptActivePresentation); } catch { tempTotalPage = -1; } } else { tempTotalPage = -1; } 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; } } try { if (PPTApplication != null) { 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; _forcePolling = false; } else { _bindingEvents = false; _forcePolling = true; } } catch (Exception) { _bindingEvents = false; _forcePolling = true; } } else { _bindingEvents = false; _forcePolling = true; } } catch (Exception ex) { _bindingEvents = false; _forcePolling = true; LogHelper.WriteLogToFile($"无法注册事件: {ex.Message}", LogHelper.LogType.Warning); } if (_pptActivePresentation != null) { UpdateCurrentPresentationInfo(); } bool wasConnected = _cachedIsConnected; _cachedIsConnected = true; if (!wasConnected) { 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() { dynamic slideShowWindows = null; dynamic slideShowWindow = null; try { if (!IsConnected || !IsInSlideShow || PPTApplication == null) return; if (!Marshal.IsComObject(PPTApplication)) return; dynamic app = PPTApplication; slideShowWindows = app.SlideShowWindows; if (slideShowWindows == null || slideShowWindows.Count == 0) return; slideShowWindow = slideShowWindows[1]; if (slideShowWindow == null) return; SlideShowBegin?.Invoke(slideShowWindow); } catch (COMException comEx) { var hr = (uint)comEx.HResult; } catch (Exception) { } finally { if (slideShowWindow != null && !PPTROTConnectionHelper.AreComObjectsEqual(_pptSlideShowWindow, slideShowWindow)) { SafeReleaseComObject(slideShowWindow); } if (slideShowWindows != null) { SafeReleaseComObject(slideShowWindows); } } } private void ApplyReconnectBackoff() { _reconnectFailureCount = Math.Min(_reconnectFailureCount + 1, 4); int delayMs = 250 * (1 << (_reconnectFailureCount - 1)); _nextReconnectAttemptUtc = DateTime.UtcNow.AddMilliseconds(delayMs); } 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) { 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) { } _bindingEvents = false; } } catch { } } private static bool IsObjectNull(object comObject) { return ReferenceEquals(comObject, null); } private void DisconnectFromPPT() { object pptApplication = PPTApplication; object activePresentation = _pptActivePresentation; object slideShowWindow = _pptSlideShowWindow; object currentPresentation = CurrentPresentation; object currentSlides = CurrentSlides; object currentSlide = CurrentSlide; if (IsObjectNull(pptApplication) && IsObjectNull(activePresentation) && IsObjectNull(slideShowWindow) && IsObjectNull(currentPresentation) && IsObjectNull(currentSlides) && IsObjectNull(currentSlide)) { return; } _isModuleUnloading = true; try { UnbindEvents(); if (activePresentation != null) { try { PresentationClose?.Invoke(activePresentation); } catch (Exception ex) { LogHelper.WriteLogToFile($"触发PresentationClose事件失败: {ex.Message}", LogHelper.LogType.Warning); } } SafeReleaseComObject(slideShowWindow, "_pptSlideShowWindow"); SafeReleaseComObject(activePresentation, "_pptActivePresentation"); SafeReleaseComObject(currentSlide, "CurrentSlide"); SafeReleaseComObject(currentSlides, "CurrentSlides"); SafeReleaseComObject(currentPresentation, "CurrentPresentation"); SafeFinalReleaseComObject(pptApplication, "PPTApplication"); PPTApplication = null; _pptActivePresentation = null; _pptSlideShowWindow = null; CurrentPresentation = null; CurrentSlides = null; CurrentSlide = null; SlidesCount = 0; _polling = 0; _forcePolling = true; _bindingEvents = false; bool wasConnected = _cachedIsConnected; _cachedIsConnected = false; _cachedIsInSlideShow = false; if (wasConnected && !_suppressDisconnectEvent) { PPTConnectionChanged?.Invoke(false); } LogHelper.WriteLogToFile("已断开PPT连接,并显式释放所有COM对象", LogHelper.LogType.Event); ThreadPool.QueueUserWorkItem(_ => { try { Thread.Sleep(2000); _isModuleUnloading = false; } catch (Exception ex) { LogHelper.WriteLogToFile($"重新启用PPT联动模块失败: {ex}", LogHelper.LogType.Error); _isModuleUnloading = false; } }); } catch (Exception ex) { LogHelper.WriteLogToFile($"断开PPT连接失败: {ex}", LogHelper.LogType.Error); _isModuleUnloading = false; } } /// /// 安全释放COM对象 /// private void SafeReleaseComObject(object comObject) { try { if (comObject != null && Marshal.IsComObject(comObject)) { Marshal.ReleaseComObject(comObject); } } catch { } } private void SafeReleaseComObject(object comObject, string objectName) { try { if (comObject != null) { // 检查是否为有效的COM对象 if (!Marshal.IsComObject(comObject)) { return; // 不是COM对象,无需释放 } // 检查COM对象是否仍然有效 try { // 尝试访问对象以验证其有效性 var _ = Marshal.GetIUnknownForObject(comObject); Marshal.Release(_); } catch (InvalidComObjectException) { // COM对象已失效,直接返回 return; } catch (COMException comEx) when (IsIgnorableDisconnectComException(comEx)) { return; } int refCount = Marshal.ReleaseComObject(comObject); } } catch (InvalidComObjectException) { // COM对象已失效,这是正常的,无需记录错误 } catch (COMException comEx) { var hr = (uint)comEx.HResult; var logType = IsIgnorableDisconnectComException(comEx) ? LogHelper.LogType.Trace : LogHelper.LogType.Warning; LogHelper.WriteLogToFile($"释放COM对象 {objectName} 时COM异常: {comEx.Message} (HR: 0x{hr:X8})", logType); } catch (Exception ex) { LogHelper.WriteLogToFile($"释放COM对象 {objectName} 时发生异常: {ex}", LogHelper.LogType.Warning); } } /// /// 终态释放 COM 对象:先尝试 FinalReleaseComObject 一次性归零引用计数,失败时回退为循环 ReleaseComObject。 /// private void SafeFinalReleaseComObject(object comObject, string objectName) { if (comObject == null) return; try { if (!Marshal.IsComObject(comObject)) return; try { Marshal.FinalReleaseComObject(comObject); } catch { try { int refCount = Marshal.ReleaseComObject(comObject); while (refCount > 0) { refCount = Marshal.ReleaseComObject(comObject); } } catch { } } } catch (InvalidComObjectException) { } catch (COMException comEx) when (IsIgnorableDisconnectComException(comEx)) { } catch (Exception ex) { LogHelper.WriteLogToFile($"最终释放COM对象 {objectName} 时发生异常: {ex.Message}", LogHelper.LogType.Warning); } } private static bool IsIgnorableDisconnectComException(COMException comEx) { var hr = (uint)comEx.HResult; return hr == 0x800706BA || // RPC server unavailable hr == 0x80010108 || // object disconnected from clients hr == 0x8001010D || // server died hr == 0x800706BE; // remote procedure call failed } private void UpdateCurrentPresentationInfo() { object activeWindow = null; object view = null; object selection = null; object slideRange = null; try { if (PPTApplication != null && Marshal.IsComObject(PPTApplication)) { 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) { } } 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 && hr != 0x80048240) { 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 (InvalidComObjectException) { LogHelper.WriteLogToFile($"更新演示文稿信息失败: COM对象已失效", LogHelper.LogType.Warning); CurrentPresentation = null; CurrentSlides = null; CurrentSlide = null; SlidesCount = 0; } catch (Exception ex) { LogHelper.WriteLogToFile($"更新演示文稿信息失败: {ex}", LogHelper.LogType.Error); CurrentPresentation = null; CurrentSlides = null; CurrentSlide = null; SlidesCount = 0; } finally { SafeReleaseComObject(slideRange); SafeReleaseComObject(selection); SafeReleaseComObject(view); SafeReleaseComObject(activeWindow); } } #endregion #region Event Handlers private void OnPresentationOpen(Presentation pres) { try { 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 { 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) { } _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; // 重置页码跟踪 try { if (_pptActivePresentation != null) { int currentPage = GetCurrentSlideIndex(_pptSlideShowWindow); int totalPage = GetTotalSlideIndex(_pptActivePresentation); if (currentPage >= totalPage) _polling = 1; else _polling = 0; SlidesCount = totalPage; _lastPolledSlideNumber = currentPage; // 初始化页码跟踪 } } catch { _polling = 1; _lastPolledSlideNumber = -1; } UpdateCurrentPresentationInfo(); if (!_lastSlideShowState) { _lastSlideShowState = true; _cachedIsInSlideShow = 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; _cachedIsInSlideShow = 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) { var hr = (uint)comEx.HResult; if (hr == 0x8001010E || hr == 0x80004005) { DisconnectFromPPT(); } LogHelper.WriteLogToFile($"跳转到幻灯片{slideNumber}失败: {comEx.Message}", LogHelper.LogType.Error); return false; } catch (Exception ex) { LogHelper.WriteLogToFile($"跳转到幻灯片{slideNumber}失败: {ex}", LogHelper.LogType.Error); return false; } finally { 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; } } public bool TryEndSlideShow() { object slideShowWindows = null; object slideShowWindow = null; object view = null; try { if (!IsConnected || !IsInSlideShow || PPTApplication == null) return false; if (!Marshal.IsComObject(PPTApplication)) return false; slideShowWindows = PPTApplication.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; 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) { var hr = (uint)comEx.HResult; if (hr == 0x8001010E || hr == 0x80004005) { // COM对象已失效,触发断开连接 DisconnectFromPPT(); } LogHelper.WriteLogToFile($"开始幻灯片放映失败: {comEx.Message}", LogHelper.LogType.Error); return false; } catch (Exception ex) { LogHelper.WriteLogToFile($"开始幻灯片放映失败: {ex}", LogHelper.LogType.Error); return false; } } /// /// 获取当前活跃的演示文稿 /// public object GetCurrentActivePresentation() { 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; // 检查COM对象是否仍然有效 try { var _ = Marshal.GetIUnknownForObject(PPTApplication); Marshal.Release(_); } catch (InvalidComObjectException) { // COM对象已失效 return null; } if (IsInSlideShow) { dynamic pptAppForSSW = PPTApplication; slideShowWindows = pptAppForSSW.SlideShowWindows; if (slideShowWindows != null) { dynamic ssw = slideShowWindows; if (ssw.Count > 0) { slideShowWindow = ssw[1]; if (slideShowWindow != null) { dynamic sswObj = slideShowWindow; view = sswObj.View; if (view != null) { dynamic viewObj = view; slide = viewObj.Slide; if (slide != null) { dynamic slideObj = slide; presentation = slideObj.Parent; return presentation; } } } } } } activeWindow = PPTApplication.ActiveWindow; if (activeWindow != null) { dynamic aw = activeWindow; presentation = aw.Presentation; if (presentation != null) { return presentation; } } return CurrentPresentation; } catch (COMException comEx) { var hr = (uint)comEx.HResult; if (hr == 0x8001010E || hr == 0x80004005) { DisconnectFromPPT(); } if (hr == 0x80048240) { return null; } LogHelper.WriteLogToFile($"获取当前活跃演示文稿失败: {comEx.Message}", LogHelper.LogType.Warning); return CurrentPresentation; } catch (Exception ex) { 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); } } private int GetTotalSlideIndex(dynamic presentation) { try { 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 { 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; 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 WPS Process Management private void RecordWpsProcessForManagement() { if (!IsSupportWPS || PPTApplication == null) return; try { 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; StartWpsProcessCheckTimer(); } else { LogHelper.WriteLogToFile("未能检测到WPS进程", LogHelper.LogType.Warning); } } catch (Exception ex) { 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(); } private void OnWpsProcessCheckTimerElapsed(object sender, ElapsedEventArgs e) { if (!IsSupportWPS) { StopWpsProcessCheckTimer(); return; } try { if (_wpsProcess == null || !_hasWpsProcessId) { StopWpsProcessCheckTimer(); return; } _wpsProcess.Refresh(); _wpsProcessCheckCount++; if (_wpsProcess.HasExited) { StopWpsProcessCheckTimer(); return; } // 检查前台WPS窗口是否存在 bool isForegroundWpsWindowActive = IsForegroundWpsWindowStillActiveOptimized(); if (isForegroundWpsWindowActive) { if (_wpsProcessCheckCount % 5 == 0) // 每10秒记录一次日志 { } return; } // 多重验证确保准确性 if (!PerformMultipleWpsWindowChecks()) { 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()) { 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)) { return false; } } // 第三重验证:检查任务栏中的WPS窗口 if (HasWpsWindowInTaskbar()) { 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) { 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; } // 第二步:释放 pptActDoc 对象(CurrentPresentation) Presentation pptActDoc = CurrentPresentation; if (pptActDoc != null) { Marshal.ReleaseComObject(pptActDoc); pptActDoc = null; CurrentPresentation = null; } // 第三步:释放 pptApp 对象(PPTApplication) if (PPTApplication != null) { try { Marshal.ReleaseComObject(PPTApplication); PPTApplication = null; } catch (Exception) { PPTApplication = null; } } // 第四步:强制垃圾回收及等待终结器执行 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(); bool wasConnected = _cachedIsConnected; _cachedIsConnected = false; _cachedIsInSlideShow = false; if (wasConnected) { 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; } #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); } } catch { // 无法访问进程路径时,基于进程名判断 if (exactWpsNames.Contains(pname)) { wpsProcesses.Add(process); } } } } catch (Exception ex) { LogHelper.WriteLogToFile($"检查进程{process.ProcessName}失败: {ex}", LogHelper.LogType.Error); } } } catch (Exception ex) { LogHelper.WriteLogToFile($"获取WPS进程失败: {ex}", LogHelper.LogType.Error); } 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) { } } else if (_lastForegroundWpsWindow == null && currentForegroundWindow != null) { } else if (_lastForegroundWpsWindow != null && currentForegroundWindow == null) { } // 更新记录 _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) { } } 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); } else { // 如果强类型转换失败,尝试使用dynamic try { dynamic ssw = pptSlideShowWindowObj; int hwndVal = ssw.HWND; hwnd = new IntPtr(hwndVal); } catch (Exception) { } } } catch (Exception) { } 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) { return candidates[0]; } else if (candidates.Count > 1) { } return IntPtr.Zero; } catch (Exception) { // 发生任何不可预知的异常(如Path解析错误等),返回安全值 return IntPtr.Zero; } } #endregion #region Dispose public void Dispose() { if (!_disposed) { StopMonitoring(); StopWpsProcessCheckTimer(); _disposed = true; } } #endregion } }