C# 使用线程运行COM对象时Excel崩溃
我在运行COM(自制)计时器时在Excel中遇到错误。 基本上Excel实例化计时器,初始化它并启动它。然后,计时器每X毫秒滴答一次,引发Excel捕获的事件(相当标准的东西)。我不使用Excel本身作为计时器,因为它的滴答声不会超过每秒钟(这对我来说太长了) 我的问题是,如果我在计时器引发事件时单击并按住电子表格,Excel会崩溃得很厉害。不幸的是,用户(有时)需要在计时器运行时单击电子表格并修改它 我在某处看到我可以在计时器中使用IMessageFilter接口。这应该确保当事件启动时,如果Excel正忙,计时器可以看到这一点并相应地采取行动。然而,我无法正确地实施它 如果有人能帮我,那就太好了 以下是我正在使用的源代码: 在Excel中,我有一个带有WithEvents ExcelTimer.ExcelTimer对象的singleton,下面是我的singleton的代码:C# 使用线程运行COM对象时Excel崩溃,c#,multithreading,vba,excel,com-object,C#,Multithreading,Vba,Excel,Com Object,我在运行COM(自制)计时器时在Excel中遇到错误。 基本上Excel实例化计时器,初始化它并启动它。然后,计时器每X毫秒滴答一次,引发Excel捕获的事件(相当标准的东西)。我不使用Excel本身作为计时器,因为它的滴答声不会超过每秒钟(这对我来说太长了) 我的问题是,如果我在计时器引发事件时单击并按住电子表格,Excel会崩溃得很厉害。不幸的是,用户(有时)需要在计时器运行时单击电子表格并修改它 我在某处看到我可以在计时器中使用IMessageFilter接口。这应该确保当事件启动时,如果
Option Explicit
Private Const m_sMODULE_NAME = "cTimerManager"
Public WithEvents oCsharpTimer As ExcelTimer.ExcelTimer
Private Sub Class_Initialize()
Set oCsharpTimer = New ExcelTimer.ExcelTimer
'The following two lines are called dynamically from somewhere else
'normally but for simplicity of my post I have put them here
oCsharpTimer.Initialize 500
oCsharpTimer.StartTimer
End Sub
Private Sub oCsharpTimer_TimeTickEvt(ByVal o As Variant, ByVal Time As String)
Const sPROCEDURE_NAME = "oCsharpTimer_TimeTickEvt"
On Error GoTo ErrorHandler
'"Send" confirmation with time to the COM object.
oCsharpTimer.TimeReceived Time
'Do whatever I wanna do when the event is trigger
CleanUp:
Exit Sub
ErrorHandler:
'My Error handling structure
If ProcessError(m_sMODULE_NAME, sPROCEDURE_NAME, Err) Then
Stop
Resume
Else
Resume Next
End If
End Sub
以下是我的COM对象的代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace ExcelTimer
{
public delegate void EventTimeRaiser(object o, string Time);
//COM Interface
public interface ICOMExcelTimer
{
[DispId(1)]
void StartTimer();
[DispId(2)]
void StopTimer();
[DispId(3)]
void Initialize(int TimeInMilliseconds);
[DispId(4)]
void TimeReceived(string ReceivedTime);
}
//Event interface
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface ICOMExcelTimerEvent
{
[DispId(1000)]
void TimeTickEvt(object o, string Time);
}
[ClassInterface(ClassInterfaceType.None),
ComSourceInterfaces(typeof(ICOMExcelTimerEvent)),
ComVisible(true)]
public class ExcelTimer : ICOMExcelTimer, IMessageFilter
{
private event EventTimeRaiser TimeTickEvt;
private bool _started;
private bool _initialised;
private int _timeInMilliseconds;
private string _lastTimeReceived;
private Control _control;
private Thread _timerThread;
private IAsyncResult _AsynchronousResult;
[ComVisible(true)]
public void Initialize(int TimeInMilliSeconds)
{
//To be called by Excel to set which timer parameters it wants
_timeInMilliseconds = TimeInMilliSeconds;
_initialised = true;
//Make sure we clear the last confirmation received
//since we are re-initialising the object
_lastTimeReceived = "";
}
[ComVisible(true)]
public void TimeReceived(string ReceivedTime)
{
//Store the last time received. Excel calls this function
_lastTimeReceived = ReceivedTime;
}
public ExcelTimer()
{
_lastTimeReceived = "";
}
[ComVisible(true)]
//Start the Timer
public void StartTimer()
{
//If the timer has not been initialised just yet
if (!_initialised)
{
//Sends back an error message to Excel
TimeTickEvt(this, "Error: Timer Not Initialised");
return;
}
try
{
//Start the timer
_timerThread = new Thread(new ThreadStart(TimeTicking));
//Start the Thread
_started = true;
_timerThread.Start();
}
catch (Exception ex)
{
System.IO.File.AppendAllText(@"C:\ErrorLog.txt", ex.Message + " - StartTimer - " + DateTime.Now.ToString("hh:mm:ss.f") + "\n");
}
}
[ComVisible(true)]
//Stop the timer
public void StopTimer()
{
//Stop the Thread
_timerThread.Abort();
//Change the status
_started = false;
}
private void TimeTicking()
{
string SentTime;
//As long as the timer is running
while (_started)
{
try
{
//Pause the timer for the right number of milliseconds
Thread.Sleep(_timeInMilliseconds);
SentTime = DateTime.Now.ToString("hh:mm:ss.ffff");
//########### The CODE Errors Here when Excel is busy! ###########
//Raise an event for Excel to grab with the time that the thread finished the sleep at.
OnTimeTick(SentTime);
//_lastTimeReceived is used so that if the link between Excel and the Thread is broken the thread stops after sometimes
//if no confirmation was received from Excel.
//If no last time was received just yet, we setup the last time received to the sent time
if (_lastTimeReceived.Equals(""))
{
_lastTimeReceived = SentTime;
}
//If the last time received is older than 10 x TimeInMilliseconds (in Seconds) we stop the timer.
else if (Convert.ToDateTime(_lastTimeReceived).AddSeconds(_timeInMilliseconds * 10 / 1000) < Convert.ToDateTime(SentTime))
{
OnTimeTick("Timer timed out. No Confirmation for more than " + _timeInMilliseconds * 10 / 1000 + " second(s).");
//Stop the timer because the thread has not received a last time recently
_started = false;
}
}
catch (Exception ex)
{
System.IO.File.AppendAllText(@"C:\ErrorLog.txt", ex.Message + " - TimeTicking - " + DateTime.Now.ToString("hh:mm:ss.f") + "\n");
}
}
}
protected virtual void OnTimeTick(string Time)
{
try
{
if (Time != null)
{
//Raise the event
TimeTickEvt(this, Time);
}
}
catch (Exception ex)
{
System.IO.File.AppendAllText(@"C:\ErrorLog.txt", ex.Message + " - OnTimeTick - " + DateTime.Now.ToString("hh:mm:ss.f") + "\n");
}
}
}
}
使用系统;
使用System.Collections.Generic;
使用System.Linq;
使用系统文本;
使用系统线程;
使用System.Runtime.InteropServices;
使用System.Windows.Forms;
命名空间ExcelTimer
{
公共委托void EventTimeRaiser(对象o,字符串时间);
//COM接口
公共接口ICOMExcelTimer
{
[附表(1)]
void StartTimer();
[附表(2)]
void StopTimer();
[附表(3)]
无效初始化(int-timein毫秒);
[附表(4)]
接收无效时间(字符串接收时间);
}
//事件接口
[接口类型(ComInterfaceType.InterfaceIsIDispatch)]
公共接口ICOMExcelTimerEvent
{
[DispId(1000)]
void timetickvt(对象o,字符串时间);
}
[ClassInterface(ClassInterfaceType.None),
ComSourceInterfaces(类型为(ICOMExcelTimerEvent)),
ComVisible(正确)]
公共类ExcelTimer:ICOMExcelTimer、IMessageFilter
{
私有事件事件时间提升器TimeTickEvt;
私人住宅开始营业;
私人住宅首字母缩写;
私有整数_timein毫秒;
私有字符串\u lastTimeReceived;
私人控制;
专用线程_timerThread;
私有IAsyncResult\u异步结果;
[ComVisible(true)]
公共void初始化(int timeinmillizes)
{
//由Excel调用以设置所需的计时器参数
_timeinmillides=timeinmillides;
_初始化=真;
//确保我们清除了上次收到的确认
//因为我们正在重新初始化对象
_lastTimeReceived=“”;
}
[ComVisible(true)]
收到的公共无效时间(字符串接收时间)
{
//存储上次收到的时间。Excel调用此函数
_lastTimeReceived=ReceivedTime;
}
公共计时器()
{
_lastTimeReceived=“”;
}
[ComVisible(true)]
//启动计时器
公共无效StartTimer()
{
//如果计时器尚未初始化
如果(!\u已初始化)
{
//将错误消息发回Excel
TimeTickEvt(这是“错误:计时器未初始化”);
返回;
}
尝试
{
//启动计时器
_timerThread=新线程(新线程开始(计时));
//开线
_开始=真;
_timerThread.Start();
}
捕获(例外情况除外)
{
System.IO.File.AppendAllText(@“C:\ErrorLog.txt”,例如Message+“-StartTimer-”+DateTime.Now.ToString(“hh:mm:ss.f”)+“\n”);
}
}
[ComVisible(true)]
//停止计时
公共无效停止计时器()
{
//停止线程
_timerThread.Abort();
//改变状态
_开始=错误;
}
私有void TimeTicking()
{
字符串发送时间;
//只要计时器还在运行
while(_启动)
{
尝试
{
//将计时器暂停正确的毫秒数
睡眠时间(_timein毫秒);
SentTime=DateTime.Now.ToString(“hh:mm:ss.ffff”);
//###########Excel忙时此处的代码错误###########
//引发一个事件,以便Excel获取线程完成睡眠的时间。
OnTimeTick(SentTime);
//_使用lastTimeReceived,这样,如果Excel和线程之间的链接断开,线程会在某些情况下停止
//如果未收到Excel的确认。
//如果还没有收到最后一次,我们将最后一次收到的时间设置为发送时间
如果(_lastTimeReceived.Equals(“”))
{
_lastTimeReceived=SentTime;
}
//如果最后一次接收的时间大于10 x timeinms(以秒为单位),我们将停止计时器。
else if(Convert.ToDateTime(_lastTimeReceived).AddSeconds(_timeinmillides*10/1000)