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:
- Make sure you compile your Thumbnail Provider DLL to match the version of the file explorer you are targeting. Either
Win32 or in my case x64.
- Once compiled you need to register your Thumbnail Provider DLL. This is best done from the command line by running:
regsvr32 MyThumbnailProvider?.dll
- As you are developing it is also a good practice to keep you registry clean by unregistering your DLL as you need running:
regsvr32 MyThumbnailProvider?.dll /u
- Note that
regsvr32.exe exists in both x64 and 32bits version both version have the same file name but live in different directory. Unless you are trying to register 32bits version of a DLL on a x64 Windows installation you should not need to worry about which version your calling.
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:
- Derive your Thumbnail Provider class from
IInitializeWithFile instead of IInitializeWithStream.
- Change the overridden
Initialize function signature accordingly.
- Make sure all reference to
IInitializeWithStream is replaced with IInitializeWithFile taking special care of the one from the QueryInterface method implementation.
- Make sure the registry key
DisableProcessIsolation value is set to one otherwise initialization with file name will never get called.
Before you release your Thumbnail Provider you also need to make sure of the following:
- Generate a CLSID using
Uuidgen.exe -s command.
- If an installer is needed for deployment make sure you use
HKEY_CLASSES_ROOT as registry Root Key instead of HKEY_CURRENT_USER as in the sample otherwise the installer DLL registration will have no effect and your Thumbnail Provider won't be functional.
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