Scott Hanselman

The Weekly Source Code 31- Single Instance WinForms and Microsoft.VisualBasic.dll

August 02, 2008 Comment on this post [19] Posted in Learning .NET | Source Code | VB | Windows Client
Sponsored By

I got an interesting question recently emphasis mine:

I am regular reader of you blog. I need some help in single instance winform. I have to open application when a file (.ext) is clicked (File is associated with that application like .doc with WINWORD). Application should be single instance. When I click the .ext file it should open the application with that content. If an instance is runnng it should ask the user whether you want to close this application and then open the new .ext file. Need help in C#.

Some questions are more interesting than others, but I think we've all had to solve this "Single Instance" problem over and over again over the last 15 years. I did this with a Dan Appleman VBX in Visual Basic 3 and I've seen piles of solutions with Mutexes and all sorts of overly complex dancing to solve this apparently simple problem. This is a really old technique, but three years later, there's just not enough people that know that the WindowsFormsApplicationBase class exists and has a lot of useful functionality in it.

There was an interesting thread over here about handling this. Someone asked the question and someone said "WinForms 2.0 has support for single instance apps built in." and the next guy said "Only for Visual Basic applications, though."

Microsoft.VisualBasic.dll has got to be one of the most useful standard installed parts of the .NET Framework out there. Folks are afraid to reference it from C#. It feels wrong.

Kind of like busting out with French words in the middle of English sentences, referencing Microsoft.VisualBasic.dll has that je ne sais quoi that tends to give C# folks a feeling of mal de mer but that assembly has a specific raison d'être. See? Feels wrong, but it still works. There's good stuff in Microsoft.VisualBasic.dll, and just because it isn't System.Something doesn't mean you shouldn't reference it with abandon. Go nuts.

Back to the problem. There's many examples, but the easiest one I've seen was over at OpenWinForms.com and it was written in C# referencing Microsoft.VisualBasic.dll. I've modified it here to make a single instance app that will open a text file name passed in on the command line. If you call the same application a second time, it'll take the new command line argument and load that text file in the first instance.

Launching it as "SuperSingleInstance foo.txt" from a command line...

Single Instance

Then, from the same command line, while the first one runs, launching a second "SuperSingleInstance bar.txt" from a command line. The first instance is reused, brought to the front, and gets an event letting us know someone tried to launch us and that event includes the new command line.

Single Instance (2)

The code is really cool as all the work is in WindowsFormsApplicationBase. It's a little confusing because you have to call a controller instance and tell it about your MainForm, rather than calling Application.Run(). The StartupNextInstance event is called in your first application when a second instance of your app gets fired up. It talks cross process between the new second instance and your original one and passes over the command line.

using System;
using System.Windows.Forms;
using Microsoft.VisualBasic.ApplicationServices;

namespace SuperSingleInstance
{
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
string[] args = Environment.GetCommandLineArgs();
SingleInstanceController controller = new SingleInstanceController();
controller.Run(args);
}
}

public class SingleInstanceController : WindowsFormsApplicationBase
{
public SingleInstanceController()
{
IsSingleInstance = true;

StartupNextInstance += this_StartupNextInstance;
}

void this_StartupNextInstance(object sender, StartupNextInstanceEventArgs e)
{
Form1 form = MainForm as Form1; //My derived form type
form.LoadFile(e.CommandLine[1]);
}

protected override void OnCreateMainForm()
{
MainForm = new Form1();
}
}
}

The Form is trivial, just loading the text from the file into a TextBox.

using System;
using System.Windows.Forms;
using System.IO;

namespace SuperSingleInstance
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}

protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
string[] args = Environment.GetCommandLineArgs();
LoadFile(args[1]);
}
public void LoadFile(string file)
{
textBox1.Text = File.ReadAllText(file);
}
}
}

There's other nice functionality in WindowsFormsApplicationBase like support for SplashScreens and network availability events. Again, check out the good stuff over at http://www.openwinforms.com/, like the Controller I used in this post.

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Hosting By
Hosted in an Azure App Service
August 02, 2008 15:03
Yeah, better to just forget about Microsoft.VisualBasic.ApplicationServices: it seems pretty cool, but has some severe bugs that Microsoft just isn't interested in fixing.

Using the single-instance feature, for example, will make your app crash with a System.Net.SocketException when Windows is running in Safe Mode. Apparently, the cross-process event code uses networking features that are not available in Safe Mode.

The resulting exception isn't handled, and since it occurs before your own Main() routine is even reached, there is nothing you can do about it: your app will just crash with a standard "send fatal error details to Microsoft?" dialog. Instant Windows Logo fail for everyone!

And there's more where that came from, such as unchecked writes to the event log (which will cause a crash if the log is full). Microsoft.VisualBasic.ApplicationServices is a cool idea, but even in .NET 3.5 it's far from ready for prime time...
MdB
August 02, 2008 18:26
Excelent article!
August 03, 2008 2:19
MdB - I guess it depends on whether your app needs to run in safe mode or not - I would guess that most business applications don't really have this requirement. I think this solution still offers a pretty good option, certainly in the absence of another solution.
August 03, 2008 2:22
David and MdB - I tend to agree. While I'm sympathetic, and I think it'd be nice if the feature just disabled by detecting Safe Mode, I personally haven't entered safe mode in YEARS. That said, this is still pretty useful.

MdB - I will bring up your point with the team that wrote it though.
August 03, 2008 3:07
I had no idea that there was a problem with this in safe mode, wonder if this applies to all socket code or if WindowsFormsApplicationBase is doing anything particularly weird?

A quick reboot into safe mode confirms <a href"http://www.flawlesscode.com/post/2008/02/Enforcing-single-instance-with-argument-passing.aspx">my implementation</a> based on named pipes works nicely though! :)
August 03, 2008 3:11
Whoops, sorry, it's late... Another try to save any interested party copy and pasting.
August 03, 2008 3:16
Sean, very nice implementation!
August 03, 2008 7:16
Excelent article!
August 03, 2008 10:18
David and Scott: I agree that Safe Mode is a mostly useless relic (especially since the Windows Installer service doesn't run in it, pretty much defying its purpose -- not sure if that was ever fixed...), but the fact remains that testing in Safe Mode remains a Windows Logo requirement (and thus sort of a best practice for all developers, even if 'only' doing internal apps), and that it's extremely easy for users to invoke this mode.

Having your app crash unconditionally under those circumstances, simply by checking a Microsoft-supplied checkbox ("Use Application Framework" in VB.NET's compile options) is extremely galling, and having your bug reports about it closed as "by design" even more so...

Sean: most socket code will not work in Safe Mode, as the network stack isn't loaded, and even simple things like resolving 'localhost' will fail. Named pipes are a good alternative, and what I use in production apps. I didn't look at your code in detail (it's C#, which hurts my feeble VB.NET brain), but one gotcha here is that using the default NULL security descriptor on the pipe will cause issues on Vista. If one instance of the app is elevated, but the other isn't, they may not be able to talk to each other, depending on the direction of communication. Granting explicit RW rights to, say, BUILTIN\Users will fix that.

--Michiel

MdB
August 03, 2008 15:16
I work with the guy who maintains that site. He will be thrilled to know you referenced him Scott :)

It's a small world..
August 05, 2008 8:52
I am one of those developers who doesn't like to reference Microsoft.VisualBasic in my C# apps because it just feels wrong. But there is a good reason that it feels wrong. Most of Microsoft.VisualBasic exists to support the VB.NET language, specifically legacy features which are only still around to encourage upgrading to VB.NET from older versions (which IMHO should be discouraged not encouraged, but that's another conversation). But WindowsFormsApplicationBase isn't in that category; its a broadly useful class which just happened to get created by the VB team. Which raises the question, why wasn't it included in System.Windows.Forms so that it could easily be used by anyone targeting the .NET framework?
August 05, 2008 20:33
I remember seeing this a couple of years ago and getting the same kind of refusal to use it from some of the developers in the shop... What's in a name(space)?
August 11, 2008 17:06
Sometimes I interrogate Microsoft.VisualBasic.dll with .NET Reflector. If the code is not too complicated, I'll just write it into my own application instead of referencing an entire assembly for just one convenient utility function.
August 14, 2008 9:53
MdB: Just to update you. I've raised this and confirmed that it's logged as a bug and the team knows about it. I'm digging in to find out what their plans are. I'm talking with them on it now.
August 15, 2008 2:04
I have no qualms referencing Microsoft.VisualBasic.dll, it's useful for certain compiler/translator aids to identify Visual Basic types and certain emission information from the Visual Basic language (such as StandardModuleAttribute). Not to mention I used to be a VB programmer (since VB5) but switched to C♯ once the Common Language Infrastructure and C♯ came out.
August 21, 2008 16:04
Similar functionality can be achieved by using a Mutex (link to MSDN help: Mutex Constructor)

Example:


public void Run()
{

//Check to see if Application is already running...
bool isOwned = false;

Mutex appStartMutex = new Mutex(

true,
"[UniqueApplicationName]",
out isOwned

);

if ( !isOwned )
{

string message = "There is already a copy of the application" +
" '[UniqueApplicationName]' running. " +
"Please close that application before starting a new one.";

MessageBox.Show(message);

Environment.Exit( 0 );

}

StartAppProcess();

}


MGC
August 26, 2008 20:51
A very interesting and educational post as ever Scott, thanks!

And coming from VB to C# recently I can definitely vouch for the really nice abilities the assembly has. It's nice to be able to rake them back into my C# code when I miss them, even if it does feel wrong hehe.
xiy
September 03, 2008 0:24
MGC,

Though that code does prevent multiple instances of an app from running, it does not provide the behavior of forwarding the command line parameters to the instance that is currently running and allowing it to deside what todo. If the just click the shortcut twice, then the existing instance can be activated.

This feature is like what Photoshop (or MS Word) does when you double-click on an image when Photoshop is already running. That new image is opened in the existing instance of photoshop.

By providing the parameter forwarding to the existing instance, you provide a better user experience, versus rapping them on the knuckles for inadvertently starting a second instance.
September 25, 2008 2:27
Does anybody know how to do this in .NET Compact Framework. The Mutex method is limited and VisualBasic.ApplicationServices isn't available.
Rob

Comments are closed.

Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.