locked
How do I properly utilize static COM member in [ClassInitialize] and [ClassCleanup]? RRS feed

  • Question

  • I am trying to set up some basic unit tests in the VS2005 testing framework, and I am running into some issues with static COM member variables in my [ClassInitialize] and [ClassCleanup] functions. Basically, I am using a static license initialization object to initialize an appropriate license on startup and to check out a particular application extension for later tests. This functionality is called in a method with the [ClassInitialize] attribute. When all tests have finished running, and I try to check my extension back in and call Shutdown on the license initialization object in the method with the [ClassCleanup] attribute, I get some kind of COM interop error related to corrupt memory access and the RCW for this variable (it says Disconnected Context). I know that I can make the member variable a non-static variable and call the necessary functions from this variable during each individual test initialization and cleanup method, respectively, but this seems like a lot of unnecessary overhead. I would like to take the more logical approach of only having to call these methods once when they are actually needed. Does anyone have any advice on how to resolve this issue? I appreciate any help you are able to give. Thanks for your time.

    Michael Rice

    Monday, December 19, 2005 11:08 PM

Answers

  • Hello Michael,

    This problem is most likely caused that your COM objects are called on different threads. Static initialization happens on one thread, TestInitialize and TestMethods are run on another thread and TestCleanup runs on another thread. By default we use STA apartment model so if you are using COM objects in different threads, this may cause issues like interop exceptions "object disconnected from it's RCW". There is a couple of things you can do:
    1) Specify MTA ApartmentState in you Run Config: open .testrunconfig file in XML Editor. Find apartmentState. Change 0 to 1. It should be like this:
        <apartmentState type="System.Threading.ApartmentState">
          <value__ type="System.Int32">1</value__>
        </apartmentState>
    2) You can make your initialization in TestInitialize or ClassInitialize. You TestContext across your tests. Now if you don't call COM objects in Test/Class/AssemblyCleanup you will be fine.

    Thank you,
    Michael Koltachev
    Visual Studio Team System

    Tuesday, December 20, 2005 3:43 PM

All replies

  • Hello Michael,

    This problem is most likely caused that your COM objects are called on different threads. Static initialization happens on one thread, TestInitialize and TestMethods are run on another thread and TestCleanup runs on another thread. By default we use STA apartment model so if you are using COM objects in different threads, this may cause issues like interop exceptions "object disconnected from it's RCW". There is a couple of things you can do:
    1) Specify MTA ApartmentState in you Run Config: open .testrunconfig file in XML Editor. Find apartmentState. Change 0 to 1. It should be like this:
        <apartmentState type="System.Threading.ApartmentState">
          <value__ type="System.Int32">1</value__>
        </apartmentState>
    2) You can make your initialization in TestInitialize or ClassInitialize. You TestContext across your tests. Now if you don't call COM objects in Test/Class/AssemblyCleanup you will be fine.

    Thank you,
    Michael Koltachev
    Visual Studio Team System

    Tuesday, December 20, 2005 3:43 PM
  • Thanks for the help, Michael. Your first suggestion seemed to do the trick! I appreciate the information.

    Michael Rice

    Tuesday, December 20, 2005 3:56 PM
  • Hi,

    I have a similar problem and adjusted the Threading modell as suggested. I now have the problem that initialsation of the OCX - Control completely fails: The exception message says something like :

    "ActiveX-component 853aaf97-e49c-11d0-a303-0040c711066c could not be instanziated because the current thread is no single thread apartment"

    Sorry for the poor translation of a german exception message. Is there any possibility to solve it or to force the unittest to be performed in a single thread? Sure: I could do every initialisation in every test (using TestInitialize and TestCleanup), but I plan to use it in a test demo - sample and that really doesn't make the code look very nice and makes execution slow, especially, when integrating Unit-tests into the build process.

    Tobias Wenig
    Friday, May 12, 2006 11:42 AM
  •  

    Hi,

     

    I am seeing a simalar issue in VS 2008.  Would you give me the official way to turn off STA for 2008?

     

    Thanks

    Tuesday, February 12, 2008 3:57 PM
  •  

    I found the answer here, http://blogs.msdn.com/ploeh/archive/2007/10/21/RunningMSTestInAnMTA.aspx,

    It is, add the following to the testrun config.

    <ExecutionThread apartmentState="1" />
     
    I have confirmed this.
    Tuesday, February 12, 2008 7:14 PM
  • I have a issue I am wrapping Microsofts HTML control to use in a windows application. I am getting COM object that has been separated from its underlying RCW cannot be used COM object exception. In your above I can find the .testrunconfig file in my project. It's a window application, is there something I can do to in class intialized that I can set the apartmentstate to MTA.

     

    Thanks

    Michael

     

     

    Thursday, July 17, 2008 12:41 PM
  • Some responses suggest solving this by switching to multi-threaded apartments. I strongly recommend against this - stick with STA. My long winded blog describes the way around this and the reasons to avoid it. Summary.

    MTA is a bad solution because:
    1. STA is the default. Many COM components will not work with Multi Threaded Apartments. For example, the .WebBrowser control in .NET 2-0. Even the trivial .NET file open dialog requires STA. You’ll get errors such as “ActiveX control '8856f961-340a-11d0-a96b-00c04fd705a2' cannot be instantiated because the current thread is not in a single-threaded apartment.” and "Current thread must be set to single thread apartment (STA) mode before OLE calls can be made. Ensure that your Main function has STAThreadAttribute marked on it." The latter, from file dialog, only occurs when a debugger is attached.

    2. If the actual application will run in STA, a test using MTA is an invalid test. Assuming this is an automated integration test  you want it to exercise the code in a realistic manner. I assume it's an integration test because it interacts with heavy COM dependencies. By definition, these are not unit tests. Anyway, testing under a different threading model will lead to false failures and successes.

    Solution:

    Problem 1: I don’t want to create a heavy office application for each test.

    Solution: While you can't share COM RCWs across threads, most heavy COM objects such as MS Office applications allow you to get an existing process via Marshal.GetActiveObject(). I create an Office Application wrapper and a builder responsible for its creation. The tests call something like Builder.GetApp(). This gets a new COM RCW that might be an existing MS Application process or a newly launched Application.

    Also note that even in Single Threaded Apartment state, you can share these wrappers and COM RCWs in a thread static variable.

    Problem 2: What about all those other smaller com objects? I want to set up a scenario during test initialization with lots of cells or ranges, then use those little COM objects in my tests.

    Solution: Store a way to find the COM object during test initialization (store the cell location, a task uniqueid, or something like that), rather than the actual COM RCW object. Then write a utility that provides a quick way to get the COM object for the test from the static variable. For example Wrapper.GetCellForActiveBook (identifier).

    Don't forget to close the COM objects!
    Read and understand Marshal.ReleaseComObject and the very handy Marshal.FinalReleaseComObject. You may need to call methods such as Excel.Application.Quit (before releasing the com object) to close an Excel process you launched. Cleanup can be tricky. For this reason, I package all heavy com objects in wrappers that implement IDisposable. A Builder pattern handles creation, and the IDisposable pattern handles cleanup.

    Happy testing!

    Sunday, August 3, 2008 6:55 AM
  • Update - responding to self. Having worked through the trouble of getting tests to run in STA mode, I'll suggest that running in MTA mode might be a good idea if you are in a hurry. My scenario demands some pretty thorough (and proper) testing, but not everyone's scenario will.

    I've managed to work around the RCW problems with only 1-3 days of work - creating wrappers and builders and all sort of instancing magic. Application objects are generally easy to get to across different threats (using GetActiveObject), but that approach does not address document level objects well (Excel.Workbook, MSProject.Project). You could choose to create them in test initialization, save them to disk, and reload for each test (creating the COM RCW on the test thread) ... but I just chose to create a new instance for each test.

    Anyway, while it's possible to get your MS Office / COM tests to work in STA mode, those of you who are in a hurry should just use MTA mode, or create one test project running in MTA, and a different project in STA. 


    Tuesday, August 19, 2008 8:49 PM
  • Hi. After long searches your suggestions seemed to me closest to my problem.

    I am developing a web service that will a) login to an account b) pull out information from this logged in account. Now the web service runs as MTA, and I call a sub void with (STA Attrib) and call it in a different thread. This works fine.

    But; the initializing of web browser component + login process takes around 30 to 35 seconds, which I want to avoid. Is there any chance to initialize the Com object (WebBrowser) and keep it in Application/Session memory, so that I can log in once and can access only the logged in information each time I cann the service's function?

    Thanks in advance.

    [WebService(Namespace = "http://tempuri.org/")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    [ScriptService()]
    public class garantifatura : System.Web.Services.WebService
    {

      private MyWebBrowser WebBrowser1;// = new MyWebBrowser();

      public garantifatura()
      {
      }

      [WebMethod(EnableSession = true)]
      public string FaturaBorcSorgu(string KurumTipi, string Kurum, string TesisatNo)
      {
        try
        {
          _kurum = Kurum; _kurumtipi = KurumTipi; _tesisatno = TesisatNo;
          Thread theThread = new Thread(Internal_FaturaSorgulama);
          theThread.SetApartmentState(System.Threading.ApartmentState.STA);
          Debug.WriteLine("Starting...", "THREAD_FaturaBorcSorgu " + theThread.ManagedThreadId.ToString());
          theThread.Start();
          theThread.Join(90000);  //90 saniye
          Debug.WriteLine(_RetVal, "THREAD_FaturaBorcSorgu " + theThread.ManagedThreadId.ToString());
          return _RetVal;
        }
        catch (Exception ex1)
        {
          return "0±FaturaBorcSorgu±" + ex1.Message; ;
        }
      }
      [STAThread()]
      private void Internal_FaturaSorgulama()
      {
        try
        {
          if (_kurumtipi == "") { throw new InvalidProgramException("Kurum Tipi Seçilmemiş"); }
          if (_kurum == "") { throw new InvalidProgramException("Kurum Seçilmemiş"); }
          if (_tesisatno == "") { throw new InvalidProgramException("Tesisat No Girilmemiş"); }
          decimal dummy = 0;
          if (!Decimal.TryParse(_tesisatno, out dummy)) { throw new InvalidProgramException("Tesisat No Hatalı"); }
          //if (WebBrowser1 == null) WebBrowser1 = new MyWebBrowser(); else WebBrowser1 = (MyWebBrowser)Marshal.GetActiveObject("MaxFatura.WebBrowser");
          //if (Session["WebBrowser"] == null)
          WebBrowser1 = new MyWebBrowser();
          //else
          //  WebBrowser1 = (MyWebBrowser)Session["WebBrowser"];
          Login();
          if (_RetVal.StartsWith("0")) return;
          TesisatNoIleFaturaCagirma();
        }
        catch (Exception ex1)
        {
          //WebBrowser1 = null;
          _RetVal = "0±InternalFaturaSorgulama±" + ex1.Message;
        }
      }

    Sunday, October 5, 2008 9:06 PM
  • I've encountered the same problem in unit testing using VS 2010. where could i found the specific configuration in VS 2010?
    Sunday, October 3, 2010 8:11 AM