We have very old legacy vb6 application, that has one global object that serves as Application Core, that stores different application settings, invokes database operations and so on. Multiple modules with different progid use this global object and have no problem with it due to single-thread apartment.
Not long ago new WPF application was created, that provide us transition from vb6, however it is still limited by vb6 legacy due to some architectural mistakes. It is able to connect to only one database per app instance. It hold static instance of vb6 global object in wrapper class, that serves as bridge to reach legacy functionality.
Now, we are developing new application that should not be limited by old legacy code, in particular it new application should be able to connect to several databases at once, but there is a catch: vb6 code limited to single database, so there should be several instances of vb6 global objects, one per each database.
So the question is: is it possible, and if it is, how is it possible to use several separated intstances of global vb6 objects in same C# application?
I presume that each instance of such object should live in it's own STA-Thread, but i don't know how to create such threads, that are kept alive for entire application runtime and that have assotiated wrappers, containing instances of global vb6 objects and supporting invocation of some functions from GUI thread (and how to organize such cross-thread communication, there is no thread.invoke(...)). I thinked about using wpf dispatcherization model ( wrapper class is DispatcherObject, each instance has it's own Dispatcher with it's own STA-Thread), but i cannot see how to implement such thing. Also I think it could be imlemented by loading each instance of wrapper class (static) in different AppDomains, but i don't know if it resolves STA problem for COM.
It is possible, using what is referred to as unmanaged code. Check out this:
http://www.codeproject.com/Articles/154144/Using-Unmanaged-VB6-Code-in-NET
You might get away with running one instance of the global object per AppDomain.
To be safe, you should run them each in its own process, since that's the assumption they were created with. I guarantee you that horrible things can happen when you violate the assumptions of ancient code - especially when you introduce them to things which simply did not exist when they were written.
Related
Let's say, I am executing an exe written in c#(just my choice of language). It has the following piece of code:
var comObj=new ComClass();
comObj.DoSomething();
Now, I would like to know in which process is the DoSomething method executed. Is it the same process where the current exe is running or a different process responds to the DoSomething call?
This is entirely transparent in COM, you cannot find out from your program either. It is determined by configuration information that is stored in the registry. The core reason why COM servers need to be registered. The different scenarios are:
On the same thread that creates the object. Used when the server is registered as an in-process server and the thread's apartment is compatible with the threading model of the COM object. The most common case, particularly so when you create objects on the UI thread of a program.
On another thread, if necessary created by COM, to give the object a thread-safe home. This commonly happens when your new statement runs on a thread that's in the MTA, the multi-threaded apartment. Commonly from a worker thread. The object you create is a proxy, its primary job is to serialize the arguments you pass to a method and deserialize them in the stub which runs on the other thread. It ensures that all calls on the object are thread-safe. Otherwise the same kind of mechanism as used in .NET Remoting. The underlying layer that takes care of the marshaling is LRPC, an obscure Windows component that was optimized to make inter-thread and inter-process calls as fast as possible.
Inside a surrogate process for an in-process component. Not very common but surrogates can be very handy to bridge a process bitness problem for example. Allowing you to use a 32-bit server in a 64-bit process. Requires both 32-bit and 64-bit proxy/stubs.
Inside another process that was registered as an out-of-process server. The canonical example are Microsoft Office programs like Word and Excel, very common in .NET programming. This is where COM starts to get brittle, unexpected program aborts tend to cause a mess when the server keeps running. A very common question at SO.
Inside another process on another machine. Called DCOM or Distributed COM. An extra configuration step is necessary to ensure the target machine and proper account privileges can be selected. Pretty notorious for giving humans a splitting headache, it doesn't get used much anymore these days. DCOM's biggest claim to fame was enabling Java to eat Microsoft's lunch in the middle-ware wars of the late 90s.
If you have no idea which of these scenarios applies in your case then a utility like SysInternals' Process Monitor tends to provide insight. You'll see your program reading the registry, telling you where to look, and load a DLL or start an EXE.
From COM Clients and Servers
There are two main types of servers, in-process and out-of-process. In-process servers are implemented in a dynamic linked library (DLL), and out-of-process servers are implemented in an executable file (EXE). Out-of-process servers can reside either on the local computer or on a remote computer.
I do think that the names are quite explicit :-)
Note that even for out-of-process COM servers, there will be some code in-process that will do the marshaling between the COM client and the COM server
How to make process-1 able to fire an event in process-2, and send along few argument, to signal the 2nd process to do a specific action, and optionally receive a reply?
It is possible to do this using the filesystem, there could be a file, where process-1 dumps some commands/querys, and process-2 would be constantly reading from that file, but, this solution is not nice.
Any other way to do it?
(I know that its easy in VB.net to fire an event in a running process whenever a new process is started, IF the "single instance" is enabled in the project properties)
You can use named EventWaitHandle to achieve cross-process synchronization.
This article seems to do what you are used to with vb.net single instance (and it seems still a viable option).
In short it seems that there are three approaches to accomplishing single instance like solutions:
Use a Mutex
Cycle through the process list to see if a process with the same name is already running
Use the Visual Basic system for single instance apps (which you can access from C#)
If by "process" you mean "app-domain", it's fairly easy to set up eventing between the two. In fact if you have two classes in two separate app-domains (where each class has MarshalByRefObject as a base class), then .net will automatically set up a remoting structure that will make events appear to behave as they would in a single app-domain. (Example here: http://msdn.microsoft.com/en-us/library/system.marshalbyrefobject.aspx)
The key here though is 'appear'. 'App-domain' and 'process' separation are intended to keep resources isolated on purpose. To access anything outside of your process you really need help from the operating system, like a shared file or internet connection or named pipes - something to that effect. But .net concepts like events don't exist outside of your space in the runtime.
In other words, you'd have to use something like Named-Pipes (http://msdn.microsoft.com/en-us/library/system.io.pipes.namedpipeserverstream.aspx) if both processes are on the same machine, TCPClient/TCPListener (http://msdn.microsoft.com/en-us/library/system.net.sockets.tcpclient.aspx) if communicating across multiple systems, or WCF if you need something more heavy duty.
If you'd like to see a specific example of one of these technologies in practice, I can write one up for you, btw.
I've written a C# program that talks to a COM server to conduct simulations. It works without any trouble, but the simulations being carried out by the COM server are fairly processor intensive, and only run single core.
As such, I've used Parallel.For to distribute the workload amongst multiple threads. It appears, however, that all the simulation results generated by the COM server are shared amongst all instances of its classes, so when I'm running the parallel task with only 1 thread, everything works as expected, but when I'm running the task with multiple threads, the results are completely garbled (as multiple threads are effectively causing the simulation engine to replace its results with new ones as they are being read).
I was wondering if there was a way to connect to the COM server multiple times in order to stop the results-sharing of class instances?
Edit
My process for connecting to the COM server was to:
Add a reference using Project->Add References->COM (VS2010)
Use the following code to instantiate the simulator object:
dss = new OpenDSSengine.DSS();
dss.Start(0);
The above code is called in the local thread data initialiser (localInit) parameter of Parallel.For, and thus a new dss object is created for each thread, but the results obtained seem to be common across all threads.
The COM server is a dll.
As you specify that your COM server is actually an in-proc server (a .dll instead of .exe), it means that every time you execute new DSS() you actually create a new instance (unless it is created with singleton class factory, which is rare but possible).
The problem, according to your description, seems to be with the fact that the DSS implementation uses some static/global state which results in garbled data when you parallelize the execution.
In that case you can run each instance of the server in a separate process by using DllSurrogate. If the default surrogate (dllhost.exe) doesn't suffice, it is possible to write the custom one. Be aware that moving the server into another process will introduce marshaling overhead for each method call done against the server.
Please also not that if you are using an STA COM server, your parallelization will have no effect, as all the calls to the server are serialized by COM infrastructure.
All that being said, before going there make sure that the problem is not on the caller side, i.e. with your parallelization and not the server itself.
First try creating multiple instances of the COM object (just call new OpenDSSengine.DSS() multiple times, storing the results in separate variables, or in an array). If the COM server was implemented well, those multiple instances will co-exist in your process without interfering with each other, and your multi-threaded client code can use them simultaneously.
If you still find that those instances are interfering with each other, that means the COM server is using some state that is global to the process. The only way to get around that would be to invoke the multiple COM objects via multiple surrogate processes, as others have suggested.
I have a program that executes for 24 hours, then restarts.
How can I shift main() from into a separate application domain, which is torn down and refreshed every 24 hours, in order to completely eliminate any potential memory leaks?
You had me right up until you said:
in order to completely eliminate any potential memory leaks?
If you want to run code in another app domain then there are plenty of resources on how to do this, for example Executing Code in Another Application Domain (C# and Visual Basic). The basic principle is to create a class that inherits from MarshalByRefObject. You then create your new app domain and instruct it to create an instance of that object - this object is then your "entry point" into your app domain:
AppDomain newAppDomain = AppDomain.CreateDomain("NewApplicationDomain");
ProxyObject proxy = (ProxyObject)newAppDomain.CreateInstanceAndUnwrap("MyAssembly", "MyNamespace.MyProxy");
However in C# there isn't really any such thing as a "memory leak", at best you just have objects which are inadvertantly kept in scope. If this is the case then an app domain is just overkill - all you really need to do is remove references to managed objects that are no longer needed and the Garbage Collector will tidy them up for you.
If you have a true memory leak in unmanaged code an app domain won't help you either. Unmanaged types aren't bounded by app domains and so any unmanaged memory allocated "inside" the app domain won't be freed when the app domain is destroyed. In this case you would be better off using separate processes instead.
I've created a class that allows you to execute code in a separate application domain which would allow you to dispose the application domain and recreate it: Executing Code in a Separate Application Domain Using C#
You can't. A process is isolated and independent and you can't transfer a thread from one process into another process.
What you could do, however, if you absolutely can't fix the memory leaks internally, is create a watchdog program which launches the app whenever it stops running, and set up your app to only be run in single execution mode.
I have an out-of-process COM server that needs to keep an eye on things. This server runs as a service and is, internally, a singleton. For simplicity sake, I will call him BossCom.
I have another out-of-process COM server that is a worker. It is, for system stability, a single-use server (meaning that if you create 2 WorkerCom's, there are 2 WorkerCom.exe's running). For simplicity sake, I will call him WorkerCom.
WorkerCom can be started by anything, even by itself if someone runs him via the command line with the right command line arguments.
The overall goal is for BossCom to know what WorkerComs are around, know what they are doing, and be able to give them orders (pause, stop, ramp up, etc).
My initial thought at this would be that whenever WorkerCom starts, he would CoCreateInstance a BossCom and call BossCom->RegisterWorker(IUnknown me). Then when the WorkerCom is about to shutdown, he would call BossCom->UnregisterWorker(IUnknown me). BossCom could QueryInterface the IUnknown for IWorkerCom and be able to issue commands.
That would work great if all these com objects were in the same process, but they're not. I thought about using the GlobalInterfaceTable, but it is only global in the sense of a single process.
I have spent a few days researching this and am at a loss. Maybe I'm tunnel-visioned.
How can I marshal a reference to a com object from the Worker to the Boss?
Oh, and, for what it's worth, BossCom is written in C# and WorkerCom is written in ATL C++ but I'll take solutions written in VB, Scala, Lisp, or anything. I figure I can translate the core idea. :-)
You should take a look at Monikers, that are used to identify a COM object instance even across different machines.
As per the comments, this does indeed work out of the box. There is one additional detail though that makes it work.
Originally when I was looking at C# interfaces that dealt with copying interfaces, the argument type was IntPtr. Well, an IntPtr is just a long so it transfers the value as is and, as such, doesn't work.
The key is to set the MarshalAs attribute on the argument. So my RegisterWorker method looks like this:
public void RegisterWorker(
[In, MarshalAs(UnmanagedType.IUnknown)] object ptr
)
{
IWorkerCom worker = (IWorkerCom) ptr;
Workers.Add(worker);
}
Utterly amazing.
I had to do something similar recently and found that using shared memory worked very well for me. The BossCom could create and own the shared memory and the workers could register by making an entry in the shared memory. Here is an MSDN link to what I am talking about. Remember to use a mutex to synchronise access to the memory...
You're a bit limited in being able to create marshalable interfaces in C#. No easy way to setup the proxy. No such problem in ATL, declare a callback interface in the IDL. And pass an instance pointer with the RegisterWorker() call. The server should store it until it gets the unregister call. Use that callback interface to generate notifications.
You should also look into the ROT (Running Object Table), it may be a different way to solve this problem.
http://msdn.microsoft.com/en-us/library/windows/desktop/ms695276(v=vs.85).aspx
http://www.codeproject.com/KB/COM/ROTStuff.aspx