Files
community/Ink Canvas/Helpers/ROTPPTManager.cs
T

2998 lines
113 KiB
C#
Raw Normal View History

2026-02-12 22:33:04 +08:00
using Microsoft.Office.Interop.PowerPoint;
using System;
2026-02-24 12:21:46 +08:00
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
2026-02-12 22:33:04 +08:00
using System.Runtime.InteropServices;
2026-02-24 12:21:46 +08:00
using System.Text;
2026-02-13 09:29:24 +08:00
using System.Threading;
2026-02-12 22:33:04 +08:00
using System.Timers;
using Timer = System.Timers.Timer;
namespace Ink_Canvas.Helpers
{
public class ROTPPTManager : IPPTLinkManager
{
#region Events
public event Action<object> SlideShowBegin;
public event Action<object> SlideShowNextSlide;
public event Action<object> SlideShowEnd;
public event Action<object> PresentationOpen;
public event Action<object> PresentationClose;
public event Action<bool> PPTConnectionChanged;
public event Action<bool> SlideShowStateChanged;
#endregion
#region Properties
2026-02-24 12:21:46 +08:00
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; }
2026-02-12 22:33:04 +08:00
2026-05-01 00:33:35 +08:00
private volatile bool _cachedIsConnected;
private volatile bool _cachedIsInSlideShow;
2026-02-12 22:33:04 +08:00
2026-05-01 00:33:35 +08:00
public bool IsConnected => _cachedIsConnected && PPTApplication != null;
2026-03-03 16:04:20 +08:00
2026-05-01 00:33:35 +08:00
public bool IsInSlideShow => _cachedIsInSlideShow && _pptSlideShowWindow != null;
2026-02-12 22:33:04 +08:00
public bool IsSupportWPS { get; set; } = false;
2026-02-22 10:50:11 +08:00
public bool SkipAnimationsWhenNavigating { get; set; } = false;
2026-02-12 22:33:04 +08:00
#endregion
#region Private Fields
2026-02-24 12:21:46 +08:00
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;
2026-02-12 22:33:04 +08:00
private bool _lastSlideShowState;
private readonly object _lockObject = new object();
private bool _disposed;
2026-03-03 16:04:20 +08:00
2026-02-24 12:21:46 +08:00
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;
2026-04-30 17:58:28 +08:00
private int _reconnectFailureCount = 0;
private DateTime _nextReconnectAttemptUtc = DateTime.MinValue;
2026-02-12 22:33:04 +08:00
#endregion
2026-02-24 12:21:46 +08:00
#region Constructor & Initialization
2026-02-12 22:33:04 +08:00
public ROTPPTManager()
{
}
public void StartMonitoring()
{
if (_disposed) return;
2026-02-24 12:21:46 +08:00
lock (_monitoringLock)
{
if (_monitoringThread != null && _monitoringThread.IsAlive)
{
return;
}
_shouldStop = false;
_monitoringThread = new Thread(PptComService)
{
IsBackground = true,
Name = "PPTMonitoringThread"
};
_monitoringThread.Start();
}
2026-02-12 22:33:04 +08:00
}
public void StopMonitoring()
{
2026-02-24 12:21:46 +08:00
lock (_monitoringLock)
{
_shouldStop = true;
2026-03-03 16:04:20 +08:00
2026-02-24 12:21:46 +08:00
if (_monitoringThread != null && _monitoringThread.IsAlive)
{
// 等待线程退出,最多等待2秒
if (!_monitoringThread.Join(2000))
{
LogHelper.WriteLogToFile("等待监控线程退出超时", LogHelper.LogType.Warning);
}
}
DisconnectFromPPT();
}
2026-02-12 22:33:04 +08:00
}
2026-02-14 14:39:15 +08:00
2026-05-01 00:33:35 +08:00
private int _isReloading; // 0 = idle, 1 = in reload
private volatile bool _suppressDisconnectEvent;
2026-02-14 14:39:15 +08:00
public void ReloadConnection()
{
if (_disposed) return;
2026-02-24 12:21:46 +08:00
2026-05-01 00:33:35 +08:00
if (Interlocked.CompareExchange(ref _isReloading, 1, 0) != 0)
{
return;
}
2026-02-12 22:33:04 +08:00
2026-05-01 00:33:35 +08:00
try
2026-02-12 22:33:04 +08:00
{
2026-05-01 00:33:35 +08:00
LogHelper.WriteLogToFile("[ROT] 执行热重载:强制断开并重新连接", LogHelper.LogType.Event);
2026-02-12 22:33:04 +08:00
2026-05-01 00:33:35 +08:00
lock (_monitoringLock)
2026-02-24 12:21:46 +08:00
{
2026-05-01 00:33:35 +08:00
_shouldStop = true;
if (_monitoringThread != null && _monitoringThread.IsAlive)
{
if (!_monitoringThread.Join(2000))
{
LogHelper.WriteLogToFile("等待监控线程退出超时(热重载)", LogHelper.LogType.Warning);
}
}
_suppressDisconnectEvent = true;
try
2026-02-24 12:21:46 +08:00
{
2026-05-01 00:33:35 +08:00
DisconnectFromPPT();
}
finally
{
_suppressDisconnectEvent = false;
2026-02-24 12:21:46 +08:00
}
2026-05-01 00:33:35 +08:00
_monitoringThread = null;
_shouldStop = false;
_isModuleUnloading = false;
2026-02-24 12:21:46 +08:00
}
2026-05-01 00:33:35 +08:00
StartMonitoring();
}
finally
{
Interlocked.Exchange(ref _isReloading, 0);
2026-02-12 22:33:04 +08:00
}
}
2026-02-24 12:21:46 +08:00
#endregion
2026-02-12 22:33:04 +08:00
2026-02-24 12:21:46 +08:00
#region Connection Management
private void PptComService()
2026-02-12 22:33:04 +08:00
{
2026-02-24 12:21:46 +08:00
_bindingEvents = false;
_lastPolledSlideNumber = -1;
_polling = 0;
int tempTotalPage = -1;
try
2026-02-12 22:33:04 +08:00
{
2026-02-24 12:21:46 +08:00
while (!_shouldStop && !_isModuleUnloading)
2026-02-12 22:33:04 +08:00
{
2026-02-24 12:21:46 +08:00
object bestApp = PPTROTConnectionHelper.GetAnyActivePowerPoint(PPTApplication, out int bestPriority, out int targetPriority);
bool needRebind = false;
2026-02-12 22:33:04 +08:00
2026-02-24 12:21:46 +08:00
if (bestApp == null)
2026-02-18 22:10:14 +08:00
{
2026-02-24 12:21:46 +08:00
var appFallback = TryConnectToPowerPoint();
if (appFallback != null)
{
bestApp = appFallback;
bestPriority = 1;
}
2026-02-18 22:10:14 +08:00
}
2026-02-24 12:21:46 +08:00
if (PPTApplication == null && bestApp != null)
2026-02-12 22:33:04 +08:00
{
2026-02-24 12:21:46 +08:00
needRebind = true;
2026-02-12 22:33:04 +08:00
}
2026-02-24 12:21:46 +08:00
else if (PPTApplication != null && bestApp != null && bestPriority > targetPriority)
2026-02-12 22:33:04 +08:00
{
2026-02-24 12:21:46 +08:00
if (!PPTROTConnectionHelper.AreComObjectsEqual(PPTApplication, bestApp))
2026-02-12 22:33:04 +08:00
{
2026-02-24 12:21:46 +08:00
needRebind = true;
2026-02-12 22:33:04 +08:00
}
}
2026-02-24 12:21:46 +08:00
if (needRebind)
2026-02-12 22:33:04 +08:00
{
2026-02-24 12:21:46 +08:00
bool wait = (PPTApplication != null);
2026-02-12 22:33:04 +08:00
DisconnectFromPPT();
2026-02-24 12:21:46 +08:00
if (bestApp != null)
{
if (wait) Thread.Sleep(1000);
2026-02-12 22:33:04 +08:00
2026-02-24 12:21:46 +08:00
try
2026-02-22 22:04:58 +08:00
{
2026-05-01 00:33:35 +08:00
ConnectToPPT(bestApp);
2026-03-03 16:04:20 +08:00
}
catch (Exception ex)
{
2026-02-24 12:21:46 +08:00
LogHelper.WriteLogToFile($"绑定失败: {ex.Message}", LogHelper.LogType.Warning);
DisconnectFromPPT();
}
}
2026-02-12 22:33:04 +08:00
}
2026-02-24 12:21:46 +08:00
else
2026-02-12 22:33:04 +08:00
{
2026-02-24 12:21:46 +08:00
if (bestApp != null && (PPTApplication == null || !PPTROTConnectionHelper.AreComObjectsEqual(PPTApplication, bestApp)))
{
PPTROTConnectionHelper.SafeReleaseComObject(bestApp);
bestApp = null;
}
else if (bestApp == null && PPTApplication != null)
{
DisconnectFromPPT();
}
2026-02-12 22:33:04 +08:00
}
2026-02-24 12:21:46 +08:00
if (PPTApplication != null && _pptActivePresentation != null)
2026-02-12 22:53:05 +08:00
{
2026-02-24 12:21:46 +08:00
dynamic activePresentation = null;
dynamic slideShowWindow = null;
try
{
// 检查COM对象是否仍然有效
if (!System.Runtime.InteropServices.Marshal.IsComObject(PPTApplication))
{
DisconnectFromPPT();
continue;
}
2026-03-03 16:04:20 +08:00
2026-02-24 12:21:46 +08:00
try
{
var _ = System.Runtime.InteropServices.Marshal.GetIUnknownForObject(PPTApplication);
System.Runtime.InteropServices.Marshal.Release(_);
}
catch (System.Runtime.InteropServices.InvalidComObjectException)
{
// COM对象已失效
DisconnectFromPPT();
continue;
}
2026-03-03 16:04:20 +08:00
2026-02-24 12:21:46 +08:00
activePresentation = PPTApplication.ActivePresentation;
if (!PPTROTConnectionHelper.AreComObjectsEqual(_pptActivePresentation, activePresentation))
{
DisconnectFromPPT();
continue;
}
}
catch (System.Runtime.InteropServices.InvalidComObjectException)
2026-02-12 22:53:05 +08:00
{
2026-02-24 12:21:46 +08:00
// COM对象已失效
DisconnectFromPPT();
continue;
2026-02-12 22:53:05 +08:00
}
2026-02-24 12:21:46 +08:00
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;
2026-03-03 16:04:20 +08:00
}
catch (Exception ex)
{
2026-02-24 12:21:46 +08:00
LogHelper.WriteLogToFile($"检查演示文稿状态失败: {ex.Message}", LogHelper.LogType.Warning);
DisconnectFromPPT();
continue;
}
finally
{
PPTROTConnectionHelper.SafeReleaseComObject(activePresentation);
activePresentation = null;
}
2026-02-12 22:33:04 +08:00
2026-02-24 12:21:46 +08:00
bool isSlideShowActive = false;
try
{
activePresentation = PPTApplication.ActivePresentation;
2026-02-12 23:19:55 +08:00
2026-02-24 12:21:46 +08:00
dynamic slideShowWindows = PPTApplication.SlideShowWindows;
int count = 0;
if (slideShowWindows != null)
{
count = slideShowWindows.Count;
}
2026-02-18 22:10:14 +08:00
2026-02-24 12:21:46 +08:00
if (activePresentation != null && count > 0)
2026-02-12 22:33:04 +08:00
{
2026-02-24 12:21:46 +08:00
isSlideShowActive = true;
dynamic activeSlideShowWindow = null;
2026-02-12 22:33:04 +08:00
try
{
2026-05-01 00:33:35 +08:00
activeSlideShowWindow = activePresentation.SlideShowWindow;
2026-02-12 22:33:04 +08:00
}
2026-02-24 12:21:46 +08:00
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;
2026-03-03 16:04:20 +08:00
2026-02-24 12:21:46 +08:00
if (isNewWindow)
{
try
{
OnSlideShowBegin(_pptSlideShowWindow);
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"手动触发SlideShowBegin失败: {ex.Message}", LogHelper.LogType.Warning);
}
}
}
}
}
}
2026-05-01 00:33:35 +08:00
PPTROTConnectionHelper.SafeReleaseComObject(slideShowWindows);
2026-02-24 12:21:46 +08:00
}
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
2026-05-01 00:33:35 +08:00
{
slideShowWindow = _pptActivePresentation.SlideShowWindow;
2026-02-24 12:21:46 +08:00
2026-05-01 00:33:35 +08:00
if (slideShowWindow != null)
{
tempTotalPage = GetTotalSlideIndex(_pptActivePresentation);
}
else
{
tempTotalPage = -1;
}
}
catch (Exception ex)
{
tempTotalPage = -1;
LogHelper.WriteLogToFile($"获取总页数失败: {ex.Message}", LogHelper.LogType.Warning);
}
finally
2026-02-24 12:21:46 +08:00
{
2026-05-01 00:33:35 +08:00
PPTROTConnectionHelper.SafeReleaseComObject(slideShowWindow);
slideShowWindow = null;
2026-02-24 12:21:46 +08:00
}
if (tempTotalPage == -1)
{
2026-05-01 00:33:35 +08:00
_lastPolledSlideNumber = -1;
2026-02-24 12:21:46 +08:00
_polling = 0;
}
else
{
try
{
2026-05-01 00:33:35 +08:00
int currentPage = GetCurrentSlideIndex(_pptSlideShowWindow);
int lastPage = _lastPolledSlideNumber;
if (currentPage >= GetTotalSlideIndex(_pptActivePresentation)) _polling = 1;
else _polling = 0;
2026-02-24 12:21:46 +08:00
2026-05-01 00:33:35 +08:00
if (lastPage != -1 && currentPage != lastPage)
{
try
2026-02-24 12:21:46 +08:00
{
2026-05-01 00:33:35 +08:00
SlideShowNextSlide?.Invoke(_pptSlideShowWindow);
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"触发轮询模式幻灯片切换事件失败: {ex.Message}", LogHelper.LogType.Warning);
2026-02-24 12:21:46 +08:00
}
}
2026-05-01 00:33:35 +08:00
_lastPolledSlideNumber = currentPage;
2026-02-24 12:21:46 +08:00
}
catch (Exception ex)
{
2026-05-01 00:33:35 +08:00
_lastPolledSlideNumber = -1;
2026-02-24 12:21:46 +08:00
_polling = 1;
LogHelper.WriteLogToFile($"获取当前页数失败: {ex}", LogHelper.LogType.Warning);
}
}
2026-05-01 00:33:35 +08:00
_updateTime = DateTime.Now;
}
2026-03-03 16:04:20 +08:00
2026-05-01 00:33:35 +08:00
if (_polling != 0)
2026-02-24 12:21:46 +08:00
{
try
{
2026-05-01 00:33:35 +08:00
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;
2026-02-24 12:21:46 +08:00
}
2026-05-01 00:33:35 +08:00
catch
2026-02-24 12:21:46 +08:00
{
2026-05-01 00:33:35 +08:00
_lastPolledSlideNumber = -1;
2026-02-24 12:21:46 +08:00
}
}
}
2026-05-01 00:33:35 +08:00
else
2026-02-24 12:21:46 +08:00
{
2026-05-01 00:33:35 +08:00
if (_lastSlideShowState)
{
PPTROTConnectionHelper.SafeReleaseComObject(_pptSlideShowWindow);
_pptSlideShowWindow = null;
_lastPolledSlideNumber = -1;
_polling = 1;
SlidesCount = 0;
2026-03-03 16:04:20 +08:00
2026-05-01 00:33:35 +08:00
_lastSlideShowState = false;
_cachedIsInSlideShow = false;
SlideShowStateChanged?.Invoke(false);
2026-03-03 16:04:20 +08:00
2026-05-01 00:33:35 +08:00
if (_pptActivePresentation != null)
{
try
{
OnSlideShowEnd(_pptActivePresentation);
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"触发SlideShowEnd事件失败: {ex.Message}", LogHelper.LogType.Warning);
}
}
2026-02-24 12:21:46 +08:00
}
2026-05-01 00:33:35 +08:00
else
2026-02-24 12:21:46 +08:00
{
2026-05-01 00:33:35 +08:00
_lastPolledSlideNumber = -1;
SlidesCount = 0;
2026-02-24 12:21:46 +08:00
}
}
}
else
{
2026-05-01 00:33:35 +08:00
_lastPolledSlideNumber = -1;
2026-02-24 12:21:46 +08:00
SlidesCount = 0;
}
2026-05-01 00:33:35 +08:00
if (_shouldStop || _isModuleUnloading)
{
break;
}
Thread.Sleep(500);
2026-02-24 12:21:46 +08:00
}
}
2026-05-01 00:33:35 +08:00
catch (InvalidComObjectException ex)
2026-02-24 12:21:46 +08:00
{
2026-05-01 00:33:35 +08:00
LogHelper.WriteLogToFile($"PptComService异常: COM对象已失效 - {ex.Message}", LogHelper.LogType.Error);
DisconnectFromPPT();
2026-02-24 12:21:46 +08:00
}
2026-05-01 00:33:35 +08:00
catch (COMException comEx)
2026-02-24 12:21:46 +08:00
{
2026-05-01 00:33:35 +08:00
var hr = (uint)comEx.HResult;
LogHelper.WriteLogToFile($"PptComService异常: COM异常 (HR: 0x{hr:X8}) - {comEx.Message}", LogHelper.LogType.Error);
DisconnectFromPPT();
2026-02-24 12:21:46 +08:00
}
catch (Exception ex)
{
2026-05-01 00:33:35 +08:00
LogHelper.WriteLogToFile($"PptComService异常: {ex.Message}", LogHelper.LogType.Error);
DisconnectFromPPT();
2026-02-24 12:21:46 +08:00
}
}
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)
2026-03-03 16:04:20 +08:00
{
PPTApplication = pptApp;
2026-02-24 12:21:46 +08:00
}
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;
2026-03-03 16:04:20 +08:00
2026-02-24 12:21:46 +08:00
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;
2026-04-30 17:55:56 +08:00
_forcePolling = false;
2026-02-24 12:21:46 +08:00
}
else
{
_bindingEvents = false;
_forcePolling = true;
}
2026-03-03 16:04:20 +08:00
}
2026-05-01 00:33:35 +08:00
catch (Exception)
2026-03-03 16:04:20 +08:00
{
2026-02-24 12:21:46 +08:00
_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)
{
2026-03-03 16:04:20 +08:00
UpdateCurrentPresentationInfo();
2026-02-24 12:21:46 +08:00
}
2026-05-01 00:33:35 +08:00
bool wasConnected = _cachedIsConnected;
_cachedIsConnected = true;
if (!wasConnected)
{
PPTConnectionChanged?.Invoke(true);
}
2026-02-24 12:21:46 +08:00
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()
{
2026-04-30 17:58:28 +08:00
dynamic slideShowWindows = null;
dynamic slideShowWindow = null;
2026-02-24 12:21:46 +08:00
try
{
2026-05-02 09:47:43 +08:00
if (!IsConnected || PPTApplication == null)
2026-02-24 12:21:46 +08:00
return;
if (!Marshal.IsComObject(PPTApplication))
return;
dynamic app = PPTApplication;
2026-04-30 17:58:28 +08:00
slideShowWindows = app.SlideShowWindows;
2026-02-24 12:21:46 +08:00
if (slideShowWindows == null || slideShowWindows.Count == 0)
return;
2026-04-30 17:58:28 +08:00
slideShowWindow = slideShowWindows[1];
2026-02-24 12:21:46 +08:00
if (slideShowWindow == null)
return;
2026-05-02 09:47:43 +08:00
OnSlideShowBegin(slideShowWindow);
2026-02-24 12:21:46 +08:00
}
catch (COMException comEx)
{
var hr = (uint)comEx.HResult;
}
2026-05-01 00:33:35 +08:00
catch (Exception)
2026-02-24 12:21:46 +08:00
{
}
2026-04-30 17:58:28 +08:00
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);
2026-02-24 12:21:46 +08:00
}
private void UnbindEvents()
{
try
{
if (_bindingEvents && PPTApplication != null)
{
try
{
Microsoft.Office.Interop.PowerPoint.Application app = PPTApplication as Microsoft.Office.Interop.PowerPoint.Application;
2026-03-03 16:04:20 +08:00
2026-02-24 12:21:46 +08:00
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);
}
2026-03-03 16:04:20 +08:00
}
2026-05-01 00:33:35 +08:00
catch (Exception)
2026-03-03 16:04:20 +08:00
{
2026-02-24 12:21:46 +08:00
}
_bindingEvents = false;
}
}
catch { }
}
2026-03-23 16:46:28 +08:00
private static bool IsObjectNull(object comObject)
{
return ReferenceEquals(comObject, null);
}
2026-02-24 12:21:46 +08:00
private void DisconnectFromPPT()
{
2026-03-23 16:46:28 +08:00
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))
2026-02-24 12:21:46 +08:00
{
return;
}
_isModuleUnloading = true;
try
{
UnbindEvents();
2026-05-02 09:47:43 +08:00
// 注意:PresentationClose 只应在真正的关闭(OnPresentationBeforeClose)中抛出。
// DisconnectFromPPT 会被重绑、热重载、瞬时 COM 失效恢复等内部路径调用,
// 此时演示文稿仍处于打开状态,抛 PresentationClose 会让上层错误地清空缓存,
// 而后续重连到同一个演示文稿也不会补发 PresentationOpen。
2026-02-24 12:21:46 +08:00
2026-05-01 00:33:35 +08:00
SafeReleaseComObject(slideShowWindow, "_pptSlideShowWindow");
SafeReleaseComObject(activePresentation, "_pptActivePresentation");
SafeReleaseComObject(currentSlide, "CurrentSlide");
SafeReleaseComObject(currentSlides, "CurrentSlides");
SafeReleaseComObject(currentPresentation, "CurrentPresentation");
SafeFinalReleaseComObject(pptApplication, "PPTApplication");
2026-02-24 12:21:46 +08:00
PPTApplication = null;
_pptActivePresentation = null;
_pptSlideShowWindow = null;
CurrentPresentation = null;
CurrentSlides = null;
CurrentSlide = null;
SlidesCount = 0;
_polling = 0;
_forcePolling = true;
_bindingEvents = false;
2026-05-01 00:33:35 +08:00
bool wasConnected = _cachedIsConnected;
_cachedIsConnected = false;
_cachedIsInSlideShow = false;
if (wasConnected && !_suppressDisconnectEvent)
{
PPTConnectionChanged?.Invoke(false);
}
2026-02-24 12:21:46 +08:00
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;
}
}
/// <summary>
/// 安全释放COM对象
/// </summary>
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;
}
2026-02-24 12:21:46 +08:00
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);
2026-02-24 12:21:46 +08:00
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"释放COM对象 {objectName} 时发生异常: {ex}", LogHelper.LogType.Warning);
}
}
2026-05-01 00:33:35 +08:00
/// <summary>
/// 终态释放 COM 对象:先尝试 FinalReleaseComObject 一次性归零引用计数,失败时回退为循环 ReleaseComObject。
/// </summary>
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
}
2026-02-24 12:21:46 +08:00
private void UpdateCurrentPresentationInfo()
{
object activeWindow = null;
object view = null;
object selection = null;
object slideRange = null;
2026-03-03 16:04:20 +08:00
2026-02-24 12:21:46 +08:00
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;
}
2026-03-03 16:04:20 +08:00
2026-02-24 12:21:46 +08:00
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)
{
2026-03-03 16:04:20 +08:00
try
2026-02-24 12:21:46 +08:00
{
2026-03-03 16:04:20 +08:00
var slideCount = CurrentSlides.Count;
if (slideCount > 0)
{
SlidesCount = slideCount;
}
else
{
SlidesCount = 0;
LogHelper.WriteLogToFile("PPT演示文稿页数为0,可能为空演示文稿", LogHelper.LogType.Warning);
}
2026-02-24 12:21:46 +08:00
}
2026-03-03 16:04:20 +08:00
catch (COMException comEx)
2026-02-24 12:21:46 +08:00
{
2026-03-03 16:04:20 +08:00
var hr = (uint)comEx.HResult;
2026-02-24 12:21:46 +08:00
SlidesCount = 0;
2026-03-03 16:04:20 +08:00
LogHelper.WriteLogToFile($"读取PPT页数失败: {comEx.Message} (HR: 0x{hr:X8})", LogHelper.LogType.Warning);
2026-02-24 12:21:46 +08:00
}
2026-03-03 16:04:20 +08:00
try
{
2026-02-24 12:21:46 +08:00
if (IsInSlideShow && _pptSlideShowWindow != null)
{
try
{
dynamic ssw = _pptSlideShowWindow;
view = ssw.View;
if (view != null)
{
dynamic viewObj = view;
CurrentSlide = viewObj.Slide;
}
}
2026-05-01 00:33:35 +08:00
catch (Exception)
2026-02-24 12:21:46 +08:00
{
2026-03-03 16:04:20 +08:00
}
2026-02-24 12:21:46 +08:00
}
2026-03-03 16:04:20 +08:00
else
2026-02-24 12:21:46 +08:00
{
2026-03-03 16:04:20 +08:00
activeWindow = PPTApplication.ActiveWindow;
if (activeWindow != null)
2026-02-24 12:21:46 +08:00
{
2026-03-03 16:04:20 +08:00
dynamic aw = activeWindow;
selection = aw.Selection;
if (selection != null)
2026-02-24 12:21:46 +08:00
{
2026-03-03 16:04:20 +08:00
dynamic sel = selection;
slideRange = sel.SlideRange;
if (slideRange != null)
2026-02-24 12:21:46 +08:00
{
2026-03-03 16:04:20 +08:00
dynamic sr = slideRange;
int slideNumber = sr.SlideNumber;
if (slideNumber > 0 && slideNumber <= SlidesCount)
{
CurrentSlide = CurrentSlides[slideNumber];
}
2026-02-24 12:21:46 +08:00
}
}
}
2026-03-03 16:04:20 +08:00
if (CurrentSlide == null && SlidesCount > 0)
{
CurrentSlide = CurrentSlides[1];
}
}
}
catch (COMException comEx)
{
var hr = (uint)comEx.HResult;
2026-05-01 00:33:35 +08:00
if (hr != 0x8001010E && hr != 0x80004005 && hr != 0x80048240)
2026-03-03 16:04:20 +08:00
{
LogHelper.WriteLogToFile($"获取当前幻灯片失败: {comEx.Message}", LogHelper.LogType.Warning);
2026-02-24 12:21:46 +08:00
}
2026-03-03 16:04:20 +08:00
if (SlidesCount > 0)
2026-02-24 12:21:46 +08:00
{
CurrentSlide = CurrentSlides[1];
}
}
}
2026-03-03 16:04:20 +08:00
else
2026-02-24 12:21:46 +08:00
{
2026-03-03 16:04:20 +08:00
CurrentPresentation = null;
CurrentSlides = null;
CurrentSlide = null;
SlidesCount = 0;
2026-02-24 12:21:46 +08:00
}
}
}
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
{
2026-05-02 09:47:43 +08:00
try
{
PresentationClose?.Invoke(pres ?? _pptActivePresentation);
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"触发PresentationClose事件失败: {ex.Message}", LogHelper.LogType.Warning);
}
2026-02-24 12:21:46 +08:00
if (_bindingEvents && PPTApplication != null)
{
try
{
Microsoft.Office.Interop.PowerPoint.Application app = PPTApplication as Microsoft.Office.Interop.PowerPoint.Application;
2026-03-03 16:04:20 +08:00
2026-02-24 12:21:46 +08:00
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);
}
}
2026-05-01 00:33:35 +08:00
catch (Exception)
2026-02-12 22:33:04 +08:00
{
2026-02-24 12:21:46 +08:00
}
_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; // 重置页码跟踪
2026-05-01 10:39:04 +08:00
// 兜底:ROT 路径下事件触发时 _pptActivePresentation 可能尚未绑定,
// 先从 SlideShowWindow.Presentation 取,再退回 PPTApplication.ActivePresentation
// 保证后续 SlidesCount 在 UI 读取前一定有效。
if (_pptActivePresentation == null)
{
try
{
if (wn != null)
{
dynamic ssw = wn;
dynamic presFromWindow = ssw.Presentation;
if (presFromWindow != null)
{
_pptActivePresentation = presFromWindow;
}
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"OnSlideShowBegin 从 SlideShowWindow 兜底获取演示文稿失败: {ex.Message}", LogHelper.LogType.Warning);
}
if (_pptActivePresentation == null && PPTApplication != null)
{
try
{
dynamic app = PPTApplication;
_pptActivePresentation = app.ActivePresentation;
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"OnSlideShowBegin 从 PPTApplication 兜底获取演示文稿失败: {ex.Message}", LogHelper.LogType.Warning);
}
}
}
2026-02-24 12:21:46 +08:00
try
{
if (_pptActivePresentation != null)
{
int currentPage = GetCurrentSlideIndex(_pptSlideShowWindow);
int totalPage = GetTotalSlideIndex(_pptActivePresentation);
2026-03-03 16:04:20 +08:00
2026-02-24 12:21:46 +08:00
if (currentPage >= totalPage) _polling = 1;
else _polling = 0;
2026-03-03 16:04:20 +08:00
2026-02-24 12:21:46 +08:00
SlidesCount = totalPage;
_lastPolledSlideNumber = currentPage; // 初始化页码跟踪
}
}
catch
{
_polling = 1;
_lastPolledSlideNumber = -1;
}
UpdateCurrentPresentationInfo();
if (!_lastSlideShowState)
{
_lastSlideShowState = true;
2026-05-01 00:33:35 +08:00
_cachedIsInSlideShow = true;
2026-02-24 12:21:46 +08:00
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);
2026-03-03 16:04:20 +08:00
2026-02-24 12:21:46 +08:00
if (currentPage >= totalPage) _polling = 1;
else _polling = 0;
2026-03-03 16:04:20 +08:00
2026-02-24 12:21:46 +08:00
_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;
2026-05-01 00:33:35 +08:00
_cachedIsInSlideShow = false;
2026-02-24 12:21:46 +08:00
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;
2026-03-03 16:04:20 +08:00
if (slideShowWindows != null)
{
dynamic ssw = slideShowWindows;
2026-02-24 12:21:46 +08:00
object slideShowWindow = ssw[1];
2026-03-03 16:04:20 +08:00
if (slideShowWindow != null)
{
dynamic sswObj = slideShowWindow;
2026-02-24 12:21:46 +08:00
try
{
2026-03-03 16:04:20 +08:00
sswObj.Activate();
2026-02-24 12:21:46 +08:00
}
catch { }
try
{
object view = sswObj.View;
2026-03-03 16:04:20 +08:00
if (view != null)
{
dynamic viewObj = view;
viewObj.Next();
}
}
2026-02-24 12:21:46 +08:00
catch { }
SafeReleaseComObject(slideShowWindow);
2026-03-03 16:04:20 +08:00
}
2026-02-24 12:21:46 +08:00
SafeReleaseComObject(slideShowWindows);
}
2026-03-03 16:04:20 +08:00
}
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);
}
2026-02-24 12:21:46 +08:00
}).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;
2026-03-03 16:04:20 +08:00
if (slideShowWindows != null)
{
dynamic ssw = slideShowWindows;
2026-02-24 12:21:46 +08:00
object slideShowWindow = ssw[1];
2026-03-03 16:04:20 +08:00
if (slideShowWindow != null)
{
dynamic sswObj = slideShowWindow;
2026-02-24 12:21:46 +08:00
try
{
2026-03-03 16:04:20 +08:00
sswObj.Activate();
2026-02-24 12:21:46 +08:00
}
catch { }
try
{
object view = sswObj.View;
2026-03-03 16:04:20 +08:00
if (view != null)
{
dynamic viewObj = view;
viewObj.Previous();
}
}
2026-02-24 12:21:46 +08:00
catch { }
SafeReleaseComObject(slideShowWindow);
2026-03-03 16:04:20 +08:00
}
2026-02-24 12:21:46 +08:00
SafeReleaseComObject(slideShowWindows);
}
2026-03-03 16:04:20 +08:00
}
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);
}
2026-02-24 12:21:46 +08:00
}).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;
}
}
/// <summary>
/// 获取当前活跃的演示文稿
/// </summary>
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;
2026-02-12 22:33:04 +08:00
}
2026-02-24 12:21:46 +08:00
}
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);
}
}
2026-02-12 22:33:04 +08:00
2026-02-24 12:21:46 +08:00
/// <summary>
/// 获取当前幻灯片编号
/// </summary>
private int GetCurrentSlideIndex(dynamic slideShowWindow)
{
object view = null;
object slide = null;
try
{
if (slideShowWindow == null) return 0;
2026-03-03 16:04:20 +08:00
2026-02-24 12:21:46 +08:00
// 检查COM对象是否有效
if (!Marshal.IsComObject(slideShowWindow))
{
return 0;
}
2026-03-03 16:04:20 +08:00
2026-02-24 12:21:46 +08:00
try
{
var _ = Marshal.GetIUnknownForObject(slideShowWindow);
Marshal.Release(_);
}
catch (InvalidComObjectException)
{
return 0;
}
2026-03-03 16:04:20 +08:00
2026-02-24 12:21:46 +08:00
dynamic ssw = slideShowWindow;
view = ssw.View;
2026-03-03 16:04:20 +08:00
if (view != null)
{
dynamic viewObj = view;
2026-02-24 12:21:46 +08:00
slide = viewObj.Slide;
if (slide != null)
{
dynamic slideObj = slide;
return slideObj.SlideIndex;
2026-02-12 22:33:04 +08:00
}
}
2026-02-24 12:21:46 +08:00
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);
}
}
2026-02-12 22:33:04 +08:00
2026-02-24 12:21:46 +08:00
private int GetTotalSlideIndex(dynamic presentation)
{
try
{
if (presentation == null) return 0;
dynamic pres = presentation;
return pres.Slides.Count;
}
catch
{
return 0;
}
}
2026-02-12 22:33:04 +08:00
2026-02-24 12:21:46 +08:00
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;
2026-02-12 22:33:04 +08:00
2026-02-24 12:21:46 +08:00
if (IsInSlideShow && _pptSlideShowWindow != null)
2026-02-12 22:33:04 +08:00
{
try
{
2026-02-24 12:21:46 +08:00
return GetCurrentSlideIndex(_pptSlideShowWindow);
2026-02-12 22:33:04 +08:00
}
2026-02-24 12:21:46 +08:00
catch
{
return 0;
}
}
activeWindow = PPTApplication.ActiveWindow;
if (activeWindow != null)
{
dynamic aw = activeWindow;
selection = aw.Selection;
if (selection != null)
2026-02-12 22:33:04 +08:00
{
2026-02-24 12:21:46 +08:00
dynamic sel = selection;
slideRange = sel.SlideRange;
if (slideRange != null)
2026-02-18 22:10:14 +08:00
{
2026-02-24 12:21:46 +08:00
dynamic sr = slideRange;
int slideNumber = sr.SlideNumber;
if (slideNumber > 0)
{
return slideNumber;
}
2026-02-18 22:10:14 +08:00
}
2026-02-12 22:33:04 +08:00
}
2026-02-24 12:21:46 +08:00
}
if (CurrentSlide != null && Marshal.IsComObject(CurrentSlide))
{
return CurrentSlide.SlideNumber;
}
return 0;
2026-02-12 22:33:04 +08:00
}
2026-02-24 12:21:46 +08:00
catch (COMException comEx)
2026-02-12 22:33:04 +08:00
{
2026-02-24 12:21:46 +08:00
var hr = (uint)comEx.HResult;
if (hr == 0x8001010E || hr == 0x80004005)
2026-02-18 22:10:14 +08:00
{
2026-02-24 12:21:46 +08:00
DisconnectFromPPT();
2026-02-18 22:10:14 +08:00
}
2026-02-24 12:21:46 +08:00
return 0;
}
catch (Exception)
{
return 0;
}
finally
{
SafeReleaseComObject(slideRange);
SafeReleaseComObject(selection);
SafeReleaseComObject(activeWindow);
2026-02-12 22:33:04 +08:00
}
}
2026-02-24 12:21:46 +08:00
public string GetPresentationName()
2026-02-12 22:33:04 +08:00
{
try
{
2026-02-24 12:21:46 +08:00
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 "";
2026-02-12 22:33:04 +08:00
}
catch (Exception ex)
{
2026-02-24 12:21:46 +08:00
LogHelper.WriteLogToFile($"获取演示文稿名称失败: {ex}", LogHelper.LogType.Error);
return "";
2026-02-12 22:33:04 +08:00
}
}
2026-02-24 12:21:46 +08:00
public string GetPresentationPath()
2026-02-12 22:33:04 +08:00
{
try
{
2026-02-24 12:21:46 +08:00
if (CurrentPresentation == null || !Marshal.IsComObject(CurrentPresentation)) return "";
return CurrentPresentation.FullName ?? "";
2026-02-12 22:33:04 +08:00
}
catch (COMException comEx)
{
var hr = (uint)comEx.HResult;
2026-02-24 12:21:46 +08:00
if (hr == 0x8001010E || hr == 0x80004005)
2026-02-12 22:33:04 +08:00
{
2026-02-24 12:21:46 +08:00
// COM对象已失效,触发断开连接
DisconnectFromPPT();
2026-02-12 22:33:04 +08:00
}
2026-02-24 12:21:46 +08:00
LogHelper.WriteLogToFile($"获取演示文稿路径失败: {comEx.Message}", LogHelper.LogType.Warning);
return "";
2026-02-12 22:33:04 +08:00
}
2026-02-24 12:21:46 +08:00
catch (Exception ex)
2026-02-12 22:33:04 +08:00
{
2026-02-24 12:21:46 +08:00
LogHelper.WriteLogToFile($"获取演示文稿路径失败: {ex}", LogHelper.LogType.Error);
return "";
2026-02-12 22:33:04 +08:00
}
}
2026-02-24 12:21:46 +08:00
public bool TryShowSlideNavigation()
2026-02-12 22:33:04 +08:00
{
2026-02-24 12:21:46 +08:00
object slideShowWindows = null;
object slideShowWindow = null;
object slideNavigation = null;
2026-02-12 22:33:04 +08:00
try
{
2026-02-24 12:21:46 +08:00
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;
2026-02-12 22:33:04 +08:00
}
catch (Exception ex)
{
2026-02-24 12:21:46 +08:00
LogHelper.WriteLogToFile($"显示幻灯片导航失败: {ex}", LogHelper.LogType.Error);
return false;
}
finally
{
SafeReleaseComObject(slideNavigation);
SafeReleaseComObject(slideShowWindow);
SafeReleaseComObject(slideShowWindows);
2026-02-12 22:33:04 +08:00
}
}
2026-02-24 12:21:46 +08:00
#endregion
2026-02-12 22:33:04 +08:00
2026-02-24 12:21:46 +08:00
#region WPS Process Management
private void RecordWpsProcessForManagement()
2026-02-12 22:33:04 +08:00
{
2026-02-24 12:21:46 +08:00
if (!IsSupportWPS || PPTApplication == null) return;
2026-02-12 22:33:04 +08:00
try
{
2026-02-24 12:21:46 +08:00
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);
}
2026-02-12 22:33:04 +08:00
}
catch (Exception ex)
{
2026-02-24 12:21:46 +08:00
LogHelper.WriteLogToFile($"记录WPS进程失败: {ex}", LogHelper.LogType.Error);
2026-02-12 22:33:04 +08:00
}
}
2026-02-24 12:21:46 +08:00
private void StartWpsProcessCheckTimer()
2026-02-12 22:33:04 +08:00
{
2026-02-24 12:21:46 +08:00
if (!IsSupportWPS) return;
if (_wpsProcessCheckTimer != null)
2026-02-12 22:33:04 +08:00
{
2026-02-24 12:21:46 +08:00
_wpsProcessCheckTimer.Stop();
_wpsProcessCheckTimer.Dispose();
2026-02-12 22:33:04 +08:00
}
2026-02-24 12:21:46 +08:00
// 增加检查间隔到2秒,减少性能开销
_wpsProcessCheckTimer = new Timer(2000);
_wpsProcessCheckTimer.Elapsed += OnWpsProcessCheckTimerElapsed;
_wpsProcessCheckTimer.Start();
2026-02-12 22:33:04 +08:00
}
2026-02-24 12:21:46 +08:00
private void OnWpsProcessCheckTimerElapsed(object sender, ElapsedEventArgs e)
2026-02-12 22:33:04 +08:00
{
2026-02-24 12:21:46 +08:00
if (!IsSupportWPS)
{
StopWpsProcessCheckTimer();
return;
}
2026-02-12 22:33:04 +08:00
try
{
2026-02-24 12:21:46 +08:00
if (_wpsProcess == null || !_hasWpsProcessId)
{
StopWpsProcessCheckTimer();
return;
}
2026-02-12 22:53:05 +08:00
2026-02-24 12:21:46 +08:00
_wpsProcess.Refresh();
_wpsProcessCheckCount++;
if (_wpsProcess.HasExited)
2026-02-12 22:53:05 +08:00
{
2026-02-24 12:21:46 +08:00
StopWpsProcessCheckTimer();
return;
2026-02-12 22:53:05 +08:00
}
2026-02-24 12:21:46 +08:00
// 检查前台WPS窗口是否存在
bool isForegroundWpsWindowActive = IsForegroundWpsWindowStillActiveOptimized();
if (isForegroundWpsWindowActive)
{
if (_wpsProcessCheckCount % 5 == 0) // 每10秒记录一次日志
{
}
return;
}
// 多重验证确保准确性
if (!PerformMultipleWpsWindowChecks())
2026-02-12 22:53:05 +08:00
{
2026-02-24 12:21:46 +08:00
return;
2026-02-12 22:53:05 +08:00
}
2026-02-12 22:33:04 +08:00
2026-02-24 12:21:46 +08:00
// 前台窗口已消失,准备结束WPS进程
LogHelper.WriteLogToFile("多重验证确认WPS窗口已消失,准备结束WPS进程", LogHelper.LogType.Event);
// 安全结束WPS进程
SafeTerminateWpsProcess();
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"WPS 进程检测失败: {ex}", LogHelper.LogType.Error);
StopWpsProcessCheckTimer();
}
}
/// <summary>
/// 前台WPS窗口检测
/// </summary>
private bool IsForegroundWpsWindowStillActiveOptimized()
{
try
{
// 快速检查:直接检查前台窗口
var foregroundWindow = GetForegroundWindow();
if (foregroundWindow == IntPtr.Zero) return false;
// 获取前台窗口的进程ID
uint processId;
GetWindowThreadProcessId(foregroundWindow, out processId);
2026-02-12 22:53:05 +08:00
2026-02-24 12:21:46 +08:00
// 如果前台窗口就是我们监控的WPS进程,则认为仍然活跃
if (processId == _wpsProcess?.Id)
2026-02-12 22:33:04 +08:00
{
2026-02-24 12:21:46 +08:00
return true;
2026-02-12 22:33:04 +08:00
}
2026-02-24 12:21:46 +08:00
// 检查是否为WPS相关窗口
var windowInfo = GetWindowInfo(foregroundWindow);
return IsWpsWindow(windowInfo);
2026-02-12 22:33:04 +08:00
}
catch (Exception ex)
{
2026-02-24 12:21:46 +08:00
LogHelper.WriteLogToFile($"WPS窗口检测失败: {ex}", LogHelper.LogType.Error);
2026-02-12 22:33:04 +08:00
return false;
}
}
2026-02-24 12:21:46 +08:00
/// <summary>
/// 多重验证WPS窗口状态,确保查杀准确性
/// </summary>
private bool PerformMultipleWpsWindowChecks()
2026-02-12 22:33:04 +08:00
{
try
{
2026-02-24 12:21:46 +08:00
// 第一重验证:等待1秒后再次检查
Thread.Sleep(1000);
if (IsForegroundWpsWindowStillActiveOptimized())
{
return false;
}
2026-02-12 22:33:04 +08:00
2026-02-24 12:21:46 +08:00
// 第二重验证:检查所有WPS进程的窗口
var wpsProcesses = GetWpsProcesses();
foreach (var process in wpsProcesses)
2026-02-12 22:33:04 +08:00
{
2026-02-24 12:21:46 +08:00
if (process.Id == _wpsProcess?.Id) continue; // 跳过当前监控的进程
2026-02-12 22:33:04 +08:00
2026-02-24 12:21:46 +08:00
var windows = GetWpsWindowsByProcess(process.Id);
if (windows.Any(w => w.IsVisible && !w.IsMinimized))
2026-02-12 22:33:04 +08:00
{
2026-02-24 12:21:46 +08:00
return false;
2026-02-12 22:33:04 +08:00
}
}
2026-02-24 12:21:46 +08:00
// 第三重验证:检查任务栏中的WPS窗口
if (HasWpsWindowInTaskbar())
2026-02-12 22:33:04 +08:00
{
2026-02-24 12:21:46 +08:00
return false;
2026-02-12 22:33:04 +08:00
}
2026-02-24 12:21:46 +08:00
LogHelper.WriteLogToFile("多重验证完成:确认WPS窗口已全部消失", LogHelper.LogType.Event);
return true;
2026-02-12 22:33:04 +08:00
}
catch (Exception ex)
{
2026-02-24 12:21:46 +08:00
LogHelper.WriteLogToFile($"多重验证失败: {ex}", LogHelper.LogType.Error);
return false; // 出错时保守处理,不进行查杀
2026-02-12 22:33:04 +08:00
}
}
2026-02-24 12:21:46 +08:00
/// <summary>
/// 检查任务栏中是否有WPS窗口
/// </summary>
private bool HasWpsWindowInTaskbar()
2026-02-12 22:33:04 +08:00
{
try
{
2026-02-24 12:21:46 +08:00
var allWindows = new List<WpsWindowInfo>();
2026-02-12 22:33:04 +08:00
2026-02-24 12:21:46 +08:00
EnumWindows((hWnd, lParam) =>
2026-02-12 22:33:04 +08:00
{
2026-02-24 12:21:46 +08:00
try
2026-02-12 22:33:04 +08:00
{
2026-02-24 12:21:46 +08:00
if (IsWindow(hWnd) && IsWindowVisible(hWnd))
2026-02-12 22:33:04 +08:00
{
2026-02-24 12:21:46 +08:00
var windowInfo = GetWindowInfo(hWnd);
if (IsWpsWindow(windowInfo) && !string.IsNullOrEmpty(windowInfo.Title))
{
allWindows.Add(windowInfo);
}
2026-02-12 22:33:04 +08:00
}
}
2026-02-24 12:21:46 +08:00
catch { }
return true;
}, IntPtr.Zero);
return allWindows.Count > 0;
2026-02-12 22:33:04 +08:00
}
catch (Exception ex)
{
2026-02-24 12:21:46 +08:00
LogHelper.WriteLogToFile($"检查任务栏WPS窗口失败: {ex}", LogHelper.LogType.Error);
return true; // 出错时保守处理,认为仍有窗口
2026-02-12 22:33:04 +08:00
}
}
2026-02-24 12:21:46 +08:00
/// <summary>
/// 安全地结束WPS进程 - 通过释放PPTCOM对象
/// </summary>
private void SafeTerminateWpsProcess()
2026-02-12 22:33:04 +08:00
{
try
{
2026-02-24 12:21:46 +08:00
if (_wpsProcess == null || _wpsProcess.HasExited)
{
StopWpsProcessCheckTimer();
return;
}
2026-02-12 22:33:04 +08:00
2026-02-24 12:21:46 +08:00
LogHelper.WriteLogToFile($"开始通过释放PPTCOM对象安全结束WPS进程 (PID: {_wpsProcess.Id})", LogHelper.LogType.Event);
// 第一步:释放 pptActWindow 对象(SlideShowWindow
SlideShowWindow pptActWindow = null;
try
2026-02-12 22:33:04 +08:00
{
2026-02-24 12:21:46 +08:00
if (PPTApplication != null && Marshal.IsComObject(PPTApplication))
2026-02-12 22:33:04 +08:00
{
2026-02-24 12:21:46 +08:00
if (PPTApplication.SlideShowWindows?.Count > 0)
2026-02-12 22:33:04 +08:00
{
2026-02-24 12:21:46 +08:00
pptActWindow = PPTApplication.SlideShowWindows[1];
2026-02-12 22:33:04 +08:00
}
}
}
2026-02-24 12:21:46 +08:00
catch (Exception ex)
2026-02-12 22:33:04 +08:00
{
2026-02-24 12:21:46 +08:00
LogHelper.WriteLogToFile($"获取SlideShowWindow对象时发生异常: {ex}", LogHelper.LogType.Warning);
2026-02-12 22:33:04 +08:00
}
2026-02-24 12:21:46 +08:00
if (pptActWindow != null)
{
Marshal.ReleaseComObject(pptActWindow);
pptActWindow = null;
}
2026-02-12 22:33:04 +08:00
2026-02-24 12:21:46 +08:00
// 第二步:释放 pptActDoc 对象(CurrentPresentation
Presentation pptActDoc = CurrentPresentation;
if (pptActDoc != null)
2026-02-12 22:33:04 +08:00
{
2026-02-24 12:21:46 +08:00
Marshal.ReleaseComObject(pptActDoc);
pptActDoc = null;
CurrentPresentation = null;
}
// 第三步:释放 pptApp 对象(PPTApplication
if (PPTApplication != null)
{
try
2026-02-12 22:33:04 +08:00
{
2026-02-24 12:21:46 +08:00
Marshal.ReleaseComObject(PPTApplication);
2026-03-03 16:04:20 +08:00
PPTApplication = null;
2026-02-24 12:21:46 +08:00
}
2026-05-01 00:33:35 +08:00
catch (Exception)
2026-02-24 12:21:46 +08:00
{
PPTApplication = null;
2026-02-12 22:33:04 +08:00
}
}
2026-02-24 12:21:46 +08:00
// 第四步:强制垃圾回收及等待终结器执行
GC.Collect();
GC.WaitForPendingFinalizers();
// 等待一段时间让COM对象完全释放
Thread.Sleep(1000);
// 检查进程是否已经结束
try
2026-02-12 22:33:04 +08:00
{
2026-02-24 12:21:46 +08:00
_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);
2026-02-12 22:33:04 +08:00
}
}
catch (Exception ex)
{
2026-02-24 12:21:46 +08:00
LogHelper.WriteLogToFile($"安全结束WPS进程时发生异常: {ex}", LogHelper.LogType.Error);
2026-02-12 22:33:04 +08:00
}
finally
{
2026-02-24 12:21:46 +08:00
// 确保清理状态
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();
2026-05-01 00:33:35 +08:00
bool wasConnected = _cachedIsConnected;
_cachedIsConnected = false;
_cachedIsInSlideShow = false;
if (wasConnected)
{
PPTConnectionChanged?.Invoke(false);
}
2026-02-24 12:21:46 +08:00
LogHelper.WriteLogToFile("WPS进程结束后已清理所有COM对象并重启连接检查", LogHelper.LogType.Event);
2026-02-12 22:33:04 +08:00
}
}
2026-02-24 12:21:46 +08:00
private void StopWpsProcessCheckTimer()
2026-02-12 22:33:04 +08:00
{
2026-02-24 12:21:46 +08:00
if (_wpsProcessCheckTimer != null)
2026-02-12 22:33:04 +08:00
{
2026-02-24 12:21:46 +08:00
_wpsProcessCheckTimer.Stop();
_wpsProcessCheckTimer.Dispose();
_wpsProcessCheckTimer = null;
}
2026-02-12 22:33:04 +08:00
2026-02-24 12:21:46 +08:00
_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))
2026-02-12 22:33:04 +08:00
{
2026-02-24 12:21:46 +08:00
var windowInfo = GetWindowInfo(foregroundHwnd);
if (IsWpsWindow(windowInfo))
2026-02-12 22:33:04 +08:00
{
2026-02-24 12:21:46 +08:00
return windowInfo;
2026-02-12 22:33:04 +08:00
}
}
}
catch (Exception ex)
{
2026-02-24 12:21:46 +08:00
LogHelper.WriteLogToFile($"获取前台WPS窗口失败: {ex}", LogHelper.LogType.Error);
2026-02-12 22:33:04 +08:00
}
2026-02-24 12:21:46 +08:00
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
2026-02-12 22:33:04 +08:00
{
2026-02-24 12:21:46 +08:00
var proc = Process.GetProcessById((int)processId);
windowInfo.ProcessName = proc.ProcessName.ToLower();
2026-02-12 22:33:04 +08:00
}
2026-02-24 12:21:46 +08:00
catch { }
return windowInfo;
2026-02-12 22:33:04 +08:00
}
2026-02-24 12:21:46 +08:00
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<Process> GetWpsProcesses()
2026-02-12 22:33:04 +08:00
{
2026-02-24 12:21:46 +08:00
var wpsProcesses = new List<Process>();
2026-02-12 22:33:04 +08:00
try
{
2026-02-24 12:21:46 +08:00
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;
}
2026-02-12 22:53:05 +08:00
2026-02-24 12:21:46 +08:00
// 精确匹配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);
}
}
2026-02-12 22:33:04 +08:00
}
catch (Exception ex)
{
2026-02-24 12:21:46 +08:00
LogHelper.WriteLogToFile($"获取WPS进程失败: {ex}", LogHelper.LogType.Error);
2026-02-12 22:53:05 +08:00
}
2026-02-24 12:21:46 +08:00
return wpsProcesses;
2026-02-12 22:33:04 +08:00
}
2026-02-24 12:21:46 +08:00
private bool IsForegroundWpsWindowStillActive()
2026-02-12 22:33:04 +08:00
{
try
{
2026-02-24 12:21:46 +08:00
var currentTime = DateTime.Now;
var currentForegroundWindow = GetForegroundWpsWindow();
2026-02-12 22:33:04 +08:00
2026-02-24 12:21:46 +08:00
// 检查窗口状态是否发生变化
if (_lastForegroundWpsWindow != null && currentForegroundWindow != null)
2026-02-12 22:33:04 +08:00
{
2026-02-24 12:21:46 +08:00
if (_lastForegroundWpsWindow.Handle != currentForegroundWindow.Handle ||
_lastForegroundWpsWindow.Title != currentForegroundWindow.Title)
{
}
2026-02-12 22:33:04 +08:00
}
2026-02-24 12:21:46 +08:00
else if (_lastForegroundWpsWindow == null && currentForegroundWindow != null)
2026-02-12 22:33:04 +08:00
{
}
2026-02-24 12:21:46 +08:00
else if (_lastForegroundWpsWindow != null && currentForegroundWindow == null)
2026-02-12 22:33:04 +08:00
{
2026-02-24 12:21:46 +08:00
}
2026-02-12 22:33:04 +08:00
2026-02-24 12:21:46 +08:00
// 更新记录
_lastForegroundWpsWindow = currentForegroundWindow;
_lastWindowCheckTime = currentTime;
2026-02-12 22:33:04 +08:00
2026-02-24 12:21:46 +08:00
if (currentForegroundWindow != null)
{
if (IsWindow(currentForegroundWindow.Handle) && IsWindowVisible(currentForegroundWindow.Handle))
2026-02-12 22:33:04 +08:00
{
2026-02-24 12:21:46 +08:00
return true;
2026-02-12 22:33:04 +08:00
}
}
2026-02-24 12:21:46 +08:00
// 检查所有WPS进程的活跃窗口
var wpsProcesses = GetWpsProcesses();
foreach (var process in wpsProcesses)
2026-02-12 22:33:04 +08:00
{
2026-02-24 12:21:46 +08:00
var windows = GetWpsWindowsByProcess(process.Id);
if (windows.Any(w => w.IsVisible && !w.IsMinimized && w.Handle == GetForegroundWindow()))
{
return true;
}
2026-02-12 22:33:04 +08:00
}
2026-02-24 12:21:46 +08:00
2026-02-12 22:33:04 +08:00
return false;
}
catch (Exception ex)
{
2026-02-24 12:21:46 +08:00
LogHelper.WriteLogToFile($"检查前台WPS窗口状态失败: {ex}", LogHelper.LogType.Error);
2026-02-12 22:33:04 +08:00
return false;
}
}
2026-02-24 12:21:46 +08:00
private List<WpsWindowInfo> GetWpsWindowsByProcess(int processId)
2026-02-12 22:33:04 +08:00
{
2026-02-24 12:21:46 +08:00
var wpsWindows = new List<WpsWindowInfo>();
2026-02-12 22:33:04 +08:00
try
{
2026-02-24 12:21:46 +08:00
EnumWindows((hWnd, lParam) =>
2026-02-12 22:33:04 +08:00
{
2026-02-24 12:21:46 +08:00
try
2026-02-12 22:33:04 +08:00
{
2026-02-24 12:21:46 +08:00
if (!IsWindow(hWnd)) return true;
uint windowProcessId;
GetWindowThreadProcessId(hWnd, out windowProcessId);
if ((int)windowProcessId == processId)
2026-02-12 22:33:04 +08:00
{
2026-02-24 12:21:46 +08:00
var windowInfo = GetWindowInfo(hWnd);
if (IsWpsWindow(windowInfo))
2026-02-12 22:53:05 +08:00
{
2026-02-24 12:21:46 +08:00
wpsWindows.Add(windowInfo);
2026-02-12 22:53:05 +08:00
}
2026-02-12 22:33:04 +08:00
}
}
2026-02-24 12:21:46 +08:00
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
/// <summary>
/// 获取PPT窗口句柄
/// </summary>
/// <returns>窗口句柄,如果获取失败返回 IntPtr.Zero</returns>
public IntPtr GetPptHwnd()
{
IntPtr ret = IntPtr.Zero;
// 方法1: 尝试从 SlideShowWindow 获取
ret = GetPptHwndFromSlideShowWindow(_pptSlideShowWindow);
2026-02-12 22:53:05 +08:00
2026-02-24 12:21:46 +08:00
if (ret == IntPtr.Zero)
{
// 方法2: 通过窗口标题匹配获取(备用方法)
2026-02-12 22:53:05 +08:00
try
{
2026-02-24 12:21:46 +08:00
if (_pptActivePresentation != null && PPTApplication != null)
2026-02-12 22:33:04 +08:00
{
2026-02-24 12:21:46 +08:00
dynamic pres = _pptActivePresentation;
string fullName = pres.FullName;
string appName = PPTApplication.Name;
ret = GetPptHwndWin32(fullName, appName);
2026-02-12 22:33:04 +08:00
}
}
2026-05-01 00:33:35 +08:00
catch (Exception)
2026-02-12 22:33:04 +08:00
{
}
}
2026-02-24 12:21:46 +08:00
return ret;
}
/// <summary>
/// 从 SlideShowWindow 对象获取窗口句柄
/// </summary>
private IntPtr GetPptHwndFromSlideShowWindow(object pptSlideShowWindowObj)
{
IntPtr hwnd = IntPtr.Zero;
if (pptSlideShowWindowObj == null) return IntPtr.Zero;
try
2026-02-12 22:33:04 +08:00
{
2026-02-24 12:21:46 +08:00
// 尝试强类型转换
2026-03-03 16:04:20 +08:00
Microsoft.Office.Interop.PowerPoint.SlideShowWindow slideWindow =
2026-02-24 12:21:46 +08:00
pptSlideShowWindowObj as Microsoft.Office.Interop.PowerPoint.SlideShowWindow;
if (slideWindow != null)
2026-02-12 22:33:04 +08:00
{
2026-02-24 12:21:46 +08:00
int hwndVal = slideWindow.HWND;
hwnd = new IntPtr(hwndVal);
}
else
{
// 如果强类型转换失败,尝试使用dynamic
try
{
dynamic ssw = pptSlideShowWindowObj;
int hwndVal = ssw.HWND;
hwnd = new IntPtr(hwndVal);
}
2026-05-01 00:33:35 +08:00
catch (Exception)
2026-02-24 12:21:46 +08:00
{
}
2026-02-12 22:33:04 +08:00
}
}
2026-05-01 00:33:35 +08:00
catch (Exception)
2026-02-12 22:33:04 +08:00
{
}
2026-02-24 12:21:46 +08:00
return hwnd;
2026-02-12 22:33:04 +08:00
}
/// <summary>
2026-02-24 12:21:46 +08:00
/// 通过窗口标题匹配获取PPT窗口句柄(备用方法)
2026-02-12 22:33:04 +08:00
/// </summary>
2026-02-24 12:21:46 +08:00
private IntPtr GetPptHwndWin32(string presFullName, string appName)
2026-02-12 22:33:04 +08:00
{
try
{
2026-02-24 12:21:46 +08:00
// 步骤 A: 基础参数校验
if (string.IsNullOrWhiteSpace(presFullName) || string.IsNullOrWhiteSpace(appName))
2026-02-12 22:33:04 +08:00
{
2026-02-24 12:21:46 +08:00
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<IntPtr> candidates = new List<IntPtr>();
// 调用 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)
{
2026-02-12 22:33:04 +08:00
}
2026-02-24 12:21:46 +08:00
return IntPtr.Zero;
2026-02-12 22:33:04 +08:00
}
2026-05-01 00:33:35 +08:00
catch (Exception)
2026-02-12 22:33:04 +08:00
{
2026-02-24 12:21:46 +08:00
// 发生任何不可预知的异常(如Path解析错误等),返回安全值
return IntPtr.Zero;
2026-02-12 22:33:04 +08:00
}
}
#endregion
2026-02-24 12:21:46 +08:00
#region Dispose
2026-02-12 22:33:04 +08:00
public void Dispose()
{
2026-02-24 12:21:46 +08:00
if (!_disposed)
2026-02-12 22:33:04 +08:00
{
StopMonitoring();
2026-02-24 12:21:46 +08:00
StopWpsProcessCheckTimer();
_disposed = true;
2026-02-12 22:33:04 +08:00
}
}
#endregion
}
2026-02-24 12:21:46 +08:00
}