C# 使用线程运行COM对象时Excel崩溃

C# 使用线程运行COM对象时Excel崩溃,c#,multithreading,vba,excel,com-object,C#,Multithreading,Vba,Excel,Com Object,我在运行COM(自制)计时器时在Excel中遇到错误。 基本上Excel实例化计时器,初始化它并启动它。然后,计时器每X毫秒滴答一次,引发Excel捕获的事件(相当标准的东西)。我不使用Excel本身作为计时器,因为它的滴答声不会超过每秒钟(这对我来说太长了) 我的问题是,如果我在计时器引发事件时单击并按住电子表格,Excel会崩溃得很厉害。不幸的是,用户(有时)需要在计时器运行时单击电子表格并修改它 我在某处看到我可以在计时器中使用IMessageFilter接口。这应该确保当事件启动时,如果

我在运行COM(自制)计时器时在Excel中遇到错误。 基本上Excel实例化计时器,初始化它并启动它。然后,计时器每X毫秒滴答一次,引发Excel捕获的事件(相当标准的东西)。我不使用Excel本身作为计时器,因为它的滴答声不会超过每秒钟(这对我来说太长了)

我的问题是,如果我在计时器引发事件时单击并按住电子表格,Excel会崩溃得很厉害。不幸的是,用户(有时)需要在计时器运行时单击电子表格并修改它

我在某处看到我可以在计时器中使用IMessageFilter接口。这应该确保当事件启动时,如果Excel正忙,计时器可以看到这一点并相应地采取行动。然而,我无法正确地实施它

如果有人能帮我,那就太好了

以下是我正在使用的源代码:

在Excel中,我有一个带有WithEvents ExcelTimer.ExcelTimer对象的singleton,下面是我的singleton的代码:

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)