-1

I have a WPF application where I use a SaveFileDialog. The flow is as follow:

1- The user uses the SaveFileDialog to choose a file name and close the dialog

2- The app tries to write to the file.

3- When trying to write to the file, if the file is locked, an IOException is thrown.

4- If I try to open the SaveFileDialog again, the app crashes with "A heap has been corrupted" on ntdll.dll

I can't figure out a solution. Even inside a Try..Catch the app crashes.

Code for the SaveFileDialog

try{
    Dispatcher.BeginInvoke(new Action(() =>
        {
            SaveFileDialog sfd = new SaveFileDialog();
            sfd.Reset();

            sfd.AddExtension = true;
            sfd.CheckFileExists = false;
            sfd.CheckPathExists = true;
            sfd.CreatePrompt = false;
            sfd.OverwritePrompt = true;
            sfd.DefaultExt = defaultExt;
            sfd.Filter = filter;
            sfd.Title = "Save As " + fileTypeDisplay;
            sfd.InitialDirectory = specialFolder;
            sfd.FileName = newFileNameNoExt;
            sfd.FilterIndex = 1;


            if (!string.IsNullOrEmpty(specialFolder))
            {
                FileDialogCustomPlace cp = new FileDialogCustomPlace(specialFolder);  // does not throw exceptions
                sfd.CustomPlaces.Add(cp);
            }
            try
            {
                if (sfd.ShowDialog(MyMainWindow) == true) //<-- ERROR HERE
                {
                    fileToSave = sfd.FileName;
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
            finally
            {
                sfd = null;
            }
        })).Wait();
}
catch(exception ex)
{
...log exception...
}
  • Hm, it looks like you are hitting some nasty bug in WPF itself. But you have also an issue. Calling `BeginInvoice().Wait()` is a mistake - use `Invoke` instead. – Nick Jan 14 '19 at 14:27
  • Try catchs everywhere = bad programming practice. Start by removing them, that code does not need them. Also MyMainWindow with capital letters is referring to a class or an instance of a class? If it is to a class, there is your error, if it's an instance of a class, then, the name shouldn't start with a capital letter as it is confusing. Finally, calling BegingInvoke and waiting for the invoke will block the calling thread so you shouldn't use it like that, use Invoke as suggested by @Nick. Maybe you should post a complete example so the problem can be better analyzed. – Isma Jan 14 '19 at 14:35
  • The try...catch everywhere is just because I was trying to handle the exception some how. MyMainWindow is an instance, not a class. few adjustments on the code was made for better reading only on Stackoverflow editor. I have tried BeginInvoke as well as with Invoke and the error is the same. I'm about 3 days on this without any solution – Frederico Almeida Jan 14 '19 at 14:38
  • What if you call ShowDialog() without any arguments? -> Same behavior – Frederico Almeida Jan 14 '19 at 15:13
  • It's not clear what you're trying to achieve by moving calling save dialog into `Dispatcher.BeginInvoke()`? I guess the problem lies here. – JohnyL Jan 14 '19 at 16:34

1 Answers1

1

This is not the answer but how I solved the crash. This is a legacy code from long ago and the clue is that the crash is always after an exception. But why an exception would cause problems to the SaveFileDialog and cause the app to crash?

Going deeper on the code I learned the code below is execute after the file is selected by the user on the SaveFileDialog.

Note the call to the method AnotherUserIsLockingPkg in the catch block.

When I commented out the call to that method, the call to SaveFileDialog.ShowDialog() on the question stopped to crash the application. I'll try to follow other suggestions and see the behavior.

If anybody has any idea of why that happens, comments are appreciated.

FileStream strm = null;
try
{
    strm = fi.Open(FileMode.Open, forFileAccessMode, fileShare);
}
catch (IOException) // the file is already open
{
    ...

    fiuEx.IsByOtherUser = AnotherUserIsLockingPkg(filePath);

    ...
}
catch (Exception ex) 
{
    ...
}
finally
{
    ....
}

The code below is used to check if the file is being locked by other application. It uses some API calls. Looks like this code was adapted from https://stackoverflow.com/a/20623311/3044154

The method AnotherUserIsLockingPkg is listed down below.

#region Check if another user's process is locking a pkg

[StructLayout(LayoutKind.Sequential)]
private struct RM_UNIQUE_PROCESS
{
    public int dwProcessId;
    public System.Runtime.InteropServices.ComTypes.FILETIME ProcessStartTime;
}

private const int RmRebootReasonNone = 0;
private const int CCH_RM_MAX_APP_NAME = 255;
private const int CCH_RM_MAX_SVC_NAME = 63;

//private enum RM_APP_TYPE
//{
//    RmUnknownApp = 0,
//    RmMainWindow = 1,
//    RmOtherWindow = 2,
//    RmService = 3,
//    RmExplorer = 4,
//    RmConsole = 5,
//    RmCritical = 1000
//}

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct RM_PROCESS_INFO
{
    public RM_UNIQUE_PROCESS Process;

    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCH_RM_MAX_APP_NAME + 1)]
    public string strAppName;

    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCH_RM_MAX_SVC_NAME + 1)]
    public string strServiceShortName;

    //public RM_APP_TYPE ApplicationType;
    public uint AppStatus;
    public uint TSSessionId;
    [MarshalAs(UnmanagedType.Bool)]
    public bool bRestartable;
}

[DllImport("rstrtmgr.dll", CharSet = CharSet.Unicode)]
private static extern int RmRegisterResources(uint pSessionHandle,
                                      UInt32 nFiles,
                                      string[] rgsFilenames,
                                      UInt32 nApplications,
                                      [In] RM_UNIQUE_PROCESS[] rgApplications,
                                      UInt32 nServices,
                                      string[] rgsServiceNames);

[DllImport("rstrtmgr.dll", CharSet = CharSet.Auto)]
private static extern int RmStartSession(out uint pSessionHandle, int dwSessionFlags, string strSessionKey);

[DllImport("rstrtmgr.dll")]
private static extern int RmEndSession(uint pSessionHandle);

[DllImport("rstrtmgr.dll")]
private static extern int RmGetList(uint dwSessionHandle,
                            out uint pnProcInfoNeeded,
                            ref uint pnProcInfo,
                            [In, Out] RM_PROCESS_INFO[] rgAffectedApps,
                            ref uint lpdwRebootReasons);


/// <summary>
/// Checks if a pkg has been locked by another user
/// </summary>
/// <param name="path">The pkg file path.</param>
/// <param name="includeCurrentUserProcesses">Check also for current user's processes</param>
/// <returns></returns>
public static bool AnotherUserIsLockingPkg(string path, bool includeCurrentUserProcesses = false)
{
    uint handle;
    string key = Guid.NewGuid().ToString();
    Process currentProcess = Process.GetCurrentProcess();

    int res = RmStartSession(out handle, 0, key);
    if (res != 0)
        throw new Exception("Could not begin restart session. Unable to determine file locker.");

    try
    {
        const int ERROR_MORE_DATA = 234;

        uint pnProcInfoNeeded = 0,
             pnProcInfo = 0,
             lpdwRebootReasons = RmRebootReasonNone;

        string[] resources = new string[] { path }; // Just checking on one resource.

        res = RmRegisterResources(handle, (uint)resources.Length, resources, 0, null, 0, null);

        if (res != 0)
            throw new Exception("Could not register resource.");

        //Note: there's a race condition here -- the first call to RmGetList() returns
        //      the total number of process. However, when we call RmGetList() again to get
        //      the actual processes this number may have increased.
        res = RmGetList(handle, out pnProcInfoNeeded, ref pnProcInfo, null, ref lpdwRebootReasons);

        if (res == ERROR_MORE_DATA)
        {
            // Create an array to store the process results
            RM_PROCESS_INFO[] processInfo = new RM_PROCESS_INFO[pnProcInfoNeeded];
            pnProcInfo = pnProcInfoNeeded;

            // Get the list
            res = RmGetList(handle, out pnProcInfoNeeded, ref pnProcInfo, processInfo, ref lpdwRebootReasons);
            //pnProcInfo contains all the processes that are using the pkg

            if (res == 0)
            {
                // Enumerate all of the results and check for waf3 process and not same session
                for (int i = 0; i < pnProcInfo; i++)
                {
                    try
                    {
                        if (includeCurrentUserProcesses)
                        {
                            if (processInfo[i].strAppName == currentProcess.ProcessName)
                                return true;
                        }
                        else
                        {
                            if (processInfo[i].strAppName == currentProcess.MainModule.ModuleName && processInfo[i].TSSessionId != currentProcess.SessionId)
                                return true;
                        }
                    }
                    // catch the error -- in case the process is no longer running
                    catch (ArgumentException)
                    { }
                }
            }
            else
                throw new Exception("Could not list processes locking resource.");
        }
        else if (res != 0)
            throw new Exception("Could not list processes locking resource. Failed to get size of result.");
    }
    finally
    {
        RmEndSession(handle);
    }

    return false;
}
#endregion