Preview handler only works when application is invoked from Explorer

496 views Asked by At

I made a simple application that creates a window and then uses preview handlers to display a preview of a file passed as an argument to the application, see code below.

This works perfectly fine when just drag'n'dropping the file to preview onto the .exe file (or alternatively just hard coding the file path instead and then launching the .exe directly from explorer), but fails for almost all preview handlers when invoking it from a terminal (powershell, cmd, whatever) with e.g. ./main.exe testfile.docx. It also fails when trying to invoke it from other processes.

The issue i described happens (among others) for the following preview handlers:

  • MS Office preview handler for excel files, CLSID {00020827-0000-0000-C000-000000000046}
  • MS Office preview handler for word files, CLSID {84F66100-FF7C-4fb4-B0C0-02CD7FB668FE}
  • Edge preview handler for pdf files, CLSID {3A84F9C2-6164-485C-A7D9-4B27F8AC009E}
  • MS Office preview handler for power point files, CLSID {65235197-874B-4A07-BDC5-E65EA825B718}. For the power point preview handler, the initialize call to the IInitializeWithFile interface fails hresult 0x86420003

It works fine for the following preview handlers:

  • Windows provided preview handler for text files, CLSID {1531d583-8375-4d3f-b5fb-d23bbd169f22}
  • Windows provided preview handler for html files, CLSID {f8b8412b-dea3-4130-b36c-5e8be73106ac}

When i say it "fails", i mean that it just doesn't display a preview, none of the function calls fail. See images:

test.xlsx excel file dragged onto .exe file, same result when hardcoding path to test.xlsx and just starting the application from explorer: enter image description here

Path to test.xlsx passed as command line argument (./main.exe ./test.xlsx in powershell): enter image description here

I am not sure, what would cause this or where to even start looking or what to search for, so any input on this would be appreciated.

Note: I asked a similar question a few days ago, but that was a seperate issue.

Compile with cl /std:c++20 /EHsc main.cpp, expects path to a file to preview as first command line parameter. Since i used a bit of winrt, this requires windows 10.

#pragma comment(lib, "Shlwapi.lib")
#pragma comment(lib, "user32.lib")
#pragma comment(lib, "WindowsApp.lib")
#pragma comment(lib, "Ole32.lib")

#include <iostream>
#include <string>
#include <string_view>
#include <array>
#include <filesystem>

#include <Windows.h>
#include <shlwapi.h>
#include <winrt/base.h>
#include <ShObjIdl.h>


class Preview{
    winrt::com_ptr<IPreviewHandler> mPreviewHandler;
    bool mIsActive = false;
    bool mIsInitialized = false;
    std::filesystem::path mFilePath;
    HWND mHwnd;
    RECT mRect;
public:
    Preview(const std::filesystem::path& filePath, const HWND hwnd, const RECT& rect):
        mFilePath(filePath), mHwnd(hwnd), mRect(rect){
        createPreviewHandler();
        initialize();
    };
    void setRect(const RECT& rect){
        mPreviewHandler->SetRect(&rect);
        mRect = rect;
    }
    void setWindow(const HWND hwnd, const RECT& rect){
        mPreviewHandler->SetWindow(hwnd, &rect);
        mHwnd = hwnd;
        setRect(rect);
    }
    void startPreview(){
        if(!mIsActive){
            if(!mIsInitialized){
                initialize();
            }
            mPreviewHandler->DoPreview();
            setRect(mRect);
            mIsActive = true;
        }
    }
    void stopPreview(){
        if(mIsActive){
            mPreviewHandler->Unload();
            mIsActive = false;
            mIsInitialized = false;
        }
    }

private:
    void createPreviewHandler(){
        CLSID previewHandlerId = getShellExClsidForType(mFilePath.extension());
        mPreviewHandler.capture(CoCreateInstance, previewHandlerId, nullptr, CLSCTX_LOCAL_SERVER);
    }
    CLSID getShellExClsidForType(const std::wstring& extension){
        winrt::hresult res;
        DWORD size;
        std::array<wchar_t, 39> interfaceIdWstr;
        size = StringFromGUID2(IID_IPreviewHandler, interfaceIdWstr.data(), interfaceIdWstr.size());
        if(!size){
            winrt::throw_hresult(HRESULT_FROM_WIN32(GetLastError()));
        }

        std::array<wchar_t, 39> exIdWstr;
        res = AssocQueryStringW(ASSOCF_INIT_DEFAULTTOSTAR, 
                                ASSOCSTR_SHELLEXTENSION, 
                                extension.c_str(), 
                                interfaceIdWstr.data(), 
                                exIdWstr.data(), 
                                &size);
        winrt::check_hresult(res);

        CLSID exId;
        res = IIDFromString(exIdWstr.data(), &exId);
        winrt::check_hresult(res);

        return(exId);
    }
    void initialize(){
        initializeFromPath(mFilePath);
        setWindow(mHwnd, mRect);
        mIsInitialized = true;
    }
    void initializeFromPath(const std::filesystem::path& filePath){
        if(!initWithFile(filePath) && !initWithStream(filePath)){
            winrt::throw_hresult(E_NOINTERFACE);
        }
    }
    bool initWithFile(const std::filesystem::path& filePath){
        if(!mPreviewHandler.try_as<IInitializeWithFile>()){
            return(false);
        }
        winrt::check_hresult(mPreviewHandler.as<IInitializeWithFile>()->Initialize(filePath.c_str(), STGM_READ));
        return(true);
    }
    bool initWithStream(const std::filesystem::path& filePath){
        if(!mPreviewHandler.try_as<IInitializeWithStream>()){
            return(false);
        }
        winrt::com_ptr<IStream> stream;
        stream.capture([](LPCWSTR pszFile, DWORD grfMode, REFIID riid, void **ppstm)
                       {return(SHCreateStreamOnFileEx(pszFile, grfMode, 0, false, nullptr, reinterpret_cast<IStream**>(ppstm)));},
                       filePath.c_str(), STGM_READ | STGM_SHARE_DENY_WRITE | STGM_FAILIFTHERE);
        winrt::check_hresult(mPreviewHandler.as<IInitializeWithStream>()->Initialize(stream.get(), STGM_READ));
        return(true);
    }
};

HMODULE getCurrentModule(){
    HMODULE hModule;
    GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,
                      (LPCTSTR)getCurrentModule,
                      &hModule);
    return(hModule);
}

LRESULT windowProc(HWND hwnd,
                   UINT msg,
                   WPARAM wParam,
                   LPARAM lParam){
    LPARAM ret = 0;
    switch(msg){
        case WM_CLOSE:
        {
            PostQuitMessage(0);
            ret = 0;
        }break;
        default:
        {
            ret = DefWindowProc(hwnd, msg, wParam, lParam);
        }break;
    }
    return(ret);
}

HWND createTestWindow(){
    WNDCLASSW wndClass;
    wndClass.style = 0;
    wndClass.lpfnWndProc = windowProc;
    wndClass.cbClsExtra = 0;
    wndClass.cbWndExtra = 0;
    wndClass.hInstance = getCurrentModule();
    wndClass.hIcon = nullptr;
    wndClass.hCursor = nullptr;
    wndClass.hbrBackground = nullptr;
    wndClass.lpszMenuName = nullptr;
    wndClass.lpszClassName = L"test";

    ATOM wndAtom = RegisterClassW(&wndClass);
    if(!wndAtom){
        winrt::throw_hresult(HRESULT_FROM_WIN32(GetLastError()));
    }
    HWND window = CreateWindowExW(0, 
                                  L"Test", 
                                  L"", 
                                  WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX, 
                                  CW_USEDEFAULT, 
                                  CW_USEDEFAULT, 
                                  CW_USEDEFAULT, 
                                  CW_USEDEFAULT, 
                                  0, 
                                  0,
                                  wndClass.hInstance, 
                                  nullptr);
    if(!window){
        winrt::throw_hresult(HRESULT_FROM_WIN32(GetLastError()));
    }

    ShowWindow(window, SW_NORMAL);

    return(window);
}

int main(int argc, char *argv[]){
    try{
        winrt::check_hresult(CoInitialize(nullptr));
        if(!SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_SYSTEM_AWARE)){
            winrt::throw_hresult(E_FAIL);
        }

        if(argc != 2){
            return(1);
        }

        std::filesystem::path filePath(argv[1]);
        HWND window = createTestWindow();

        RECT rect;
        GetClientRect(window, &rect);
        Preview preview(filePath, window, rect);
        preview.startPreview();

        MSG msg;
        while(GetMessageW(&msg, nullptr, 0, 0) != 0){
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }catch(winrt::hresult_error err){
        std::wcout << "0x" << std::hex << err.code() << " " << static_cast<std::wstring_view>(err.message());
    }catch(...){}
}
1

There are 1 answers

0
Anders On BEST ANSWER

Call GetFullPathName on argv[1]. The shell functions can't parse a relative path into a pidl.