Creating a Web service in .NET is easy. Setting up a call to a COM object in .NET is very easy as well. However, putting the two together can be difficult when the COM object uses a single-threaded apartment model. You see, Web services are inherently defined by a multithreaded apartment model. However, making a Web service call a single-threaded, apartment-model COM object is not impossible; I will show you how.
What is a threading model?
Windows, like most operating systems today, allows you to have multiple programs running at the same time. Every program is essentially a process (or in rare cases, a collection of processes). A process is a collection of memory address space containing code and data, as well as other system objects and handles that a process may need. Every process in Windows has the ability to have multiple threads of execution. A thread of execution, or more simply a thread, is a path of execution in the code. When the process starts, one thread is started as the main function for the program. From there, the main thread can spawn other threads as needed. Most programs are developed for only a single thread of execution. In other words, the main thread does not ever spawn any new threads.
Another way that threads are spawned is if multiple requests come in for the same type of object. For instance, when a second request is executed to a Web server, a new thread is spawned to service that request. With a Web service, each new request to the Web service is automatically spawned as a new thread in the Web service process. Because of this, Web services are inherently multithreaded.
In COM, the way that a COM object handles multiple threads is important, so you should be clear on how the outside world should communicate with the object. Most COM objects developed by programmers in languages like Visual Basic and Delphi are single-threaded. In other words, they do not internally understand the concept of multiple threads operating at the same time within the process.
But the options for COM objects do include single-threaded or multithreaded. Basically, a single-threaded COM object will enforce the running of only one thread of execution at a time. A multithreaded object will allow multiple threads to run at the same time. Free-threaded, which you may have heard of, is really another name for multithreaded objects. There is no restriction that you must use the same kind of threading model for every object.
Calling a COM object from .NET
Calling a COM object from .NET is simple enough. You set up the reference in the project and VS.NET creates an interop object for you to use to call the COM object. From there, it is just like creating any other object from a native .NET class. This works well when the threading model of the COM component matches the threading model of the .NET application. However, when this is not the case, you will receive an error when you try to instantiate the object.
The solution is to call the COM object from a thread that has the same threading model as the COM object itself. In cases like a Web service, where you cannot control the threading model of the main thread, you will have to spawn a new thread using the System.Threading namespace. In the new threads that you create, you have the option of controlling the threading model so you can match it to the COM component.
To create a thread, you will first have to create a ThreadStart delegate. If you are unfamiliar with delegates, they are essentially function pointers. Rather than using an actual function name, you can assign a function name to a delegate and then use that delegate to perform the operation. The ThreadStart is, as the name implies, the starting point for the thread. You are telling .NET where to start executing code.
The ThreadStart delegate is for a function that takes no parameters and returns no parameters. In order for this to be of much use, you will want to encapsulate the function in an object (technically making it a method call). In this way, you can initially create an object, stuff the data the thread will need into the object, and then have the method that you use for ThreadStart process the data that is already in the object.
Unwinding the threads when done
Before I show you a fully functional code listing, there are two things that you should know about threads. First, threads may end at any time. They are running independently of one another. However, in most COM object calling situations, you will eventually want to wait on the COM object to finish its work before proceeding. On a Thread object, that is the purpose of a Join() method call, which pauses the current thread (the main thread, in our case) until the thread being referenced has stopped. This is a simple way of synchronizing the operation of one thread to another.
The second important point about threads is that they exist within their own exception space. Exceptions thrown in a thread are not sent back to the thread that spawned them. As a result, you will need to wrap your code in a try/catch block and store the thrown exception in the object that you wrapped the function call in. Then, the calling thread can check to see if the thread has completed (Thread.IsAlive == false) and then check if there is a value other than null in the internal exception. If there is a value, then you know that your thread threw an exception. If not, then there were no problems.
Seeing it in action
Rather than showing a Web service that would require either using the automatic Web interface or using a specialized tool to call the service, I created a simple console application which performs the exact same steps that you would perform in a Web service.
Listing A (Class1.cs) shows a simple console application class that creates 60 threads by creating 60 instances of a shell class and then creates a thread that starts in the ThreadStart delegate of each of those classes in turn. It sets the threading model for the thread that was created to STA, or single-threaded apartment. This allows even a Web service to call STA COM objects. Once all of the threads are created, it iterates through the array of threads and joins to each one to ensure that it closes properly.
Listing B (ShellClass.cs) is the class that each thread instance lives in. It contains a holder for any exception that is thrown (which the console application ignores). It also contains some basic trace statements and a creation of our COM object. In this case, the object is a Delphi object whose only purpose is to expose the Sleep method; however, any STA COM object will work.
Tying up
Getting a STA COM object to run in an MTA architecture is not straightforward; however, it can be done safely if you are willing to get tied up with a few threads.