Link to home
Start Free TrialLog in
Avatar of rjberryman
rjberryman

asked on

Using COM Interop on a Single Threaded Apartment from a Windows Service

I am using Visual Basic in Visual Studio 2005 and .NET Framework 2.0

I'm writing a Windows Service that loads up multiple File System Watchers on different directories.  When the OnChanged() event is activated (i.e. a file is dropped or FTP'd to one of these directories) the service processes the file and loads them to a SQL Database.  This processing uses an unmanaged DLL via COM Interop.

I use this DLL in various web applications in ASP.NET successfully by adding the aspcompat="true" page directive to any pages that use the unmanaged DLL.  The COM Interop and DLL works great in Windows Forms applications.

When I try and use COM Interop and the unmanaged DLL in a Windows Service, the service hangs when try and instatiate it using either:

Dim myObject as Object = CreateObject("mydll.sampleClass")
or
Dim myObject as new myDLL.sampleClass

When I run the code in the IDE I do not get the hang.  This only occurs when I compile and install the service.  I have ruled out permission issues by running the service on the logged in Adminstrator account.  I have ruled out improperly registered DLL my using the same DLL with other applications.  This application, others and the DLL are all installed in the same directory.

I have set Try / Catch blocks around the problem line, attached to the process and cannot trap an error.  I have loaded the unmanaged DLL in a debugger with a breakpoint at the activation point, but can't hit the breakpoint.

After over a week of working on this issue I am leaning towards this being a threading issue with COM Interop.

According to MSDN:
http://msdn2.microsoft.com/en-us/library/system.stathreadattribute(VS.80).aspx

'In the .NET Framework version 2.0, new threads are initialized as ApartmentState.MTA if their apartment state has not been set before they are started. The main application thread is initialized to ApartmentState.MTA by default. You can no longer set the main application thread to ApartmentState.STA by setting the Thread.ApartmentState property on the first line of code. Use the STAThreadAttribute instead."

Immediately before I instatiate the COM Interop DLL, I noticed my CurrentThread apartment state was indeed MTA.  My unmanaged DLL is compiled STA.  It made sense to me that this would cause the application to essentially "hang" if the threads couldn't sync with each other.

The only explanation I can find  on the usage of the STAThreadAttribute is to set it on the entry point or main function.  So I assumed I could set this on either the File System Watcher's OnChanged() event (since this is the bottom of the call stack while debugging) or the Service's Sub Main().  I have tried both and in both instances the CurrentThread is still MTA by the time my Interop DLL gets instantiated.  (I actually tried it in every function and sub leading up to my COM Interop call with no luck)  

Here's how I used the attribute:

 <STAThreadAttribute()> _
Sub Main()

     InitializeLog() 'instantiates a log I'm using to write unhandled exceptions and other stuff to
     Dim ServicesToRun() As System.ServiceProcess.ServiceBase
     ServicesToRun = New System.ServiceProcess.ServiceBase() _
            {New srvInboundEDI()}

     System.ServiceProcess.ServiceBase.Run(ServicesToRun)

End Sub

 If I check the apartment state in the Sub Main() it is STA at this point, but as soon as it leaves, it switches back to MTA.

I think the root of the problem is that I need to call the COM Interop DLL on an STA thread.  The STAThreadAttribute is suppossed to ensure this happens but I don't think I'm implementing it properly.

Another suggestion I have seen is to create a new thread, set it to ApartmentStateSTA and call your process on it.

So I tried this:

 Function ProcessFile() As Boolean

     Try
          Dim myThread As Thread = New Thread(AddressOf ProcessFileOnSingleThread)
          myThread.SetApartmentState(ApartmentState.STA)

          myThread.Start()

          myLog.WriteEntry(System.Threading.Thread.CurrentThread.GetApartmentState.ToString,                     EventLogEntryType.Information)   'prints STA to the log

          ProcessFileOnSingleThread()

          myThread.Join()

         Return True

     Catch ex As Exception
          myLog.WriteEntry("Error processing file: " & ex.ToString, EventLogEntryType.Information)
          Return False

     End Try
End Function

Private Sub ProcessFileOnSingleThread()

      myLog.WriteEntry(System.Threading.Thread.CurrentThread.GetApartmentState.ToString, EventLogEntryType.Information)
'prints MTA to the log

     Dim myObj as Object = CreateObject("myDLL.testClass")
     'hangs here

End Sub

As you can see, this approach does create a thread in the STA apartment state, but when you enter the called sub, the thread state is once again MTA.

I even added the <STAThreadAttribute()> and <STAThread()> attribute (intellisense only gives me the STAThread but both compile and the online documentation lists STAThreadAttribute) to each procedure header with the same results.

So I've tried using the STAThreadAttribute, STAThread and creating a single STA thread to run a procedure on without any success.  Please help... Thank you.
ASKER CERTIFIED SOLUTION
Avatar of rjberryman
rjberryman

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial