通过COM抛出访问冲突的事件调用

时间:2017-05-19 17:54:39

标签: c# .net multithreading vb6 com-interop

我有一个遗留的VB6应用程序,其中我们引用了.NET DLL设置以向COM公开。在这个.NET DLL中的一个方法中,我们需要在等待事件时阻塞线程,一旦我们收到该事件,我们就可以从该方法返回。目前我正在创建一个Task对象来执行我们收到事件后所做的工作,然后我用这个任务订阅了所述事件,最后我WaitOne上的AutoResetEvent我用来控制线程。我还将任务ContinueWith设置为Set AutoResetEvent

当我使用控制台应用程序(用.NET编写)测试此设置时,我没有一个问题。但是,当我通过VB6调用相同的方法时,有时它可以正常工作,有时则不会,并且会无限期地挂起,等待该事件触发。

我一直在使用WinDbg分开它,我注意到所有挂起的尝试,都会抛出访问冲突。我正在尝试找出当前这种访问冲突的来源,但是在.NET代码中我们调用事件委托(如果不是null)是正确的。

以下是来自WinDbg的示例:

  

>>>实例化对象

     

>>>开始事件线程

     

>>>订阅OnCreateCtiConf

     

>>>等待任务结果。

     

>>>排队OnCreateCTIConf:没有消息

     

(1e44.1c4c):访问冲突 - 代码c0000005(第一次机会)

     

在任何异常处理之前报告第一次机会异常。

     

可以预期并处理此异常。

     

eax = 0a61e9d0 ebx = 0a65bccc ecx = 00000000 edx = 0a7057a4 esi = 0a61e9d0 edi = 0caef804

     

eip = 091249a4 esp = 0caef7e0 ebp = 0caef810 iopl = 0 nv up ei pl nz na po nc

     

cs = 001b ss = 0023 ds = 0023 es = 0023 fs = 003b gs = 0000 efl = 00010202

     

091249a4 3909 cmp dword ptr [ecx],ecx ds:0023:00000000 = ????????

以下是我们调用的方法有时挂起:

public bool AgentLogin(string extension, string agentId, string password)
{
    _extension = extension;
    _agentId = agentId;
    _eventQueue = new Queue<AvayaEvent>();
    try
    {
        _phone = new CTISoftphone($"ext={extension},logFile={Settings.LogLocation}");
    }
    catch (Exception ex)
    {
        addEvent(new AvayaEvent(CTISoftphone.CTIEvent.OnError, $"{_wrapperLogPrefix} Unable to create CTISoftPhone. The error was: {ex.Message}"));
        return false;
    }

    _phone.PhoneEvent += new CTISoftphone.PhoneEventHandler((evnt, msg) =>
    {
        Trace.WriteLine($">>> Enqueued {evnt} : {(string.IsNullOrEmpty(msg) ? "No message" : msg)}");
        addEvent(new AvayaEvent(evnt, msg));
    });
    Trace.WriteLine(">>> Starting events thread");
    t_avayaEvents.Start();

    var are = new AutoResetEvent(false);
    var task = new Task(() =>
    {
        try
        {
            Trace.WriteLine(">>> Calling LoginAgent");
            _phone.LoginAgent(agentId, password);
        }
        catch (Exception)
        {
        }
    });
    task.ContinueWith((t) => are.Set());

    var task_handler = new OnCreateCtiConfHandler(task.Start);

    Trace.WriteLine(">>> Subscribing to OnCreateCtiConf");
    OnCreateCtiConf += task_handler;
    Trace.WriteLine(">>> Waiting for task result.");
    are.WaitOne();
    Trace.WriteLine(">>> Unsubscribing from OnCreateCtiConf");
    OnCreateCtiConf -= task_handler;
     return true;
}

我们最初认为在我们订阅公开的PhoneEvent之前错过了事件,因此事件队列循环线程。基本上我们将AvayaEvent添加到Queue<T>,并且单独的线程处理这些事件。如果有一个委托,它会触发并忘记,否则,该事件将被重新排队。作为参考,我们在实现事件队列之前仍然遇到了这个问题。

对于COM互操作,我们做了一些事情:

  1. 已为Phone对象及其事件(成员和事件的单独接口)创建了接口

  2. ComVisible(true)已设置在我们需要向COM公开的任何内容

  3. DispId已在每个接口的所有成员上设置

  4. 我们的主要对象Phone的装饰方式如下:

    [ComSourceInterfaces(typeof(IClickToDialEvents))]
    [ComDefaultInterface(typeof(IClickToDial))]
    [ClassInterface(ClassInterfaceType.None)]
    [ComVisible(true)]
    public class Phone : IClickToDial
    {
        [...]
    }
    

    引用的接口IClickToDialIClickToDialEvents的装饰方式如下:

    [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
    public interface IClickToDial
    {
        [...]
    }
    
    [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
    public interface IClickToDialEvents
    {
        [...]
    }
    

    事件是Phone类的成员,下面是它们的样本示例:

    [ComVisible(false)]
    public delegate void OnCreateCtiConfHandler();
    
    public event OnCreateCtiConfHandler OnCreateCtiConf;
    

    进程事件循环如下所示:

    private void processEventsInQueue()
    {
        while (true)
        {
            if (_eventQueue?.Count > 0)
            {
                var evnt = getNextEvent();
                Trace.WriteLine($">>> Dequeued {evnt.Type}");
                switch (evnt.Type)
                {
                    case CTISoftphone.CTIEvent.OnCreateCtiConf:
                        if (OnCreateCtiConf != null)
                            OnCreateCtiConf();
                        else
                            addEvent(evnt);
                        break;
                    [...]
                }
             }
         }
     }
    

    最后,addEventgetNextEvent方法如下所示:

    private AvayaEvent getNextEvent()
    {
        lock (this)
            return _eventQueue.Dequeue();
    }
    private void addEvent(AvayaEvent evnt)
    {
        lock (this)
        {
            try
            {
                _eventQueue.Enqueue(evnt);
            }
            catch (NullReferenceException)
            {
                //swallow the nullrefexception since we null out _eventQueue when we dont want to queue anything
            }
        }
    }
    

    最后,当我们用COM注册这个DLL时,我们使用以下RegAsm命令:.\RegAsm.exe 'C:\Program Files\Avaya\ClickToDial\<OurDll>.dll' /codebase /tlb然后我们在VB6应用程序中引用生成的tlb文件。

    就像我说的,有时这很好用,我们从AgentLogin方法返回没有问题。 Othertimes,我们无限期挂起,我总是看到这些挂起的访问冲突。我说分裂大约是50/50。

    这是我参与的第一个COM互操作项目,所以我可能错过了一些明显的东西,但这让我发疯了!如果需要更多信息/说明,请告诉我。

    提前感谢您的帮助!

0 个答案:

没有答案