views:

978

answers:

4

I'm running a multithreaded windows service that need to call a VB6 dll. There's no documentation about this VB6 dll and this legacy system supports a very critical business process.

At first time (1º thread), this dll performs well. As other threads need access, it start provide wrong results.

I read one guys saying:

"Just be careful of one thing if you are using VB6. Your threading model is going to have to change to support apartments if you are running a multithreaded service. VB only supports multiple single-threaded apartments, but .NET runs fully free threaded normally. The thread that calls into the VB6 DLL needs to be compatible with the DLL."

Another guy from team gave me the idea to put this ddl in a separated application domain. But I'm not sure.

How can we work with VB6 dll called from a multithreaded c# windows service application?

A: 

This article on multithreading Visual Basic 6 DLL's provides some insight. It says:

To make an ActiveX DLL project multithreaded, select the desired threading options on the General tab of the Project Properties dialog box.

This article says there are three possible models to choose from:

One thread of execution 
Thread pool with round-robin thread assignment 
Every externally created object is on its own thread

I assume that the default is one thread of execution, and that one of the other two options needs to be selected.

Robert Harvey
A: 

You might want to take a look at this: linky

And here is a snippet that caught my attention:

VB6 COM objects are STA objects, that means they must run on an STA thread. You did create two instances of the object from two MTA threads, but the object itself will run on a single (COM (OLE) created) STA thread, and access from the two MTA threads will be marshaled and synchronized. So what you should do is, initialize the threads as STA so that each objects runs on his own STA thread without marshaling and you will be fine.

Anyway, VB style COM objects are always STA. Now in order to prevent apartment marshaling and thread switching you need to create instances in STA initialized apartments. Note also that when you set the [MTAThread] attribute on Main, you effectively initialize the main thread as MTA, when you create instances of STA objects from MTA threads COM will create a separate (unmanaged) thread and initialize it as STA (this is called the default STA), all calls to STA objects from MTA threads will be marshaled (and incur thread switches), in some cases Idispatch calls will fail due to IP marshaling failures. So the advise is use STA (and therefore VB6) objects from compatible apartments only.

Sergey
+2  A: 

When the threads come in, are you saving objects and reusing them later on new threads? If you can, create the objects fresh for every thread. We have a situation like this with a data layer dll we use. If you create a connection on one thread, it can't be used from another. If you create a new connection on each thread, it works fine.

If it's slow to create your objects, look at the ThreadPool class and the ThreadStatic attribute. Threadpools recycle the same set of threads over and over to do work, and ThreadStatic lets you create an object that exists for one thread only. eg

[ThreadStatic]
public static LegacyComObject myObject;

As a request comes in, turn it into a job and queue it in your thread pool. When the job starts, check if the static object is initialised;

void DoWork()
{ 
    if (myObject == null)
    { 
        // slow intialisation process
        myObject = New ...
    }

    // now do the work against myObject
    myObject.DoGreatStuff();
}
Steve Cooper
+1  A: 

You say

I'm running a multithreaded windows service that need to call a VB6 dll. There's no documentation about this VB6 dll and this legacy system supports a very critical business process.

and at the same time you say

At first time (1º thread), this dll performs well. As other threads need access, it start provide wrong results.

I'd make very certain that Management is aware of the failure you're seeing because the code supporting the critical business process is old and undocumented, and is being used in a way it was never intended to be used, and was never tested to be used. I bet it's also never been tested to be used from .NET before, has it?

Here's my suggestion, and this is similar to something I've actually implemented:

The VB6 DLL expects to be called on a single thread. Do not disappoint it! When your service starts, have it start up a thread of the appropriate type (I can't say, since I've deliberately forgotten all that STA/MTA stuff). Queue up requests to that thread for access to the VB6 DLL. Have all such access go through the single thread.

That way, as far as the VB6 DLL is concerned, it's running exactly as it was tested to run.


BTW, this is slightly different from what I've implemented. I had a web service, not a Windows Service. I had a C DLL, not VB6, and it wasn't COM. I just refactored all access to the thing into a single class, then put lock statements around each of the public methods.

John Saunders
I couldn't agree with John more. Since the VB6 DLL is effectively a black box for you, go with what is guaranteed to be true. At best, the DLL is going to support a single-threaded apartment (STA) threading model. See this link on MSDN for considerations when calling an STA COM object: http://msdn.microsoft.com/en-us/library/ms680112(VS.85).aspx. For maximum bang for the buck, you might want to simply leverage an adapter or singleton to regulate object access to one thread at a time (as John suggested). Be sure to watch your COM interop and marshalling from .NET, too.
Sean McDonough