Events not firing from COM component when in Unit (Integration) Test

有些话、适合烂在心里 提交于 2019-12-25 09:14:28

问题


I have an unmanaged DLL which I'm trying to create a .NET wrapper library for but am getting different behavior when I try and run a NUnit(v3) test over it compared to if it is just run from a button click off a WinForm app.

Background: During startup of the DLL I call its Connect() method, which ultimately causes the DLL to make a TCP connection. When the TCP connection is established I then get notified by wiring up a handler to its "Connected" event. Once connected I then call other commands on the DLL.

In a simple test Winforms app, I have 1 button which instantiates the "DLL" and then calls the Connect() method. When the thread completes, the app sits idle for about 2 seconds, and then the "connected" event handler fires as expected. The event does not return anything.

But because the connect() is an expensive operation, and because my library is destined for a larger application, I created a ConnectAsync() method in my library and made use of the async and await keywords, and a AutoResetEvent. The ConnectAsync() method returns an instance of the "instantiated" DLL after it gets notified that the TCP connection is up from the event. A bit of refactoring to the test WinForms app, and it works as expected.

Next step was to make an integration test using NUnit. However when the ConnectAsync() method is called from an async test, I can see the TCP connection establish on the remote application, but the event handler never fires. A day's worth of testing, searching and trial and error could not turn up why the ConnectAsync() works perfect off a simple Winforms button but not from a UnitTest.

Here is the test code

[Test()]
public async Task Test1()
{
    var conn = await GetConnection();
    //assert some commands on the conn
}

private async Task<TCPConnector> GetConnection()
{   
    return await Task.Run(() =>
    {
        var mre = new AutoResetEvent(false);        
        var ctrl = new TCPConnector();
        ctrl.serverName = server;
        ctrl.serverPort = serverPort;
        ctrl.onConnected += () => { mre.Set(); };
        ctrl.Connect();
        mre.WaitOne();
        return ctrl;
    });
}

I know this is not strictly a question, but I'm stumped and looking for ideas to try. Or pointers as to what is different between a button click event and a NUnit test execution.

In case it means something to somebody, the dll I'm calling is an unmanaged ActveX

Update1: If use MSTest it works! So it has something to do with NUnit's startup environment.

Update2: Through investigations in this SO post, I by chance replicated the same behaviour without any unit testing frameworks, but instead via reg free COM. So I'm now thinking it is something to do with how the COM is activated and consumed ?

Resolution Finally found the answer. Credit to Chris for his answer on this question. All I had to do was add a <comInterfaceExternalProxyStub /> section to the manifest as outlined, and bingo

UPDATE 4

Ignore the last updates and the resolution. They contain misdirection and false positives, and some lack of understanding of my behalf as I worked through the whole world of COM, Regfree COM, Interop, and COM Events. The problem is still unresolved.

The key issue remains that when the COM is run under the context of a unit test the COM events do not fire. When run in a plain .exe they work just fine


回答1:


My guess, without knowing what exactly the unmanaged DLL is doing, is that it is an single threaded apartment (STA) COM dll. In this threading model, COM interop will marshall all calls to the DLL to the thread that creates the object (which in your unit test is blocked waiting on the auto reset event, thus nothing happens).

The event pattern works in the Winforms app because the main UI thread is an STA thread (check the attribute on your main method) and it is pumping messages, so callbacks are allowed and locks are superseded by COM message pumping.

If this is the case, the only way to test the wrapper would be to create an STA thread, run a message pump on it, then pass a message to the thread to trigger the creation of the COM object and the connection (in other words, it's a huge pain). What's worse is that the object will behave this way in a client application as well, so unless you create an STA thread in your wrapper and marshall all calls to it, you will not be able to use it asynchronously.



来源:https://stackoverflow.com/questions/39943244/events-not-firing-from-com-component-when-in-unit-integration-test

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!