Slion.net>Dev>MakingOfMs3dThumbnailProvider

Making of MS3D Thumbnail Provider

Here reads the story of Ms3dThumbnailProvider.

Motivations

As I'm developing 3D applications I'm starting to own quite a large collection of 3D models. Sorting out through them is often a tedious task without getting proper previews from your file explorer. A while back on an XP machine I had noticed that installing the 3DS Max demo version would give you file explorer Thumbnails for your 3DS files. Thus I started relying on this feature as most my models are also available in 3DS format. However I could not get that 3DS Thumbnail feature working on my Windows 7 machine. Moreover I figured having this working for MS3D files would be great as I'm using it as my primary format.

Rendering Engine

I needed a piece of software to read and render MS3D files on Windows 7 to generate the thumbnails. The obvious choice was the excellent MilkShape 3D Binary Model Viewer which source code you can get from chUmbalum sOft download section. As is you can invoke msViewer2 from the command line and pass it a file to render. Only trouble is msViewer2 is using freeglut which does not seems to provide support for off-screen rendering on Windows.

Now I wasted quite a lot of time trying to figure out how to do off-screen rendering using OpenGL on Windows. I decided not to do proper off-screen rendering for a first version and simply go the glReadPixel way. However somehow I managed to hide the rendering window but your file explorer still loses the focus when the renderer is launched for a second or so which is pretty annoying but still bearable for a first release.

So I slightly modified msViewer2 and the freeglut version that comes with it to suite the specific needs of my Thumbnail renderer. The Thumbnail Provider DLL ends up simply calling my custom renderer using command lines like this one:

c:\install\path\msViewer2.exe mymodel.ms3d -thumbnail c:\tempdir\tempfile.tmp -geometry 256x256

The renderer will then output the thumbnail in BMP format as specified from the command line.

Thumbnail Provider

The Thumbnail Provider itself is a DLL plug-in for Windows file explorer. The Windows 7 SDK comes with a Thumbnail Provider template. I used this as starting point to implement my Ms3dThumbnailProvider.

There is a few things you need to do to get that Recipe Thumbnail Provider Sample working:

Now I needed my Thumbnail Provider to work with file name as opposed than with stream as in the SDK sample. To achieve this you need to:

Before you release your Thumbnail Provider you also need to make sure of the following:

Code Samples

We modified the DLL registration code to suite the needs of our specific implementation:

const WCHAR KDwordOne[] = {0x0001};

/*
 * Registry entry description 
 */
struct REGISTRY_ENTRY
   {
   HKEY   hkeyRoot;
   PCWSTR pszKeyName;
   PCWSTR pszValueName;
   DWORD  dwValueType;
   PCWSTR pszData;
   };

/* 
 * Create and set the given registry entry
 * @param The registry entry to register
 */
HRESULT CreateRegKeyAndSetValue(const REGISTRY_ENTRY *pRegistryEntry)
   {
   HKEY hKey;
   HRESULT hr = HRESULT_FROM_WIN32(RegCreateKeyEx(pRegistryEntry->hkeyRoot, pRegistryEntry->pszKeyName,
      0, NULL, REG_OPTION_NON_VOLATILE, KEY_SET_VALUE, NULL, &hKey, NULL));
   if (SUCCEEDED(hr))
      {
      //We support only REG_DWORD and REG_SZ type
      DWORD size;
      BYTE* lpData = (LPBYTE) pRegistryEntry->pszData;
      switch (pRegistryEntry->dwValueType)
         {
         case REG_SZ:
            size = ((DWORD) wcslen(pRegistryEntry->pszData) + 1) * sizeof(WCHAR);
            break;
         case REG_DWORD:
            size = sizeof(DWORD);
            break;
         default:
            return E_INVALIDARG;
         }

      hr = HRESULT_FROM_WIN32(RegSetValueExW(hKey, pRegistryEntry->pszValueName, 0, pRegistryEntry->dwValueType,
         lpData, size ));
      RegCloseKey(hKey);
      }
   return hr;
   }


//
// Registers this COM server
//
STDAPI DllRegisterServer()
{
    HRESULT hr;

    WCHAR szModuleName[MAX_PATH];

    if (!GetModuleFileNameW(g_hInst, szModuleName, ARRAYSIZE(szModuleName)))
    {
        hr = HRESULT_FROM_WIN32(GetLastError());
    }
    else
    {
        // List of registry entries we want to create
        const REGISTRY_ENTRY rgRegistryEntries[] =
        {
            // RootKey            KeyName                                                                ValueName   ValueType   Data
            {HKEY_CLASSES_ROOT,   L"CLSID\\" SZ_CLSID_MS3DTHUMBHANDLER, NULL, REG_SZ, SZ_MS3DTHUMBHANDLER},
            {HKEY_CLASSES_ROOT,   L"CLSID\\" SZ_CLSID_MS3DTHUMBHANDLER L"\\InProcServer32", NULL, REG_SZ, szModuleName},
            {HKEY_CLASSES_ROOT,   L"CLSID\\" SZ_CLSID_MS3DTHUMBHANDLER L"\\InProcServer32", L"ThreadingModel", REG_SZ, L"Apartment"},
            {HKEY_CLASSES_ROOT,   L".ms3d\\ShellEx\\{e357fccd-a995-4576-b01f-234630154e96}",   NULL, REG_SZ, SZ_CLSID_MS3DTHUMBHANDLER},
            //Disable overlay of the MS3D app icon, not actually working?
            {HKEY_CLASSES_ROOT,   L".ms3d", L"TypeOverlay", REG_SZ, L""},
            //Make sure IInitializeWithFile is called
            {HKEY_CLASSES_ROOT,  L"CLSID\\" SZ_CLSID_MS3DTHUMBHANDLER , L"DisableProcessIsolation", REG_DWORD, KDwordOne},
        };

        hr = S_OK;
        for (int i = 0; i < ARRAYSIZE(rgRegistryEntries) && SUCCEEDED(hr); i++)
        {
            hr = CreateRegKeyAndSetValue(&rgRegistryEntries[i]);
        }
    }
    if (SUCCEEDED(hr))
    {
        // This tells the shell to invalidate the thumbnail cache.  This is important because any .ms3d files
        // viewed before registering this handler would otherwise show cached blank thumbnails.
        SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL);
    }
    return hr;
}

//
// Unregisters this COM server
//
STDAPI DllUnregisterServer()
{
    HRESULT hr = S_OK;

   //Leave in the TypeOverlay that's no big deal
    const PCWSTR rgpszKeys[] =
    {
        L"CLSID\\" SZ_CLSID_MS3DTHUMBHANDLER,
        L".ms3d\\ShellEx\\{e357fccd-a995-4576-b01f-234630154e96}"
    };

    // Delete the registry entries
    for (int i = 0; i < ARRAYSIZE(rgpszKeys) && SUCCEEDED(hr); i++)
    {
        hr = HRESULT_FROM_WIN32(RegDeleteTree(HKEY_CLASSES_ROOT, rgpszKeys[i]));
        if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND))
        {
            // If the registry entry has already been deleted, say S_OK.
            hr = S_OK;
        }
    }
    return hr;
}

References