1

From what I understand from apartments in .NET, when we create a apartment threaded COM object inside a STA thread we should get a reference to the real object, not the cross-apartment proxy.

[TestMethod]
Public void ExcelShouldNotBeMarshalled()
{
     var excelApp = new Microsoft.Office.Interop.Excel.Application();
     if (excelApp is IMarshal)
         throw new Exception(“Should not be a proxy as we are running this in a STA and registry ThreadingModel is empty”);
}

I cannot get this to work with any combination of ThreadingModel I try.

Is there anything I could be missing?

Why proxies are not okay from me? I want my test to run as closest as possible to the real app. When the application is running as Excel add-in, then I get non proxy references and everything work as a charm, but in tests, events callbacks do pump to random MTA threads instead of pumping back in the UI Thead.

Simon Mourier
  • 132,049
  • 21
  • 248
  • 298
ErikWitkowski
  • 460
  • 3
  • 8
  • 2
    If you connect to an out of process COM server, you always get a proxy. – Simon Mourier Oct 19 '20 at 10:51
  • Just use Thread.CurrentThread.GetApartmentState() to do this test. – Hans Passant Oct 19 '20 at 10:58
  • @SimonMourier thanks, I think it solves my original question. Now a second question would be, if there is any way to make sure event handlers pumping from this proxy application object are always propagated to the same STA thread (as if it was in-process)? – ErikWitkowski Oct 19 '20 at 11:10
  • Not sure what you're exactly after and I don't understand what you mean by "...event handlers ... are propagated to the same STA thread". Is your thread where you created the excel object STA or MTA? – Simon Mourier Oct 19 '20 at 17:47
  • It is Sta. For instance, when I try to respond to Worksheet.Change(target) event inside my test, it bubbles up to a “random” MTA thread, instead of the Sta thread which I assigned the event handler in first place. – ErikWitkowski Oct 19 '20 at 17:57
  • 1
    If you want your "test to run as close as possible to the real app"...then, why not write an AddIn and actually use the real app? Or...you really don't need to do that...write some C# that is callable by an Excel VBA script. Excel VBA could create your C# COM object which then would be in same apartment as Excel, initialize the object with the Excel object of your choice, and run your test. If you are calling Excel from a different process, there will always have to be marshaling. – Joseph Willcoxson Oct 19 '20 at 22:56
  • Ok, yes this is by design, .NET COM object (you have one w/o even explicitly creating it) can be called from any aparment (~=thread): https://learn.microsoft.com/en-us/dotnet/standard/threading/managed-and-unmanaged-threading-in-windows?redirectedfrom=MSDN#managed-threads-and-com-apartments see here if you want to (try to) tweak things: https://stackoverflow.com/questions/28628811/how-to-make-make-a-net-com-object-apartment-threaded/28640078#28640078 IMHO you shouldn't need this kind of plumbing (or why is this such a problem) – Simon Mourier Oct 20 '20 at 07:21
  • @JosephWillcoxson thanks for the idea, I'm thinking this is the way to go, just need to sort out integration to my test runner process so to run normally as other tests (starts excel process passing test parameter, attach debugger, detect test readiness or error, collect result from external process) shouldnt be hard I think – ErikWitkowski Oct 20 '20 at 07:56
  • @SimonMourier I suppose the tweak would require me to rewrite bits of the excel library right? Probably dont wanna go down this path. The event handling is a bit of a problem because I need all UI code to run on the UI thread, just like when running the real app. – ErikWitkowski Oct 20 '20 at 08:04
  • Just marshal the result(s) back to the UI thread. If Winforms, use Control.BeginInvoke, if WPF use Dispatcher.BeginInvoke, etc. – Simon Mourier Oct 20 '20 at 09:01
  • That will not do, because sometimes the UI thead itself triggers the event and it must not bubble up in another thread, otherwise I cannot dispatch back to the UI thread which is blocked waiting for the event to complete. I'm trying the suggestion from Joseph to run the test inside the excel process (calling .Net from VBA), looks promising. – ErikWitkowski Oct 20 '20 at 18:28
  • This suggestion is a completely different thing. Out-of-process, BeginInvoke will always do. If it blocks it's because you don't pump windows messages or for some other reason. Or show a reproducing code where it doesn't. – Simon Mourier Oct 20 '20 at 20:35
  • Thanks for the help. I could probably try the BeginInvoke approach as you described, but it gets to the point where production code is changed only to get the tests working, so I'm not putting more effort in this direction. Furthermore, I am moving to the idea from Joseph to run the test in the same process as Excel and avoid marshalling. In my case it solves my UI threading problem and plus gives bit more of speed to run the tests. I think we can close this thread, thanks again to both of you! – ErikWitkowski Oct 21 '20 at 21:44

1 Answers1

1

As Simon replied in the comments, Excel runs in a separate process and the ‘new Application’ part of the code will always give a proxy - no matter what theading model.

To avoid proxy objects and run the test just like in production, Joseph proposed in the comments to run the test using VBA, calling .Net methods. I found it suitable so I implemented using xUnit capabilities to customize test execution and handle the VBA invoking my test methods. It solves my proxy related problems and as a plus, improves test speed.

Thanks to all involved!

ErikWitkowski
  • 460
  • 3
  • 8