 |
|
 |
Hi,
I used this code for may application but this class in a DLL file.
When I run my application on Windows Vista, it have error
"Application has generated an exception that could not be handled
Process id=0x694, Thread id=...."
Why??? Pls help me!
==========================
Nothing to change my life
|
|
|
|
 |
|
 |
As it stands, your code works as is. However, if you move the SingleProgramInstance class into a DLL and then have your EXE use the SingleProgramInstance class in the DLL, there is a bug. If you launch an instance of an app which uses SingleProgramInstance, minimize it, then launch another instance, with the (current) following code:
public void RaiseOtherProcess() { Process proc = Process.GetCurrentProcess(); // ... string assemblyName = Assembly.GetExecutingAssembly().GetName().Name; foreach (Process otherProc in Process.GetProcessesByName(assemblyName)) { ... ...the call to Process.GetCurrentProcess() returns back the process of the DLL, not the EXE. My fix is to change RaiseOtherProcess() to: public void RaiseOtherProcess(Process proc, Assembly executingAssembly) { if(proc == null) { throw new ArgumentException("proc"); } if(executingAssembly == null) { throw new ArgumentException("executingAssembly"); } // Using Process.ProcessName does not function properly when // the name exceeds 15 characters. Using the assembly name // takes care of this problem and is more accurate than other // work arounds. string assemblyName = executingAssembly.GetName().Name; foreach (Process otherProc in Process.GetProcessesByName(assemblyName)) { //ignore this process if (proc.Id != otherProc.Id) { // Found a "same named process". // Assume it is the one we want brought to the foreground. // Use the Win32 API to bring it to the foreground. IntPtr hWnd = otherProc.MainWindowHandle; if (IsIconic(hWnd)) { ShowWindowAsync(hWnd,SW_RESTORE); } SetForegroundWindow(hWnd); return; } } }
...and change the code which calls RaiseOtherProcess() to: spi.RaiseOtherProcess(System.Diagnostics.Process.GetCurrentProcess(), System.Reflection.Assembly.GetExecutingAssembly());
Jeff
-- modified at 16:43 Thursday 15th March, 2007
|
|
|
|
 |
|
 |
PS. I really hate how CodeProject removes whitespace. I originally submitted the code with tabs but CP removed all of the tabs. I then went back in and edited the message and changed the tabs to spaces, and CP _STILL_ removed the whitespace.
Darn. Well, imagine the previous, but with proper formating.
Jeff
|
|
|
|
 |
|
 |
Next time use the < pre > or < code > html tag
Also in the for loop there is a bug, the correct line looks like:
foreach (Process otherProc in Process.GetProcessesByName(proc.ProcessName))
|
|
|
|
 |
|
 |
Hi Balazs, thanks for the tip about the formatting - I just changed my previous posting to use the correct formatting. Re. GetProcessesByName(proc.ProcessName) vs. GetProcessesByName(assemblyName), my original code is correct and assemblyName should be used, not proc.ProcessName. When running in the debugger, assemblyName is "MyApp" and proc.ProcessName is "MyApp.vshost" (this is something that VS does), so if you use proc.ProcessName, the procID won't be found and although the new instance of your application won't run, the previous instance's window won't be brought to the front via SetForegroundWindow(). When running normally (and not in the debugger), both proc.ProcessName and assemblyName will be "MyApp". Try it, you'll see. foreach (Process otherProc in Process.GetProcessesByName(assemblyName)) ...is the way to go. Thanks, Jeff
|
|
|
|
 |
|
 |
Thanks! I tried this code with your modification and it works great!
"If you're not sure what option to choose, talk to your system administrator. If you are the systems administrator and you're not sure, select Manual, and consider a career in the food-service industry..."
-Excerpt from RedHat Installation Manual
|
|
|
|
 |
|
 |
You could modify your routine above to the following which will eliminate the need to pass the proc & assembly parameters:
public void RaiseOtherProcess()
{
Process process = Process.GetCurrentProcess();
Assembly assembly = Assembly.GetCallingAssembly();
....
The call should always be within the same process regardless of which assembly it originated from.
The Assembly.GetCallingAssembly will fail if you are chaining the call through another assembly, but I don't know why you'd be doing that so...
JB
|
|
|
|
 |
|
 |
Hi. If you want a reference to the assembly that originated the running process, you should use Assembly.GetEntryAssembly(). This approach should work from any method within any assembly loaded by that process (actually it does inside my Windows applications ). Daniele.
|
|
|
|
 |
|
 |
Just wanted to thank you for this great project.
Im using it in http://fmdc.no-ip.org/
|
|
|
|
 |
|
 |
Hi to everyone.
Can someone help me to implement that peace of code to win. form.
This is new for me, but this is excellent way to learn more complex code writing.
If someone already have simple form with Single Process Instance Object feature implemented I'll be grateful if sent it to me.
Thanks.
Sorry for my poor English.
|
|
|
|
 |
|
 |
I agree with the other respondents - this is a great article! I just have one question/concern. You call Release() from within the finalizer. All documentation that I've ever read instructs us not to reference any managed objects from within the finalizer (http://msdn2.microsoft.com/en-us/library/system.idisposable.aspx,
Eric Gunnerson's C#, etc. etc. If I'm not mistaken, the using forces a call to Dispose() which neatly cleans up, calling Release() and suppressing finalization. It is therefore highly unlikely that the finalizer would ever be called. If it were however, would not this result in a runtime error? Just curious?
Scott
|
|
|
|
 |
|
 |
You're quite correct that this is a violation.
This created a hard to find bug in an application I'm developing.
Here are the suggested changes (which works for me):
...
private bool m_Disposed = false;
...
~SingleProgramInstance()
{
Dispose(false);
}
...
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (this.m_Disposed == false)
{
if (disposing)
this.Release();
this.m_Disposed = true;
}
}
The GC will call finalize with Dispose(false) and under this condition no managed objects are referenced.
If a user calls Dispose() this will translate to Dispose(true) and managed objects can be referenced.
Nice article though. Very useful.
modified on Tuesday, December 04, 2007 9:19:35 AM
|
|
|
|
 |
|
 |
Calling Dispose( false ) means it doesn't actually do anything here. Isn't this just a fancy (complicated) way of making the destructor redundant?
|
|
|
|
 |
|
 |
I've used your suggestions very succesfully, thanks. The only problem is that under W98 I can't show and restore the original application if it is in the tray. Does ShowWindowAsync not work under W98, do you know?
Dave
|
|
|
|
 |
|
 |
Thanks for the excellent job. I had a program finished and the client requested that is it only be openned once. I freaked until I found your article which allowed me to add this feature in less than an hour. This is the kind of article that makes CodeProject so valuable.
Thanks,
Joe
More stuff at www.smileymicros.com
|
|
|
|
 |
|
 |
This is a great article but the method of "raising" the existing process is is a little complicated and doesn't always work (hidden windows, task tray icons and so on). The workarounds suggested here and elsewhere for these limitations are even more complicated and still don't always work.
I have implemented this basic approach in my applications using a registered message to "wake up" the existing instance. The main disadvantage is that you have to add a little code to your main form; the main advantage is that it works (for me anyway).
Here's the result (you may want to take more care in the choice of the string used to register the message - replace "MyApplication_Wakeup":
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
using System.Reflection;
namespace SingleInstance
{
public class SingleProgramInstance : IDisposable
{
static public uint WakeupMessage = NativeMethods.RegisterWindowMessage("MyApplication_Wakeup");
static IntPtr HWND_BROADCAST = (IntPtr)0xffff;
private Mutex _processSync;
private bool _owned = false;
public SingleProgramInstance() : this("")
{
}
public SingleProgramInstance(string identifier)
{
_processSync = new Mutex(true, Assembly.GetExecutingAssembly().GetName().Name + identifier, out _owned);
}
~SingleProgramInstance()
{
Release();
}
public bool IsSingleInstance
{
get { return _owned; }
}
public void RaiseOtherProcess()
{
NativeMethods.PostMessage(HWND_BROADCAST, WakeupMessage, IntPtr.Zero, IntPtr.Zero);
}
private void Release()
{
if (_owned)
{
_processSync.ReleaseMutex();
_owned = false;
}
}
#region Implementation of IDisposable
public void Dispose()
{
Release();
GC.SuppressFinalize(this);
}
#endregion
}
class NativeMethods
{
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern uint RegisterWindowMessage(string lpString);
[DllImport("user32.dll", SetLastError = true)]
public static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
}
}
The code needed in the main form class is something like this:
protected override void WndProc(ref Message m)
{
if ((uint)m.Msg == SingleInstance.SingleProgramInstance.WakeupMessage)
{
if (WindowState == FormWindowState.Minimized)
{
Visible = true;
WindowState = FormWindowState.Normal;
}
this.Activate();
}
base.WndProc(ref m);
}
Regards,
Phil
The opinions expressed in this communication do not necessarily represent those of the author (especially if you find them impolite, discourteous or inflammatory).
|
|
|
|
 |
|
 |
I couldn't get it to work with PostMessage, the currently running process wasn't recieving the message. However using SendMessage instead solved the problem, here are the changes:
[DllImport("user32.dll")]
public static extern int SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam );
NativeMethods.SendMessage(HWND_BROADCAST, WakeupMessage, IntPtr.Zero, IntPtr.Zero);
|
|
|
|
 |
|
 |
PostMessage works for me and is to be preferred. If you use SendMessage and some app in the system fails to process the message then your app is hung. You should do everything you can to avoid making other people's bugs look like yours!
The receiving app needs to have a top-level window to process the message; that means (I think) its ShowInTaskbar property needs to be true. It is, however, permissible for the window to be invisible or disabled.
The opinions expressed in this communication do not necessarily represent those of the author (especially if you find them impolite, discourteous or inflammatory).
|
|
|
|
 |
|
 |
well ... i seem to be a bit more unfortunate ... as none of them is working for me?
The difference between my application and others where it works could possible be:
I dont have my application entry in the taskbar, though it works while the first instance Form is behind other windows.
It doesn't work only when the Form is hidden.
Please let me know if someone has faced the similar situation and the resolution.
Thanks ........... Saud
|
|
|
|
 |
|
 |
Nice work Michael. Cheers!
|
|
|
|
 |
|
 |
Nice class Michael Thank you for sharing - you've got my five
David Roh
|
|
|
|
 |
|
 |
I converted all the code to VB and was wondering if anyone knows why it might not work with VB. The Mutex object got created with each new application that was run. I've solved the problem by just searching by process name, but would prefer the Mutex method... everything else works fine. The window raises properly and everything.
|
|
|
|
 |
|
 |
thanks for your article, my application is on the tray, i would like to pop it up when user tries to open another instance , do you have an idea what to use.
|
|
|
|
 |
|
 |
Well first you'll want to see this bit of code, which is where you would stick your code:
if (IsIconic(hWnd))
{
ShowWindowAsync(hWnd,SW_RESTORE);
}
SetForegroundWindow(hWnd);
Now right before that we add:
if (!IsWindowVisible(hWnd)) {
ShowWindowAsync(hWnd,SW_SHOW);
}
That should work. You might also need to add some other stuff in there if you need to change the tray icon or some variables. My own tray icon stuff would only need to use that, though.
You'll need to declare the Win32 IsWindowVisible function as well, search msdn.microsoft.com to find the prototype.
If this doesn't work and hWnd is 0, try checkinng that thread below about the hidden main window.
|
|
|
|
 |
|
 |
For me, while the Window/Form is hidden, the hWnd is always "0" ... hence using SendMessage/PostMessage or any other method that needs hWnd will not work.
Any clues?
|
|
|
|
 |