2

I am trying to use existing preview handlers to display previews of files.

I wrote a simple test program to 1) find the CLSID of the preview handler for a given file, 2) instantiate the preview handler, 3) initialize it, either by stream or file and 4) render the preview on a basic window.

This works. More or less.

It works just fine for the pdf preview handler provided by adobe acrobat reader, but doesn't work with the windows provided pdf preview handler (CLSID {3A84F9C2-6164-485C-A7D9-4B27F8AC009E}, provided by edge in PdfPreviewHandler.dll, just for reference). (It doesn't fail anywhere, it just doesnt work and doesn't render a preview, see images). windows provided pdf preview handler acrobat reader pdf handler

Same situation for the microsoft office preview handlers for excel (.xlsx) and power point (.pptx) files.

For word (.docx) files, it fails completely. The IInitializeWithFile call in line 106 fails with "Unspecified Error" (HRESULT 0x80004005).

A bunch of other preview handlers work just fine, some initialized by stream, some by file (e.g. windows provided handler for html and text files).

I don't really know what the issue could be or where i even should start looking, any input on this would be appreciated.

compile with cl /std:c++20 test.cpp ole32.lib shlwapi.lib user32.lib /EHsc, expects file path as first executable argument.

#include <filesystem>
#include <array>
#include <cassert>
#include <stdexcept>
#include <iostream>

#include "Windows.h"
#include "ShObjIdl.h"
#include "shlwapi.h"
#include "objbase.h"

#define checkHresult(res) (checkHresult_(res, __LINE__, __FILE__))

void checkHresult_(HRESULT res, int line, const char *file){
    if(res != S_OK){
        std::stringstream msg;
        msg << file << ':' << line << ": 0x" << std::hex << res << ' ';
        LPSTR errMsg;
        if(FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, 
                          nullptr, 
                          res, 
                          MAKELANGID(LANG_ENGLISH, SUBLANG_DEFAULT), 
                          reinterpret_cast<LPSTR>(&errMsg), 
                          0, 
                          nullptr)){
            msg << errMsg;
            LocalFree(errMsg);
        }
        throw std::runtime_error(msg.str());
    }
}

CLSID getShellExtensionClsidForFileType(const std::wstring& extension, const GUID& interfaceID){
    HRESULT res;
    std::array<wchar_t, 39> ifIdWStr;
    int written;
    written = StringFromGUID2(interfaceID, ifIdWStr.data(), ifIdWStr.size());
    if(written == 0){
        checkHresult(HRESULT_FROM_WIN32(GetLastError())); //StringFromGUID2 should not fail
    }

    std::array<wchar_t, 39> extIdWStr;
    DWORD extIdWStrSize = extIdWStr.size();
    res = AssocQueryStringW(ASSOCF_INIT_DEFAULTTOSTAR, 
                            ASSOCSTR_SHELLEXTENSION,
                            extension.c_str(),
                            ifIdWStr.data(),
                            extIdWStr.data(),
                            &extIdWStrSize);
    checkHresult(res);
    
    CLSID extId;
    res = IIDFromString(extIdWStr.data(), &extId);
    checkHresult(res); //IIDFromString should not fail
    std::wcout << "preview handler clsid: " << extIdWStr.data() << '\n';
    return(extId);
}


IPreviewHandler* getIPreviewHandlerInterfaceForType(const std::wstring& extension){
    HRESULT res;
    //get the CLSID for the preview handler for the specified fily type
    CLSID iPreviewHandlerClsid(getShellExtensionClsidForFileType(extension, IID_IPreviewHandler));
    IPreviewHandler *iPreviewHandler;
    res = CoCreateInstance(iPreviewHandlerClsid, 
                           nullptr, 
                           CLSCTX_LOCAL_SERVER, 
                           IID_IPreviewHandler, 
                           reinterpret_cast<LPVOID*>(&iPreviewHandler));
    checkHresult(res);
    return(iPreviewHandler);
}

int wmain(int argc, wchar_t *argv[]){
    try{
        if(argc != 2){
            return(1);
        }
        HRESULT res;

        res = CoInitialize(nullptr);
        checkHresult(res);

        std::filesystem::path filePath(argv[1]);
        filePath.make_preferred();

        //Instantiate the preview handler for the specified file type
        IPreviewHandler *iPreviewHandler = getIPreviewHandlerInterfaceForType(filePath.extension());

        IInitializeWithStream *iInitializeWithStream;
        IInitializeWithFile *iInitializeWithFile;

        iPreviewHandler->QueryInterface(IID_IInitializeWithStream, reinterpret_cast<LPVOID*>(&iInitializeWithStream));
        iPreviewHandler->QueryInterface(IID_IInitializeWithFile, reinterpret_cast<LPVOID*>(&iInitializeWithFile));

        //Initialize preview handler, preferably with a stream
        if(iInitializeWithStream){
            IStream *iStream;
            res = SHCreateStreamOnFileEx(filePath.c_str(), STGM_READ | STGM_SHARE_DENY_WRITE, 0, false, nullptr, &iStream);
            checkHresult(res);
            res = iInitializeWithStream->Initialize(iStream, STGM_READ);
            checkHresult(res);
            std::cout << "Initialized with Stream\n";
        }else if(iInitializeWithFile){
            res = iInitializeWithFile->Initialize(filePath.c_str(), STGM_READ);
            checkHresult(res);
            std::cout << "Initialized with File\n";
        }else{
            checkHresult(E_NOINTERFACE);
        }

        //create basic window
        WNDCLASSW wndClass;
        wndClass.style = 0;
        wndClass.lpfnWndProc = DefWindowProcW;
        wndClass.cbClsExtra = 0;
        wndClass.cbWndExtra = 0;
        wndClass.hInstance = GetModuleHandleA(nullptr);
        wndClass.hIcon = nullptr;
        wndClass.hCursor = nullptr;
        wndClass.hbrBackground = nullptr;
        wndClass.lpszMenuName = nullptr;
        wndClass.lpszClassName = L"test";

        ATOM wndAtom = RegisterClassW(&wndClass);
        if(wndAtom == 0){
            checkHresult(HRESULT_FROM_WIN32(GetLastError()));
        }
        HWND window = CreateWindowExW(0, 
                                      L"test", 
                                      L"", 
                                      WS_VISIBLE, 
                                      CW_USEDEFAULT, 
                                      CW_USEDEFAULT, 
                                      CW_USEDEFAULT, 
                                      CW_USEDEFAULT, 
                                      0, 
                                      0, 
                                      wndClass.hInstance, 
                                      nullptr);
        if(window == nullptr){
            checkHresult(HRESULT_FROM_WIN32(GetLastError()));
        }

        ShowWindow(window, SW_NORMAL);

        RECT rect;
        GetClientRect(window, &rect);
        res = iPreviewHandler->SetWindow(window, &rect);
        checkHresult(res);
        res = iPreviewHandler->DoPreview();

        MSG msg;
        while(GetMessageW(&msg, nullptr, 0, 0) > 0){
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }catch(std::runtime_error err){
        std::cout << err.what();
    }
}
lulle2007200
  • 888
  • 9
  • 20

1 Answers1

1

You just need to add a SetRect call after initialization:

RECT rect;
GetClientRect(window, &rect);
iPreviewHandler->SetWindow(window, &rect);
iPreviewHandler->DoPreview();

// add this
iPreviewHandler->SetRect(&rect);
Simon Mourier
  • 132,049
  • 21
  • 248
  • 298
  • Just tried that, unfortunately didn't solve it (no change). I am passing the rect together with the hwnd in the SetWindow call already though. To my understanding, the SetRect function is (mainly) meant to change the draw area at a later point, if necessary. – lulle2007200 May 31 '22 at 11:30
  • @lulle - Works fine for me while with DOCX, EXCEL etc. while it didn't work before https://i.imgur.com/9NxTqQS.png (Windows 10, compiled as x64 with Office installed) – Simon Mourier May 31 '22 at 11:35
  • Hm, interesting. I was using a x64 Windows 11 machine with office 365 installed. I will test it on Windows 10 later, i guess. Did it only work with the additional SetRect call for you? Found a note somewhere in msdn documentation that that should not be necessary. – lulle2007200 May 31 '22 at 11:43
  • @lulle - It's supposed not to be necessary, but for most handlers, it works only with SetRect. Also many files (like images) have no specific handler and the Shell fallbacks to the thumbnail provider, if any. – Simon Mourier May 31 '22 at 12:03
  • Actually, the additional SetRect call did the trick. Seems like a lot of preview handlers don't work properly otherwise. There are some other scaling related issues though. When dpi scaling is not set to 100%, some preview handlers are not aligned to the rect and/or render too big/small/not at all. But that's another story. – lulle2007200 May 31 '22 at 13:43