1

When our C# app is missing a dependency it reports this "module could not be found" error dialog. Unfortunately this dialog does not include the name of the file it could not find. In this case the missing dependency was a DirectX dll D3DCompiler_43.dll. If a customer reports "module could not be found" it's not very helpful, but if they reported the name of the file not found it gives us at least a clue.

Can we catch this exception and produce an error dialog which includes the name of the file that wasn't found? Since it happens early in application startup (before main I believe) how can we do this?

Error Dialog

Philip
  • 1,691
  • 4
  • 21
  • 41
  • I was hoping there was a way to install an handler to catch this exception and then report a nice error message which included the name of the not-found file. The app loads 100+ dlls, so to get an error that says "something is missing" without an indication of what's missing is super unhelpful. – Philip Nov 21 '12 at 14:52
  • I said it happens early, before main. That is not true, I can step through main fine before the error occurs. When I step over Application.Run that is when the error occurs. – Philip Dec 09 '12 at 01:24
  • I can in fact catch this exception with a [ThreadExceptionEventHandler](http://www.csharp-examples.net/catching-unhandled-exceptions/). But I can't see the name of the DLL it was looking for, just "the specified module could not be found" so that doesn't help. – Philip Dec 09 '12 at 03:30

5 Answers5

2

Try subscribing to the AssemblyResolve event which is fired when the application cannot locate a required assembly. In the handler of this event you can provide your own message box containing the name of the assembly and any other info you like.

public static void Main()
{            
     AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(AssemblyNotFoundEventHandler);

     InvokeExternalType();
}

private static Assembly ResolveEventHandler(object sender, ResolveEventArgs args)
{
    MessageBox.Show("Error, can't find assembly: " + args.Name, "Error", MessageBoxButtons.AbortRetryIgnore, MessageBoxIcon.Error);
    return null;
}

private static void InvokeExternalType()
{
    MyClass doc = ... // from an external assembly.
}

There is one hitch where the external type (that might belong to a missing assembly) must not be used in the main method or else the event won't be fired. It has to be called further in your code in a separate method as shown in the code sample above.

Adam
  • 2,762
  • 1
  • 30
  • 32
  • Sounds really promising but I tried it. Does not fire in our case. Perhaps because the DLL in question is pure C++, it's not actually an Assembly? Because we have a C# app, it links to C++ code which references system (directx) DLLs. This ResolveEventHandler does not seem to fire if the directx DLL is mising. – Philip Dec 09 '12 at 00:51
2

If you can modify the code and redeploy:

  • Create a new startup class (one with Main()), make sure you choose that class in project properties
  • Ensure there are no "using" and external references from your startup class outside System
  • put a try/catch around a call to your previous startup class's Main
  • print out the exception and all inner exceptions in a message box (or debug log)

    public class MyNewEntryClass{

    public static void Main(){

      try{
          MyPreviousEntryClass.Main();
      } catch (Exception x){
         Exception ix = x;
         while (ix!=null){
            MessageBox.Show("Exception: "+ix);
            ix=ix.InnerException;
         }
      }
    

    } }

EDIT: I see your exception is coming from a message handler. The above code edit can (and should) be applied to all entry methods, which includes all event handlers that handle events from outside your code. The above crash screen could show up for exceptions in various handlers, asynchronously executed code. For example a button handler with asynchrounous calls should have two try/catches to prevent the crash window from showing up:

private void Button_Clicked(object sender, EventArgs arg){
    try{
        Action<string> asyncCall = (s)=>{
            try{
                //...exception here will cause crash  
                // as it's not handled in Button_Clicked
            } catch (Exception xOnAsyncThread){
            }
        };
        asyncCall.BeginInvoke("outahere",null,null);
    } catch (Exception xOnUIThread){
    }
}

The above statement is true for windows message handling as well - it's just another event handler for an outside event

If you can't modify the code and redeploy use Sysinternals Process Monitor: http://technet.microsoft.com/en-us/sysinternals/bb896645 Configure it to monitor your app and highlight file not found events (see their docs for details)

If ProcessMonitor is too much copy all the contents from the exception message and go by exclusion - the ones that are loaded aren't the problem

Sten Petrov
  • 10,943
  • 1
  • 41
  • 61
  • Tried first snippet. Clever idea to have a 2nd main but does not help in this case. Seems like that would help if the exception were firing before the original main was entered. But my exception is firing after main is entered. I already had a try/catch around the original main. That doesn't catch the exception neither does the one in this "outter" main. – Philip Dec 09 '12 at 01:12
  • Second snippet I'm not clear where to put that. This is on app startup there is no button clicked or similar handler involved. I put a breakpoint on our WndProc and it doesn't get called before the FileNotFoundException happens, so I don't know why it's in the stack. – Philip Dec 09 '12 at 01:20
  • The second snippet illustrates __any__ event handler, such as WndProc or Button_Clicked. If your WndProc doesn't get hit then try calling it through a wrapper - apply the same logic as described above for Main to your WndProc; This means a new class with no references, just static WndProc method that would call the original one. ALSO - subscribe to AppDomain.CurrentDomain.AssemblyResolve and AppDomain.CurrentDomain.UnhandledException events and show in a message box the info you're getting from them – Sten Petrov Dec 09 '12 at 19:00
  • I can catch the exception with ThreadExceptionEventHandler. The problem is the exception does not contain any information about the file which was not found. – Philip Dec 09 '12 at 22:40
  • What did the AssemblyResolve event output last, before the exception? Try enabling the fusion log as described in another comment here, this should put the missing assembly name in the exception. – Sten Petrov Dec 09 '12 at 22:52
  • AssemblyResolve handler is never called. The FusionLog works, but doesn't contain any trace of the missing DLL. It seems to only contain IL/assembly stuff, no native DLLs. The caught exception (via ThreadExceptionEventHandler) does not contain any more information when the FusionLog is running, in fact the FusionLog field in the exception is null. – Philip Dec 11 '12 at 22:30
  • Did you try using Sysinternals' Process Monitor? What is the last DLL it fails to find? Does this function happen to work on the dev machine but fail on a user machine? – Sten Petrov Dec 11 '12 at 23:06
  • Can you post or send the entire contents of that error message window? You can click on the text, CTRL-A CTRL-C to copy it. – Sten Petrov Dec 11 '12 at 23:18
  • Does your code work on developers' machines but fail for users? – Sten Petrov Dec 12 '12 at 05:06
  • Process Monitor did work. We found the name of the missing DLL. We've only see it missing on non-developer machines. We have a proposed solution to the problem, a fix to our installer. So in the future we should never see this exact missing DLL. – Philip Dec 12 '12 at 13:31
  • The SO question was: in general for any missing DLL, can the EXE itself report "foo.dll not found" instead of "module not found". I conclude that it's not possible. The exception can be caught, but the exception doesn't have the name of the DLL in it. – Philip Dec 12 '12 at 13:56
1

Make you customer run depends.exe on your exe, save the results into a file and send this file to you.

Flot2011
  • 4,601
  • 3
  • 44
  • 61
  • Good idea, but the DirectX DLL's don't show up as a dependency in depends. Maybe because although it's a C# app we link to C++ libraries which pull in DirectX. – Philip Nov 21 '12 at 14:35
1

I don't think you can do this programmatically. However, if you have access to the machine you can turn on a "global flag" to show what's called "Loader Snaps" (Show loader snaps (Windows Debuggers)) at least to identify the problem.

Here is an article about it that explains how to do it: Debugging LoadLibrary Failures

Simon Mourier
  • 132,049
  • 21
  • 248
  • 298
  • This looks like a great debugging technique. However it doesn't resolve the question which is how to have the application report the missing file on an unmodified machine. But definitely good to know about, thanks. – Philip Dec 08 '12 at 03:21
  • Accepted this one because it says you can't do it, you can't get the name of the missing DLL programatically. Did not try the loader snaps part, I used Process Monitor to figure out the name of the DLL as a one time thing. But really wanted a way for the EXE to report the name of the missing DLL directly to the user. This does not seem to be possible, in my case. – Philip Dec 27 '12 at 04:20
1

There is a "Fuison Log" which could be turned on by setting few registry entries. It helps to identify any kind of assembly loading issues. All you will need is the log files from the customer's PC.
Please see https://stackoverflow.com/a/1527249/1288776 for the details.
In case the link is not available:
Add the following values to HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Fusion

DWORD ForceLog set value to 1
DWORD LogFailures set value to 1
DWORD LogResourceBinds set value to 1
String LogPath set value to folder for logs ie) C:\FusionLog\

Make sure you include the backslash after the Folder name and that the Folder exists.

Community
  • 1
  • 1
Andrew
  • 91
  • 1
  • 3
  • This is great to know about but what we really want is an EXE running on an unmodified PC that can report the name of the missing dll. But thanks, this is useful. – Philip Dec 09 '12 at 00:49
  • I was able to turn on this log, but it didn't have any information about the missing DLL. Seemed like this was all IL/assembly stuff, and pure C/C++ DLL's weren't included. Maybe I am missing something, but I grepped through all logs products for the DLL we're missing, and for DLL in general, and did not see normal system DLLs in there. – Philip Dec 12 '12 at 04:39