Creating second-level AppDomain hangs - c#

I have two assemblies with Main methods.
The first one executes the other.
The second assembly creates an object in a new AppDomain.
That object is about to create a file or print something on the screen.
The first assembly (simplified example):
class MainClass
{
public static void Main(string[] args)
{
var domain = AppDomain.CreateDomain("server");
new Task(() => domain.ExecuteAssembly(PathToSecondAssembly)).Start();
new ManualResetEvent(false).WaitOne(); //wait forever
}
}
The second one:
class MainClass
{
public static void Main(string[] args)
{
var domain = AppDomain.CreateDomain("name");
Console.WriteLine ("A");
domain.CreateInstance(Assembly.GetExecutingAssembly().FullName,
typeof(Run).FullName, false, 0, null,
new object[0], null, new object[0]);
Console.WriteLine ("B");
new ManualResetEvent(false).WaitOne();
}
}
class Run
{
public Run()
{
File.Create("something");
Console.WriteLine ("C");
}
}
The result varies depending on the program I execute.
If I run the second Main, I get:
A
C
B
and the file is created. I treat it as a proof, that the second application works.
When I run the first Main, I get only:
A
and the file does not appear. The application does not crash, but it hangs.
I have verified it both on .NET 4 and Mono 2.10.9 and 3.0.3 (~git head).
Why is that? How could I override this problem?
==EDIT== (tested on .NET)
I'm getting more and more confused.
I have a problem with PathToSecondAssembly. When the binaries are in the save folder everything seems to be perfectly fine (tested on .NET 4.0, but I assume mono as well).
When I use a relative path or a path to a different directory the results are as follows:
in Debug in Visual Studio 2010 I get FileNotFoundException, but A is shown.
Message:
Could not load file or assembly 'test2, Version=1.0.0.0,
Culture=neutral, PublicKeyToken=null' or one of its dependencies.
The exception comes up on the line in first assembly:
new Task(() => domain.ExecuteAssembly(PathToSecondAssembly)).Start();
when ran without debug, only A is printed, but then nothing happens.
So the assembly is found (A is printed), but somehow the exception comes up.
I have truly no idea why. Some kind of permissions issue? How to overcome this situation?

I had a similar problem once (I was not doing the same things, but the failure in creating a 3rd domain from the 2nd domain sprung the memories in my head and then the FileNotFoundException kind of confirmed "my fears").
It's nothing a good'ol Evidence can't fix.
As MSDN states: http://msdn.microsoft.com/en-us/library/system.security.policy.evidence.aspx
Evidence
Defines the set of information that constitutes input to security policy decisions. This class cannot be inherited.
So just do this:
class MainClass
{
public static void Main(string[] args)
{
var currentEvidence = AppDomain.CurrentDomain.Evidence;
var domain = AppDomain.CreateDomain("server", securityInfo: currentEvidence);
new Task(() => domain.ExecuteAssembly(PathToSecondAssembly)).Start();
new ManualResetEvent(false).WaitOne(); //wait forever
}
}
And if you plan on starting up a 4th domain (and so on), keep on passing the evidence from one "truster" to another "trustee":
class MainClass
{
public static void Main(string[] args)
{
var evAgain = AppDomain.CurrentDomain.Evidence;
var domain = AppDomain.CreateDomain("name", securityInfo: evAgain);
Console.WriteLine ("A");
domain.CreateInstance(Assembly.GetExecutingAssembly().FullName,
typeof(Run).FullName, false, 0, null,
new object[0], null, new object[0]);
Console.WriteLine ("B");
new ManualResetEvent(false).WaitOne();
}
}
Hope that was the issue.

Related

Get the ClickOnce query parameters in the currently running instance of the application

I have a WPF app which uses ClickOnce deployment. It has some query parameters when launched, which we get through ApplicationDeployment.CurrentDeployment.ActivationUri. This method worked just fine, but now the app is single instance. We handle the "single instance" by implementing WindowsFormsApplicationBase from Microsoft.VisualBasic dll.
So when a new instance of an app is launched I should get the query parameters and and handle them in the already running app. My problem is that I can't get the query params when launching app using click once.
I'm trying to get the parameter by overriding the OnStartupNextInstance of the WindowsFormsApplicationBase class, like this:
protected override void OnStartupNextInstance(StartupNextInstanceEventArgs eventArgs)
{
var queryString = eventArgs.CommandLine;
//handle queryStringParams
}
eventArgs.CommandLine has 0 elements when launched using ClickOnce, but it does have elements when I launch my app through command line, like this:
myApp.exe "param1=someValue&param2=someOtherValue"
Also if I try to get them through ApplicationDeployment.CurrentDeployment.ActivationUri it gives me the params of the currently running app (obviously).
I found a solution to my problem, although not so straight forward as i expected. As I already explained in my question, the only place the already running app can catch the cmd params of a new launched instance is the override of the OnStartupNextInstance(StartupNextInstanceEventArgs eventArgs). Command parameters will be available in the eventArgs argument, but not the parameters the new instance received from the ClickOnce launch link.
The solution i used:
I left the OnStartupNextInstance(StartupNextInstanceEventArgs eventArgs), which is in a separate class called SingleInstanceManager which inherits from WindowsFormsApplicationBase intact, (after I little while i understood that i had no chance of getting ClickOnce parameters here).
Instead I modified the void static Main(string[] args) method of the App.xaml.cs. Main modifications:
I got the ActivationUri through
ApplicationDeployment.CurrentDeployment.ActivationUri;
Made a string out of the Query propery of the ActivationUri;
Added that string to the args parameter;
Passed that parameter to the SingleInstanceManager class described
above.
The entire solution finally looks like this:
[STAThread]
public static void Main(string[] args)
{
AppDomain currentDomain = AppDomain.CurrentDomain;
//Creating SingleInstaceManager
SingleInstanceManager manager = new SingleInstanceManager();
//Uncommend the following if statement if you want to debug App's Main method
//if (!System.Diagnostics.Debugger.IsAttached)
//{
// System.Diagnostics.Debugger.Launch();
//}
try
{
if (ApplicationDeployment.CurrentDeployment.ActivationUri != null)
{
var query = ApplicationDeployment.CurrentDeployment.ActivationUri?.Query;
var cmdParams = string.IsNullOrWhiteSpace(query)
? new NameValueCollection(0)
: HttpUtility.ParseQueryString(query);
var updatedArgs = args.ToList();
updatedArgs.Add(cmdParams.ToString());
args = updatedArgs.ToArray();
}
}
//Empty catch to ignore the annoying InvalidDeploymentException while debuging;
catch (InvalidDeploymentException)
{ }
//Passing parameters to the SingleInstaceManager
manager.Run(args);
}
Note:
This solution is not WPF specific only, and it should work for any kind of desktop app, as long as it has an entry point in which you can get the ActivationUri. Hope this will help someone.

Attaching a debugger to code running in another app domain programmatically

I am working on a Visual Studio extension and one of it's functions creates a new app domain and load an assembly into that app domain. Then it runs some functions in the app domain. What I'd like to do, and am not sure if it's possible, is have my extension attach a debugger to the code running in the new app domain so when that code fails, I can actually see what's going on. Right now I'm flying blind and debugging the dynamical loaded assembly is a pain.
So I have a class that creates my app domain something like this:
domain = AppDomain.CreateDomain("Test_AppDomain",
AppDomain.CurrentDomain.Evidence,
AppDomain.CurrentDomain.SetupInformation);
And then creates an object like this:
myCollection = domain.CreateInstanceAndUnwrap(
typeof(MyCollection).Assembly.FullName,
typeof(MyCollection).FullName,
false,
BindingFlags.Default,
null,
new object[] { assemblyPath }, null, null);
MyCollection does something like this in it's constructor:
_assembly = Assembly.LoadFrom(assemblyPath);
So now that assembly has been loaded into Test_AppDomain since the MyCollection object was created in that domain. And it's that loaded assembly that I need to be able to attach the debugger to.
At some point myCollection creates an instance of an object and hooks up some events:
currentObject = Activator.CreateInstance(objectType) as IObjectBase;
proxy.RunRequested += (o, e) => { currentObject?.Run(); };
And basically where I have the handler for RunRequested and it runs currentObject?.Run(), I want to have a debugger attached, although it probably wouldn't be a problem (and may actually work better) if the debugger was attached earlier.
So is there a way to achieve this? Is it possible to programmatically attach a debugger when the user triggers the event that will lead to the Run function of the object created in the new AppDomain being called? How do I get the debugger attached to that (and not the extension itself)?
I tried something like this:
var processes = dte.Debugger.LocalProcesses.Cast<EnvDTE.Process>();
var currentProcess = System.Diagnostics.Process.GetCurrentProcess().Id;
var process = processes.FirstOrDefault(p => p.ProcessID == currentProcess);
process?.Attach();
But it seems the id from System.Diagnostics.Process.GetCurrentProcess().Id doesn't exist within LocalProcesses?
Even though you likely have already moved on, I found the problem very fascinating (and related to what I've been researching to blog about). So I gave it a shot as an experiment - I wasn't sure how you intended to trigger the Run() method with events (and even if it was material for your use case) so I opted for a simple method call.
Injecting Debugger.Launch()
as a PoC I ended up IL-emitting a derived class and injecting a debugger launch call before passing it onto dynamically loaded method:
public static object CreateWrapper(Type ServiceType, MethodInfo baseMethod)
{
var asmBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName($"newAssembly_{Guid.NewGuid()}"), AssemblyBuilderAccess.Run);
var module = asmBuilder.DefineDynamicModule($"DynamicAssembly_{Guid.NewGuid()}");
var typeBuilder = module.DefineType($"DynamicType_{Guid.NewGuid()}", TypeAttributes.Public, ServiceType);
var methodBuilder = typeBuilder.DefineMethod("Run", MethodAttributes.Public | MethodAttributes.NewSlot);
var ilGenerator = methodBuilder.GetILGenerator();
ilGenerator.EmitCall(OpCodes.Call, typeof(Debugger).GetMethod("Launch", BindingFlags.Static | BindingFlags.Public), null);
ilGenerator.Emit(OpCodes.Pop);
ilGenerator.Emit(OpCodes.Ldarg_0);
ilGenerator.EmitCall(OpCodes.Call, baseMethod, null);
ilGenerator.Emit(OpCodes.Ret);
/*
* the generated method would be roughly equivalent to:
* new void Run()
* {
* Debugger.Launch();
* base.Run();
* }
*/
var wrapperType = typeBuilder.CreateType();
return Activator.CreateInstance(wrapperType);
}
Triggering the method
Creating a wrapper for loaded method seems to be as easy as defining a dynamic type and picking a correct method from target class:
var wrappedInstance = DebuggerWrapperGenerator.CreateWrapper(ServiceType, ServiceType.GetMethod("Run"));
wrappedInstance.GetType().GetMethod("Run")?.Invoke(wrappedInstance, null);
Moving on to AppDomain
The above bits of code don't seem to care much for where the code will run, but when experimenting I discovered that I'm able to ensure the code is in correct AppDomain by either leveraging .DoCallBack() or making sure that my Launcher helper is created with .CreateInstanceAndUnwrap():
public class Program
{
const string PathToDll = #"..\..\..\ClassLibrary1\bin\Debug\ClassLibrary1.dll";
static void Main(string[] args)
{
var appDomain = AppDomain.CreateDomain("AppDomainInMain", AppDomain.CurrentDomain.Evidence, AppDomain.CurrentDomain.SetupInformation);
appDomain.DoCallBack(() =>
{
var launcher = new Launcher(PathToDll);
launcher.Run();
});
}
}
public class Program
{
const string PathToDll = #"..\..\..\ClassLibrary1\bin\Debug\ClassLibrary1.dll";
static void Main(string[] args)
{
Launcher.RunInNewAppDomain(PathToDll);
}
}
public class Launcher : MarshalByRefObject
{
private Type ServiceType { get; }
public Launcher(string pathToDll)
{
var assembly = Assembly.LoadFrom(pathToDll);
ServiceType = assembly.GetTypes().SingleOrDefault(t => t.Name == "Class1");
}
public void Run()
{
var wrappedInstance = DebuggerWrapperGenerator.CreateWrapper(ServiceType, ServiceType.GetMethod("Run"));
wrappedInstance.GetType().GetMethod("Run")?.Invoke(wrappedInstance, null);
}
public static void RunInNewAppDomain(string pathToDll)
{
var appDomain = AppDomain.CreateDomain("AppDomainInLauncher", AppDomain.CurrentDomain.Evidence, AppDomain.CurrentDomain.SetupInformation);
var launcher = appDomain.CreateInstanceAndUnwrap(typeof(Launcher).Assembly.FullName, typeof(Launcher).FullName, false, BindingFlags.Public|BindingFlags.Instance,
null, new object[] { pathToDll }, CultureInfo.CurrentCulture, null);
(launcher as Launcher)?.Run();
}
}
One way to get around this is to generate another assembly with a function that will take in a MethodInfo object and simply call System.Diagnostics.Debugger.Launch() and then the given MethodInfo, and then all you have to do is unwrap that assembly's function call it with whatever Method's Info you want to start the actual domain in, and your good it will enable the debugger and then call the method you want it to start in.

Cannot evaluate expression because a native frame is on top of the call stack

I'm creating a simple window service and when I go to debug I get the error, "Cannot evaluate expression because a native frame is on top of the call stack.". Also, when I build the service in Release and run it just hangs.
static void Main()
{
ServiceBase[] ServicesToRun;
ServicesToRun = new ServiceBase[] { new MyService1() };
ServiceBase.Run(ServicesToRun);
}
Thats all that is in the Program.cs file, where it normally gets hung on the ServiceBase.Run(ServicesToRun) line.
Everything I've been able to find only relates to the expression not being evaluated because the code is optimized or having to deal with asp.net and response.redirect.
Code for the Service.
public TruckRateClearService()
{
InitializeComponent();
}
protected override void OnStart(string[] args)
{
tmrProcess.Enabled = true;
}
protected override void OnCustomCommand(int command)
{
base.OnCustomCommand(command);
if (command == 129)
{
OnStart(null);
}
}
protected override void OnStop()
{
tmrProcess.Enabled = false;
}
private void tmrProcess_Tick(object sender, EventArgs e)
{
tmrProcess.Enabled = false;
try
{
eventLog.WriteEntry("Clearing Truck Rates Start" + DateTime.Now.ToString());
TruckRateClearingAgent.Process();
eventLog.WriteEntry("Clearing Truck Rates Finished" + DateTime.Now.ToString());
}
catch (Exception ex)
{
eventLog.WriteEntry(ex.ToString(), EventLogEntryType.Error);
}
tmrProcess.Enabled = true;
}
internal void Run()
{
tmrProcess_Tick(tmrProcess, null);
}
The Internal Void Run() was added just recent on the suggestion in the comments by Eren Ersönmez. His idea has been very helpful for helping debug my logic until I can figure the rest out.
I was able to get the into the Native call stack and it sits on one location, 76F17094 ret. Now I have no idea what this is but maybe someone else will.
Also, when I start the service and look into attaching it to VS I'm noticing two instances of it. One is the normal .exe and another is a .vshost.exe. When I start other services I only see the .exe file in the Attach to process part of the debugger. Could this be because one is on the v4 Framework ( .vshost .exe service ) and another on the v2 ( single .exe service ) Framework?
I believe I got it working. It seems that the problem lied with the timer I was using. The original timer I was using was a System.Windows.Forms timer. I switched it to System.Timers.Timers and everything started working again. Still cant attach VS to it but I can debug it still by using the Internal Run() method. Thanks for all the help n.n
Your main problem is that you're trying to directly run a windows service exe. Windows services can only be started via Service Control Manager (SCM). In order to be able to debug in VS, I'd recommend something like this:
static void Main()
{
if (Environment.UserInteractive)
{
new MyService1().Run();
Thread.Sleep(Timeout.Infinite);
}
else
{
ServiceBase.Run(new ServiceBase[] { new MyService1() });
}
}
You'd create a MyService1.Run method which spawns a new thread that runs the service loop. Also, you'd call the same Run method from within the MyService1.Onstart.
This scheme runs it as a service when being started by SCM, but treats it like a normal exe when being debugged in VS (or being run directly as an exe outside VS).
The problem
This notification means that the thread is currently executing unmanaged code, and therefore cannot be used to evaluate the expression.
In some situations, you could wait for the call to return to managed code before evaluating the expression. Unfortunately, in this situation, that won't happen until you shut down the service.
An Alternative
You might consider overriding the ServiceBase.OnCustomCommand method and putting a breakpoint there so you can evaluate your expression.
protected override void OnCustomCommand(int command)
{
//Debugger.Break() <- or just put a breakpoint in here.
}
You can invoke the custom command as follows:
c:\>sc control YourServiceName 129
The exception you're seeing means that unmanaged code is throwing an exception, so the .NET debugger can't show you the usual useful details.
What are you doing in MyService1() ? Can you post the code inside it?
Also are you trying to debug the service by just starting it from the environment. That might not work.
I usually write something like this:
static void Main(params string[] args)
{
if (args.Length > 0 && args[0] == "/console")
{
// Run whatever your service calls here
}
else
{
ServiceBase[] ServicesToRun;
ServicesToRun = new ServiceBase[] { new MyService1() };
ServiceBase.Run(ServicesToRun);
}
}
Then in the project properties under the Debug tab enter /console as the command line arguments. You should be able to step into the application and debug it. You can only debug a service by installing it first: http://msdn.microsoft.com/en-us/library/7a50syb3(v=vs.80).aspx

Multiple InfoPath interop automation instances

I am trying to automate multiple parallel instances of Office InfoPath 2010 via a windows service. I understand automating Office from a service is not supported however it is a requirement of my customer.
I can automate other Office applications in a parallel fashion, however InfoPath behaves differently.
What I have found is that there will only ever be one instance of the INFOPATH.EXE process created, no matter how many parallel calls to CreateObject("InfoPath.Application") are made. In contrast to this, multiple instances of WINWORD.EXE can be created via the similar mechanism CreateObject("Word.Application")
To reproduce this issue, a simple console application can be used.
static void Main(string[] args) {
// Create two instances of word in parallel
ThreadPool.QueueUserWorkItem(Word1);
ThreadPool.QueueUserWorkItem(Word2);
System.Threading.Thread.Sleep(5000);
// Attempt to create two instances of infopath in parallel
ThreadPool.QueueUserWorkItem(InfoPath1);
ThreadPool.QueueUserWorkItem(InfoPath2);
}
static void Word1(object context) {
OfficeInterop.WordTest word = new OfficeInterop.WordTest();
word.Test();
}
static void Word2(object context) {
OfficeInterop.WordTest word = new OfficeInterop.WordTest();
word.Test();
}
static void InfoPath1(object context) {
OfficeInterop.InfoPathTest infoPath = new OfficeInterop.InfoPathTest();
infoPath.Test();
}
static void InfoPath2(object context) {
OfficeInterop.InfoPathTest infoPath = new OfficeInterop.InfoPathTest();
infoPath.Test();
}
The InfoPathTest and WordTest classes (VB) are in another project.
Public Class InfoPathTest
Public Sub Test()
Dim ip As Microsoft.Office.Interop.InfoPath.Application
ip = CreateObject("InfoPath.Application")
System.Threading.Thread.Sleep(5000)
ip.Quit(False)
End Sub
End Class
Public Class WordTest
Public Sub Test()
Dim app As Microsoft.Office.Interop.Word.Application
app = CreateObject("Word.Application")
System.Threading.Thread.Sleep(5000)
app.Quit(False)
End Sub
End Class
The interop classes simply create the automation objects, sleep and then quit (although in the case of Word, I have completed more complex tests).
When running the console app, I can see (via Task Manager) two WINWORD.EXE processes created in parallel, and only a single INFOPATH.EXE process created. In fact when the first instance of InfoPathTest calls ip.Quit, the INFOPATH.EXE process terminates. When the second instance of InfoPathTest calls ip.Quit, a DCOM timeout exception is thrown - it appears as though the two instances were sharing the same underlying automation object, and that object no longer exists after the first call to ip.Quit.
At this stage my thoughts were only a single INFOPATH.EXE is supported per user login. I expanded the windows service to start two new processes (a console application called InfoPathTest), each running under a different user account. These new processes would then attempt to automate INFOPATH.EXE
Here's where it gets interesting, this actually works, but only on some machines, and I cannot figure out why that is the case.
And the service code (with help from AsproLock):
public partial class InfoPathService : ServiceBase {
private Thread _mainThread;
private bool isStopping = false;
public InfoPathService() {
InitializeComponent();
}
protected override void OnStart(string[] args) {
if (_mainThread == null || _mainThread.IsAlive == false) {
_mainThread = new Thread(ProcessController);
_mainThread.Start();
}
}
protected override void OnStop() {
isStopping = true;
}
public void ProcessController() {
while (isStopping == false) {
try {
IntPtr hWinSta = GetProcessWindowStation();
WindowStationSecurity ws = new WindowStationSecurity(hWinSta, System.Security.AccessControl.AccessControlSections.Access);
ws.AddAccessRule(new WindowStationAccessRule("user1", WindowStationRights.AllAccess, System.Security.AccessControl.AccessControlType.Allow));
ws.AddAccessRule(new WindowStationAccessRule("user2", WindowStationRights.AllAccess, System.Security.AccessControl.AccessControlType.Allow));
ws.AcceptChanges();
IntPtr hDesk = GetThreadDesktop(GetCurrentThreadId());
DesktopSecurity ds = new DesktopSecurity(hDesk, System.Security.AccessControl.AccessControlSections.Access);
ds.AddAccessRule(new DesktopAccessRule("user1", DesktopRights.AllAccess, System.Security.AccessControl.AccessControlType.Allow));
ds.AddAccessRule(new DesktopAccessRule("user2", DesktopRights.AllAccess, System.Security.AccessControl.AccessControlType.Allow));
ds.AcceptChanges();
ThreadPool.QueueUserWorkItem(Process1);
ThreadPool.QueueUserWorkItem(Process2);
} catch (Exception ex) {
System.Diagnostics.Debug.WriteLine(String.Format("{0}: Process Controller Error {1}", System.Threading.Thread.CurrentThread.ManagedThreadId, ex.Message));
}
Thread.Sleep(15000);
}
}
private static void Process1(object context) {
SecureString pwd2;
Process process2 = new Process();
process2.StartInfo.FileName = #"c:\debug\InfoPathTest.exe";
process2.StartInfo.UseShellExecute = false;
process2.StartInfo.LoadUserProfile = true;
process2.StartInfo.WorkingDirectory = #"C:\debug\";
process2.StartInfo.Domain = "DEV01";
pwd2 = new SecureString(); foreach (char c in "password") { pwd2.AppendChar(c); };
process2.StartInfo.Password = pwd2;
process2.StartInfo.UserName = "user1";
process2.Start();
process2.WaitForExit();
}
private static void Process2(object context) {
SecureString pwd2;
Process process2 = new Process();
process2.StartInfo.FileName = #"c:\debug\InfoPathTest.exe";
process2.StartInfo.UseShellExecute = false;
process2.StartInfo.LoadUserProfile = true;
process2.StartInfo.WorkingDirectory = #"C:\debug\";
process2.StartInfo.Domain = "DEV01";
pwd2 = new SecureString(); foreach (char c in "password") { pwd2.AppendChar(c); };
process2.StartInfo.Password = pwd2;
process2.StartInfo.UserName = "user2";
process2.Start();
process2.WaitForExit();
}
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr GetProcessWindowStation();
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr GetThreadDesktop(int dwThreadId);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern int GetCurrentThreadId();
}
The InfoPathTest.exe process simply calls the InfoPathTest.Test() method detailed above.
In summary, this works, but only on certain machines. When it fails, the second INFOPATH.EXE process is actually created, but immediately quits with an exitcode of 0. There is nothing in the event logs, nor any exceptions in the code.
I've looked at many things to try and differentiate between working / non working machines, but I'm now stuck.
Any pointers appreciated, especially if you have other thoughts on how to automate multiple InfoPath instances in parallel.
I'm guessing you'd get similar behavior if you tried to do the same thing with Outlook, which would mean Microsoft thinks it is a bad idea to run multiple copies.
If that is so, I see two options.
Option one is to make your Infopath automation synchronous, running one instance at a time.
Option two, and I have NO idea if it would even work, would be to see if you can launch virtual machines to accomplish youe InfoPath work.
I hope this can at least spark some new train of though that will lead to success.
I’ve encountered a very similar issue with Outlook. The restriction of allowing only a single instance of the application to be running does not apply per user, but per interactive login session. You may read more about it in Investigating Outlook's Single-Instance Restriction:
Outlook was determining whether or not another instance was already running in the interactive login session. […] During Outlook's initialization, it checks to see if a window named "Microsoft Outlook" with class name "mspim_wnd32" exists, and if so, it assumes that another instance is already running.
There are ways of hacking around it – there is a tool for launching multiple Outlook instances on the Hammer of God site (scroll down) – but they will probably involve intercepting Win32 calls.
As for your code only working on certain machines: That’s probably due to a race condition. If both processes manage to start up fast enough simultaneously, then they won’t detect each other’s window, and assume that they’re the only instance running. However, if the machine is slow, one process would open its window before the other, thereby causing the second process to detect the first process’s window and shut itself down. To reproduce, try introducing a delay of several seconds between launching the first process and the second – this way, only the first process should ever succeed.

The uncatchable exception, pt 2

Update: I've filed a bug report on Microsoft Connect: https://connect.microsoft.com/VisualStudio/feedback/details/568271/debugger-halting-on-exception-thrown-inside-methodinfo-invoke#details
If you can reproduce this problem on your machine, please upvote the bug so it can be fixed!
Ok I've done some testing and I've reduced the problem to something very simple:
i. Create a method in a new class that throws an exception:
public class Class1 {
public void CallMe() {
string blah = null;
blah.ToLower();
}
}
ii. Create a MethodInfo that points to this method somewhere else:
Type class1 = typeof( Class1 );
Class1 obj = new Class1();
MethodInfo method = class1.GetMethod( "CallMe" );
iii. Wrap a call to Invoke() in a try/catch block:
try {
method.Invoke( obj, null ); // exception is not being caught!
} catch {
}
iv. Run the program without the debugger (works fine).
v. Now run the program with the debugger. The debugger will halt the program when the exception occurs, even though it's wrapped in a catch handler that tries to ignore it. (Even if you put a breakpoint in the catch block it will halt before it reaches it!)
In fact, the exception is happening when you run it without the debugger too. In a simple test project it's getting ignored at some other level, but if your app has any kind of global exception handling, it will get triggered there as well. [see comments]
This is causing me a real headache because it keeps triggering my app's crash-handler, not to mention the pain it is to attempt to debug.
I can reproduce this on my .NET 4 box, and you're right -- it only happens on .NET 4.0.
This smells very much like a bug to me, and should go on MS Connect. Major bummer if this is tripping your crash handler. Sounds like a non-pleasing way to work around this is to wrap the invoked method inside its own handler. :-(
One thing I can not reproduce, though, is tripping the crash handler. Here's my program:
namespace trash {
public class Class1 {
public void CallMe() {
string blah = null;
blah.ToLower();
}
}
class Program {
static void Main(string[] args) {
AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
var class1 = typeof(Class1);
var method = class1.GetMethod("CallMe");
try {
var obj = new Class1();
method.Invoke(obj, null); // exception is not being caught!
}
catch (System.Reflection.TargetInvocationException) {
Console.Write("what you would expect");
}
}
static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) {
Console.Write("it would be horrible if this got tripped but it doesn't!");
}
}
}
You can't catch all exceptions. There's a few assumptions in your example. You are, for instance, assuming the exception was raised on the calling thread. Catching unhandled exceptions on other threads depends on which runtimes you're using (console, winforms, WPF, ASP.Net, etc).
Additionally, calls to System.Environment.FailFast() do not generate any handlable condition - the process is effectively terminated with no chance for intervention.

Categories

Resources