IPreviewHandler выдает неуловимое исключение

Я импортировал интерфейс COM IPreviewHandler в приложение WinForms, и я использую его для отображения предварительных просмотров для различных типов документов (я ищу GUID соответствующего обработчика предварительного просмотра в реестре, затем использую Activator.CreateInstance(guid) создать конкретный класс COM.

Это прекрасно работает для подавляющего большинства типов файлов - форматов Office, PDF, видео и т. Д. - однако, после того, как я создаю экземпляр "Microsoft Windows TXT Preview Handler" {1531d583-8375-4d3f-b5fb-d23bbd169f22}, инициализируйте его потоком, содержащим обычный файл.txt, установите границы окна предварительного просмотра и, наконец, вызовите DoPreview(), Я получаю исключение, которое не может быть перехвачено с помощью try...catch:

try {
    Type comType = Type.GetTypeFromCLSID(guid);
    object handler = Activator.CreateInstance(comType);

    if (handler is IInitializeWithStream) {
        Stream s = File.Open(filename, FileMode.Open);
        // this just passes the System.IO.Stream as the COM type IStream
        ((IInitializeWithStream)handler).Initialize(new StreamWrapper(s), 0);
    }
    else {
        throw new NotSupportedException();
    }

    RECT r = new RECT();
    r.Top = 0;
    r.Left = 0;
    r.Right = hostControl.Width;
    r.Bottom = hostControl.Height;

    ((IPreviewHandler)handler).SetWindow(hostControl.Handle, ref r);
    ((IPreviewHandler)handler).DoPreview();    // <-- crash occurs here
}
catch (Exception) {
    // this will never execute
}

Когда я перехожу к отладчику, происходит сбой процесса размещения Visual Studio. Без отладчика приложение вылетает без запуска AppDomain.UnHandledException или же Application.ThreadException События.

Я не против того, чтобы я не мог просматривать простые текстовые файлы, используя эту технику (рабочие обработчики предварительного просмотра для форматов Office и т. Д. Достаточны для требований моего приложения), но я обеспокоен тем, что мое приложение неуправляемо аварийно завершает работу, если пользователь выбирает файл.txt. Есть ли способ, как я могу поймать эту ошибку и обработать ее изящно? Еще лучше, есть ли способ, как я могу преодолеть это и заставить обработчик работать?

5 ответов

Решение

Я не мог заставить GetPreviewHandlerGUID() распознавать файл.txt, и мне пришлось напрямую вводить GUID. Вы можете увидеть, что идет не так, когда вы используете Project + Properties, Debug, отметьте флажок Включить отладку неуправляемого кода.

Отладчик теперь остановится на проблеме и отобразит

Обнаружено STATUS_STACK_BUFFER_OVERRUN

Вершина стека вызовов выглядит следующим образом:

kernel32.dll!_UnhandledExceptionFilter@4()  + 0x1a368 bytes 
shell32.dll!___report_gsfailure()  + 0xc8 bytes 
shell32.dll!CRTFPreviewHandler::_StreamInCallback()  + 0x74 bytes   
msftedit.dll!CLightDTEngine::ReadPlainText()  + 0xed bytes  
msftedit.dll!CLightDTEngine::LoadFromEs()  + 0x202b3 bytes  
msftedit.dll!CTxtEdit::TxSendMessage()  + 0x1e25f bytes 
msftedit.dll!_RichEditWndProc@16()  + 0x13d bytes   

Проблема находится в функции StreamInCallback(). Он вызывается RichTextBox, который используется для отображения предварительного просмотра (msftedit.dll) для загрузки файла. В коде этой функции обратного вызова есть ошибка, он уничтожает "канарейку", которая используется для обнаружения повреждения кадра стека из-за переполнения буфера.

Это часть контрмер, которые Microsoft предприняла, чтобы не дать вирусам внедриться из-за переполнения буфера. Параметр компиляции /GS в Visual Studio для языков C/C++. После обнаружения CRT очень быстро завершает программу. Это происходит без получения исключения, стек нельзя безопасно развернуть, потому что он был взломан. Соответственно, CLR не может поймать исключение.

Эта ошибка относится только к программе просмотра файлов TXT. Вы ничего не можете с этим поделать, кроме как не использовать его. Сообщать об этой ошибке на connect.microsoft.com, вероятно, бесполезно, они закроют ее как "внешнюю". В противном случае это тонкий намек на то, что может случиться, если вы позволите неуправляемому коду запускаться внутри вашей программы;)

Настоящая причина, по которой вы получаете эту проблему, заключается в том, что вы создаете объект обработчика предварительного просмотра в процессе. Правильный способ - создать его вне процесса.

РАСКРЫТИЕ ИНФОРМАЦИИ Следующее содержит рекламу моего блога / фрагмента кода.

См. https://github.com/GeeLaw/PreviewHost для примера. В частности, смотрите строку 219 PreviewHandler.cs, где вы должны пройти CLSCTX_LOCAL_SERVER в CoCreateInstance, Как объяснено в одной из моих записей в блоге, Activator.CreateInstance разрешает внутрипроцессный сервер, который не соответствует ожиданиям обработчиков предварительного просмотра, поскольку они должны быть созданы в правильном суррогатном процессе, как описано в MSDN.

У меня была та же проблема, и я смог заставить TXT PreviewHandler работать, компилируя в x64 вместо AnyCPU,

Я использую Visual Studio 2010 в Windows 7 (64-разрядная версия), поэтому этот ответ не будет применяться, если вы работаете в 32-разрядной ОС.

В Visual Studio 2010

  • нажми на Configurations выпадающий список
  • Выбрать Configuration Manager...
  • нажмите в Platform клетка рядом с вашим проектом
  • Выбрать New... и выберите целевую платформу x64
  • скопировать настройки из AnyCPU и понеслось.

Это очень маловероятно, но может быть проблемой здесь - catch(Exception) будет ловить только исключения типа Exception - попробуйте использовать catch без фильтрации любого типа.

catch(Exception ex) {
   // Normal logging etc
}
catch
{
   // Exception of types other than System.Exception.
}

Я думаю, что нашел решение этой проблемы. Дело в том, что создаваемый вами поток очищается сборщиком мусора или чем-то еще. Если вы вызываете метод initialize, используя поток, созданный кодом ниже, он должен работать:

System.Runtime.InteropServices.ComTypes.IStream stream;
    byte[] fileData = System.IO.File.ReadAllBytes(filename);
    System.IntPtr hGlobal = System.Runtime.InteropServices.Marshal.AllocHGlobal(fileData.Length);
    System.Runtime.InteropServices.Marshal.Copy(fileData, 0, hGlobal, fileData.Length);
    NativeMethods.CreateStreamOnHGlobal(hGlobal, false, out stream);
    //[DllImport("ole32.dll")]
    //internal static extern int CreateStreamOnHGlobal(IntPtr hGlobal, bool fDeleteOnRelease, out IStream ppstm);

Я использую приведенный выше код в приложении Windows Forms, явно установленном на 32-битный (x86) и работающем в режиме однопоточных квартир.

Кредит идет на дома Шерлока ( http://www.tech-archive.net/Archive/DotNet/microsoft.public.dotnet.framework.interop/2010-09/msg00003.html)

Другие вопросы по тегам