Reputation: 55
the .net Windows Form application we developed (vb.net but c# answer is ok too) has some APIs (edit: yes, our own) to allow users to automate some tasks. Everything is fine when the application is started through APIs by, say, Visual Studio. What we cannot get to work though is to assign an already running instance of our application to a new application object in visual studio. We have seen there are some methods available for COM objects (getojbect) to access a running instance of an application but how about .net applications?
Rephrasing the question, we would like that, when a user calls the New() constructor of our application, the new object points to the running instance of our application (if any) instead of trying to create a new one (which is not possible by the way because we have made it single instance by checking through Mutex that no other instance of our application is running).
EDIT: Sample code in the user application to automate some tasks
Imports TheApplication
Public Class WinFormByUser
Private ApplicationObject As TheApplication.MainForm
Public Sub OpenTheApplication()
ApplicationObject = New TheApplication.MainForm
Rem here theapplication should create a new instance if no instance of TheApplication is running. BUT, if an instance of the application
Rem is already running (in a different process, maybe started directly from the user), the ApplicationObject should point to the running
Rem instance from now on, instead of trying to create a new instance
ApplicationObject.DoSomething()
End Sub
End Class
Sample code inside TheApplication
Imports System.Threading
Public Class MainForm
Private ApplicationOpenedThroughAPI As Boolean = False
Private Shared mtx As Mutex
Private firstInstance As Boolean = False
Dim AppName As String = "TheApplicationName"
Public Sub New()
If Application.ProductName.ToString() <> AppName Then
Rem if TheApplication is opened externally through API the name is different therefore we can determine the boolean value
ApplicationOpenedThroughAPI = True
End If
mtx = New Mutex(True, AppName, firstInstance)
If firstInstance Then
InitializeComponent()
DoAllTheNecessaryStuff()
Else
If ApplicationOpenedThroughAPI = False Then
MsgBox("Application is running, can't open second instance")
Else
ReturnTheRunningInstance()
End If
End If
End Sub
Private Sub ReturnTheRunningInstance()
Rem please help here. what to do?
End Sub
Public Sub DoSomething()
Rem this does something and can be called by API user
End Sub
End Class
Please note that the solution could either be adding some code inside the application in the Sub ReturnTheRunningInstance() or in the user code, maybe checking if the application is running through something like Process.GetProcessesByName("TheApplicationName").Length and then do something in case.
Thanks!
Upvotes: 3
Views: 1814
Reputation: 54387
We have seen there are some methods available for COM objects (getojbect) to access a running instance of an application but how about .net applications?
Let's start with this part. You essentially need to have one process access another process. .Net provides a variety of forms of cross-process communication. WCF seems the most appropriate here.
WCF is a large subject, but here's a basic architecture that might accomplish your goals.
Have your application host a service, available to local callers over TCP.
Consider this pseudocode; there is plenty of documentation available on WCF once you know what to search for.
// the contract
[ServiceContract]
public interface IMyService
{
[OperationContract]
int Foo( int bar );
}
// the implementation
public MyService : IMyService
{
public int Foo( int bar ){ return bar * 100; }
}
// hosting the service within your application
var baseUri = new Uri( "net.tcp://localhost:59999/" );
var serviceHost = new ServiceHost( typeof( MyService ), baseUri );
// many options can/should be set here, e.g. throttling, security, and serialization behavior
var binding = new NetTcpBinding();
var endpoint = serviceHost.AddServiceEndpoint( typeof( IMyService ), binding, baseUri );
This is all you need for a caller to interface with an existing instance of the application, but it doesn't address the need to ensure that the app is running.
A wrapper class may make it easier to locate/launch your application.
public sealed class MyWrapper
{
public IMyService GetService()
{
// TODO: perform appropriate OS-wide locking here
// TODO: see if app is running
// TODO: if not, launch it in a new process
// create a channel to connect the WCF endpoint we just defined
var channel = GetChannel();
// TODO: release lock
// return the channel to the caller
return channel;
}
public GetChannel( Binding binding, EndpointAddress endpointAddress )
{
var channelFactory = new ChannelFactory<IMyService>( binding, endpointAddress );
return _channelFactory.CreateChannel();
}
}
Your callers can connect to your application from anywhere on the machine (or beyond, if you wish):
var wrapper = new Wrapper();
var service = wrapper.GetService();
int result = service.Foo( 123 );
While a bit unusual, your service code could also manipulate the GUI. For example:
var wrapper = new Wrapper();
var service = wrapper.GetService();
// call a method, the implementation of which launches a "contact form"
// with data preloaded for the specified contact ID
service.ShowContactForm( 1 );
Note that this syntax I've shown so far is elegant, but it doesn't handle closing the channel or channel factory. There are a variety of ways to do this; I've used a pattern like this:
public sealed class ServiceClient
{
private readonly ChannelFactory<IMyService> _channelFactory;
public ServiceClient( Binding binding, EndpointAddress endpointAddress )
{
_channelFactory = new ChannelFactory<IMyService>( binding, endpointAddress );
Channel = _channelFactory.CreateChannel();
}
public IMyService Channel { get; private set; }
public void Dispose()
{
if( Channel != null )
{
// TODO: check the state of the channel and close/abort appropriately
}
if( _channelFactory != null )
{
_channelFactory.Close();
}
}
}
public sealed class MyWrapper
{
public ServiceClient GetClient()
{
// Similar setup to the previous example, except the service client wraps
// the channel factory.
}
}
var wrapper = new Wrapper();
using( var client = wrapper.GetClient() )
{
client.Channel.Foo( 123 );
}
It's a bit more verbose, but it gives you much more control over cleanup and any other options you wish to control.
All of this code can potentially live in one assembly. However, it may be cleaner to place the wrapper in a separate assembly and the service contract(s) interfaces into another assembly referenced by the wrapper and the main application.
Upvotes: 3