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.sample Class")
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.Serv iceBase
ServicesToRun = New System.ServiceProcess.Serv iceBase() _
{New srvInboundEDI()}
System.ServiceProcess.Serv iceBase.Ru n(Services ToRun)
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 (Apartment State.STA)
myThread.Start()
myLog.WriteEntry(System.Th reading.Th read.Curre ntThread.G etApartmen tState.ToS tring, EventLogEntryType.Informat ion) 'prints STA to the log
ProcessFileOnSingleThread( )
myThread.Join()
Return True
Catch ex As Exception
myLog.WriteEntry("Error processing file: " & ex.ToString, EventLogEntryType.Informat ion)
Return False
End Try
End Function
Private Sub ProcessFileOnSingleThread( )
myLog.WriteEntry(System.Th reading.Th read.Curre ntThread.G etApartmen tState.ToS tring, EventLogEntryType.Informat ion)
'prints MTA to the log
Dim myObj as Object = CreateObject("myDLL.testCl ass")
'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.
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.sample
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.Serv
ServicesToRun = New System.ServiceProcess.Serv
{New srvInboundEDI()}
System.ServiceProcess.Serv
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
myThread.Start()
myLog.WriteEntry(System.Th
ProcessFileOnSingleThread(
myThread.Join()
Return True
Catch ex As Exception
myLog.WriteEntry("Error processing file: " & ex.ToString, EventLogEntryType.Informat
Return False
End Try
End Function
Private Sub ProcessFileOnSingleThread(
myLog.WriteEntry(System.Th
'prints MTA to the log
Dim myObj as Object = CreateObject("myDLL.testCl
'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
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.