diff --git a/dlls/shdocvw/Makefile.in b/dlls/shdocvw/Makefile.in index 660e2825d3c..ee9723a53fb 100644 --- a/dlls/shdocvw/Makefile.in +++ b/dlls/shdocvw/Makefile.in @@ -5,7 +5,8 @@ SRCDIR = @srcdir@ VPATH = @srcdir@ MODULE = shdocvw.dll IMPORTLIB = libshdocvw.$(IMPLIBEXT) -IMPORTS = urlmon ole32 user32 advapi32 kernel32 +IMPORTS = user32 advapi32 kernel32 +DELAYIMPORTS = urlmon ole32 oleaut32 EXTRALIBS = -luuid C_SRCS = \ @@ -17,6 +18,7 @@ C_SRCS = \ persist.c \ regsvr.c \ shdocvw_main.c \ + shlinstobj.c \ webbrowser.c RC_SRCS = shdocvw.rc diff --git a/dlls/shdocvw/shdocvw.h b/dlls/shdocvw/shdocvw.h index 19d40fb7aa1..fb225a3decc 100644 --- a/dlls/shdocvw/shdocvw.h +++ b/dlls/shdocvw/shdocvw.h @@ -48,6 +48,11 @@ typedef struct extern IClassFactoryImpl SHDOCVW_ClassFactory; +/********************************************************************** + * Shell Instance Objects + */ +extern HRESULT SHDOCVW_GetShellInstanceObjectClassObject(REFCLSID rclsid, + REFIID riid, LPVOID *ppvClassObj); /********************************************************************** * WebBrowser declaration for SHDOCVW.DLL diff --git a/dlls/shdocvw/shdocvw_main.c b/dlls/shdocvw/shdocvw_main.c index 3e3ca9a77d9..4df0ead11b1 100644 --- a/dlls/shdocvw/shdocvw_main.c +++ b/dlls/shdocvw/shdocvw_main.c @@ -490,7 +490,8 @@ HRESULT WINAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID *ppv) return S_OK; } - return CLASS_E_CLASSNOTAVAILABLE; + /* As a last resort, figure if the CLSID belongs to a 'Shell Instance Object' */ + return SHDOCVW_GetShellInstanceObjectClassObject(rclsid, riid, ppv); } /*********************************************************************** diff --git a/dlls/shdocvw/shlinstobj.c b/dlls/shdocvw/shlinstobj.c new file mode 100644 index 00000000000..19ba3b84fae --- /dev/null +++ b/dlls/shdocvw/shlinstobj.c @@ -0,0 +1,421 @@ +/* + * Shell Instance Objects - Add hot water and stir until dissolved. + * + * Copyright 2005 Michael Jung + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* 'Shell Instance Objects' allow you to add a node to the shell namespace + * (typically a shortcut to some location in the filesystem), just by setting + * some registry entries. This feature was introduced with win2k. Please + * search for 'Shell Instance Objects' on MSDN to get more information. */ + +#include + +#define COBJMACROS +#define COM_NO_WINDOWS_H + +#include "windef.h" +#include "winbase.h" +#include "winreg.h" +#include "objbase.h" +#include "oleauto.h" + +#include "shdocvw.h" + +#include "wine/unicode.h" +#include "wine/debug.h" + +WINE_DEFAULT_DEBUG_CHANNEL(shdocvw); + +#define ADJUST_THIS(c,m,p) ((c*)(((long)p)-(long)&(((c*)0)->lp##m##Vtbl))) +#define STATIC_CAST(i,p) ((i*)&p->lp##i##Vtbl) +#define CHARS_IN_GUID 39 + +/****************************************************************************** + * RegistryPropertyBag + * + * Gives access to a registry key's values via the IPropertyBag interface. + */ +typedef struct _RegistryPropertyBag { + const IPropertyBagVtbl *lpIPropertyBagVtbl; + LONG m_cRef; + HKEY m_hInitPropertyBagKey; +} RegistryPropertyBag; + +static void RegistryPropertyBag_Destroy(RegistryPropertyBag *This) { + TRACE("This=%p)\n", This); + + RegCloseKey(This->m_hInitPropertyBagKey); + HeapFree(GetProcessHeap(), 0, This); +} + +static HRESULT WINAPI RegistryPropertyBag_IPropertyBag_QueryInterface(IPropertyBag *iface, + REFIID riid, void **ppv) +{ + RegistryPropertyBag *This = ADJUST_THIS(RegistryPropertyBag, IPropertyBag, iface); + + TRACE("(iface=%p, riid=%s, ppv=%p)\n", iface, debugstr_guid(riid), ppv); + + if (!ppv) + return E_INVALIDARG; + + if (IsEqualIID(&IID_IUnknown, riid) || IsEqualIID(&IID_IPropertyBag, riid)) { + *ppv = STATIC_CAST(IPropertyBag, This); + } else { + *ppv = NULL; + return E_NOINTERFACE; + } + + IUnknown_AddRef((IUnknown*)*ppv); + return S_OK; +} + +static ULONG WINAPI RegistryPropertyBag_IPropertyBag_AddRef(IPropertyBag *iface) { + RegistryPropertyBag *This = ADJUST_THIS(RegistryPropertyBag, IPropertyBag, iface); + ULONG cRef; + + TRACE("(iface=%p)\n", iface); + + cRef = InterlockedIncrement(&This->m_cRef); + + if (cRef == 1) + SHDOCVW_LockModule(); + + return cRef; +} + +static ULONG WINAPI RegistryPropertyBag_IPropertyBag_Release(IPropertyBag *iface) { + RegistryPropertyBag *This = ADJUST_THIS(RegistryPropertyBag, IPropertyBag, iface); + ULONG cRef; + + TRACE("(iface=%p)\n", iface); + + cRef = InterlockedDecrement(&This->m_cRef); + + if (cRef == 0) { + RegistryPropertyBag_Destroy(This); + SHDOCVW_UnlockModule(); + } + + return cRef; +} + +static HRESULT WINAPI RegistryPropertyBag_IPropertyBag_Read(IPropertyBag *iface, + LPCOLESTR pwszPropName, VARIANT *pVar, IErrorLog *pErrorLog) +{ + RegistryPropertyBag *This = ADJUST_THIS(RegistryPropertyBag, IPropertyBag, iface); + WCHAR *pwszValue; + DWORD dwType, cbData; + LONG res; + VARTYPE vtDst = V_VT(pVar); + HRESULT hr = S_OK; + + TRACE("(iface=%p, pwszPropName=%s, pVar=%p, pErrorLog=%p)\n", iface, debugstr_w(pwszPropName), + pVar, pErrorLog); + + res = RegQueryValueExW(This->m_hInitPropertyBagKey, pwszPropName, NULL, &dwType, NULL, &cbData); + if (res != ERROR_SUCCESS) + return E_INVALIDARG; + + pwszValue = HeapAlloc(GetProcessHeap(), 0, cbData); + if (!pwszValue) + return E_OUTOFMEMORY; + + res = RegQueryValueExW(This->m_hInitPropertyBagKey, pwszPropName, NULL, &dwType, + (LPBYTE)pwszValue, &cbData); + if (res != ERROR_SUCCESS) { + HeapFree(GetProcessHeap(), 0, pwszValue); + return E_INVALIDARG; + } + + V_VT(pVar) = VT_BSTR; + V_BSTR(pVar) = SysAllocString(pwszValue); + HeapFree(GetProcessHeap(), 0, pwszValue); + + if (vtDst != VT_BSTR) { + hr = VariantChangeTypeEx(pVar, pVar, LOCALE_SYSTEM_DEFAULT, 0, vtDst); + if (FAILED(hr)) + SysFreeString(V_BSTR(pVar)); + } + + return hr; +} + +static HRESULT WINAPI RegistryPropertyBag_IPropertyBag_Write(IPropertyBag *iface, + LPCOLESTR pwszPropName, VARIANT *pVar) +{ + FIXME("(iface=%p, pwszPropName=%s, pVar=%p) stub\n", iface, debugstr_w(pwszPropName), pVar); + return E_NOTIMPL; +} + +static const IPropertyBagVtbl RegistryPropertyBag_IPropertyBagVtbl = { + RegistryPropertyBag_IPropertyBag_QueryInterface, + RegistryPropertyBag_IPropertyBag_AddRef, + RegistryPropertyBag_IPropertyBag_Release, + RegistryPropertyBag_IPropertyBag_Read, + RegistryPropertyBag_IPropertyBag_Write +}; + +HRESULT RegistryPropertyBag_Constructor(HKEY hInitPropertyBagKey, REFIID riid, LPVOID *ppvObject) { + HRESULT hr = E_FAIL; + RegistryPropertyBag *pRegistryPropertyBag; + + TRACE("(hInitPropertyBagKey=%p, riid=%s, ppvObject=%p)\n", hInitPropertyBagKey, + debugstr_guid(riid), ppvObject); + + pRegistryPropertyBag = HeapAlloc(GetProcessHeap(), 0, sizeof(RegistryPropertyBag)); + if (pRegistryPropertyBag) { + pRegistryPropertyBag->lpIPropertyBagVtbl = &RegistryPropertyBag_IPropertyBagVtbl; + pRegistryPropertyBag->m_cRef = 0; + pRegistryPropertyBag->m_hInitPropertyBagKey = hInitPropertyBagKey; + + /* The clasping AddRef/Release is for the case that QueryInterface fails, which will result + * in a reference count of 0 in the Release call, which will result in object destruction.*/ + IPropertyBag_AddRef(STATIC_CAST(IPropertyBag, pRegistryPropertyBag)); + hr = IPropertyBag_QueryInterface(STATIC_CAST(IPropertyBag, pRegistryPropertyBag), riid, ppvObject); + IPropertyBag_Release(STATIC_CAST(IPropertyBag, pRegistryPropertyBag)); + } + + return hr; +} + +/****************************************************************************** + * InstanceObjectFactory + * Builds Instance Objects and asks them to initialize themselves based on the + * values of a PropertyBag. + */ +typedef struct _InstanceObjectFactory { + const IClassFactoryVtbl *lpIClassFactoryVtbl; + LONG m_cRef; + CLSID m_clsidInstance; /* CLSID of the objects to create. */ + IPropertyBag *m_pPropertyBag; /* PropertyBag to initialize those objects. */ +} InstanceObjectFactory; + +static void InstanceObjectFactory_Destroy(InstanceObjectFactory *This) { + IPropertyBag_Release(This->m_pPropertyBag); + HeapFree(GetProcessHeap(), 0, This); +} + +static HRESULT WINAPI InstanceObjectFactory_IClassFactory_QueryInterface(IClassFactory *iface, + REFIID riid, LPVOID* ppv) +{ + InstanceObjectFactory *This = ADJUST_THIS(InstanceObjectFactory, IClassFactory, iface); + + TRACE("iface=%p, riid=%s, ppv=%p)\n", iface, debugstr_guid(riid), ppv); + + if (!ppv) + return E_INVALIDARG; + + if (IsEqualIID(&IID_IUnknown, riid) || IsEqualIID(&IID_IClassFactory, riid)) { + *ppv = STATIC_CAST(IClassFactory, This); + } else { + *ppv = NULL; + return E_NOINTERFACE; + } + + IUnknown_AddRef((IUnknown*)*ppv); + return S_OK; +} + +static ULONG WINAPI InstanceObjectFactory_IClassFactory_AddRef(IClassFactory *iface) +{ + InstanceObjectFactory *This = ADJUST_THIS(InstanceObjectFactory, IClassFactory, iface); + ULONG cRef; + + TRACE("(iface=%p)\n", iface); + + cRef = InterlockedIncrement(&This->m_cRef); + + if (cRef == 1) + IClassFactory_LockServer(iface, TRUE); + + return cRef; +} + +static ULONG WINAPI InstanceObjectFactory_IClassFactory_Release(IClassFactory *iface) +{ + InstanceObjectFactory *This = ADJUST_THIS(InstanceObjectFactory, IClassFactory, iface); + ULONG cRef; + + TRACE("(iface=%p)\n", iface); + + cRef = InterlockedDecrement(&This->m_cRef); + + if (cRef == 0) { + IClassFactory_LockServer(iface, FALSE); + InstanceObjectFactory_Destroy(This); + } + + return cRef; +} + +static HRESULT WINAPI InstanceObjectFactory_IClassFactory_CreateInstance(IClassFactory *iface, + IUnknown *pUnkOuter, REFIID riid, LPVOID *ppvObj) +{ + InstanceObjectFactory *This = ADJUST_THIS(InstanceObjectFactory, IClassFactory, iface); + IPersistPropertyBag *pPersistPropertyBag; + HRESULT hr; + + TRACE("(pUnkOuter=%p, riid=%s, ppvObj=%p)\n", pUnkOuter, debugstr_guid(riid), ppvObj); + + hr = CoCreateInstance(&This->m_clsidInstance, NULL, CLSCTX_INPROC_SERVER, + &IID_IPersistPropertyBag, (LPVOID*)&pPersistPropertyBag); + if (FAILED(hr)) { + TRACE("Failed to create instance of %s. hr = %08lx\n", + debugstr_guid(&This->m_clsidInstance), hr); + return hr; + } + + hr = IPersistPropertyBag_Load(pPersistPropertyBag, This->m_pPropertyBag, NULL); + if (FAILED(hr)) { + TRACE("Failed to initialize object from ProperyBag: hr = %08lx\n", hr); + IPersistPropertyBag_Release(pPersistPropertyBag); + return hr; + } + + hr = IPersistPropertyBag_QueryInterface(pPersistPropertyBag, riid, ppvObj); + IPersistPropertyBag_Release(pPersistPropertyBag); + + return hr; +} + +static HRESULT WINAPI InstanceObjectFactory_IClassFactory_LockServer(IClassFactory *iface, + BOOL fLock) +{ + TRACE("(iface=%p, fLock=%d) stub\n", iface, fLock); + + if (fLock) + SHDOCVW_LockModule(); + else + SHDOCVW_UnlockModule(); + + return S_OK; +} + +static const IClassFactoryVtbl InstanceObjectFactory_IClassFactoryVtbl = { + InstanceObjectFactory_IClassFactory_QueryInterface, + InstanceObjectFactory_IClassFactory_AddRef, + InstanceObjectFactory_IClassFactory_Release, + InstanceObjectFactory_IClassFactory_CreateInstance, + InstanceObjectFactory_IClassFactory_LockServer +}; + +HRESULT InstanceObjectFactory_Constructor(REFCLSID rclsid, IPropertyBag *pPropertyBag, REFIID riid, + LPVOID *ppvObject) +{ + InstanceObjectFactory *pInstanceObjectFactory; + HRESULT hr = E_FAIL; + + TRACE("(RegistryPropertyBag=%p, riid=%s, ppvObject=%p)\n", pPropertyBag, + debugstr_guid(riid), ppvObject); + + pInstanceObjectFactory = HeapAlloc(GetProcessHeap(), 0, sizeof(InstanceObjectFactory)); + if (pInstanceObjectFactory) { + pInstanceObjectFactory->lpIClassFactoryVtbl = &InstanceObjectFactory_IClassFactoryVtbl; + pInstanceObjectFactory->m_cRef = 0; + memcpy(&pInstanceObjectFactory->m_clsidInstance, rclsid, sizeof(CLSID)); + pInstanceObjectFactory->m_pPropertyBag = pPropertyBag; + IPropertyBag_AddRef(pPropertyBag); + + IClassFactory_AddRef(STATIC_CAST(IClassFactory, pInstanceObjectFactory)); + hr = IClassFactory_QueryInterface(STATIC_CAST(IClassFactory, pInstanceObjectFactory), + riid, ppvObject); + IClassFactory_Release(STATIC_CAST(IClassFactory, pInstanceObjectFactory)); + } + + return hr; +} + +/****************************************************************************** + * SHDOCVW_GetShellInstanceObjectClassObject [Internal] + * + * Figure if there is a 'Shell Instance Object' conformant registry entry for + * the given CLSID and if so create and return a corresponding ClassObject. + * + * PARAMS + * rclsid [I] CLSID of the 'Shell Instance Object'. + * riid [I] Desired interface. Only IClassFactory supported. + * ppvClassObj [O] The corresponding ClassObject. + * + * RETURNS + * Success: S_OK, + * Failure: CLASS_E_CLASSNOTAVAILABLE + */ +HRESULT SHDOCVW_GetShellInstanceObjectClassObject(REFCLSID rclsid, REFIID riid, + LPVOID *ppvClassObj) +{ + WCHAR wszInstanceKey[] = { 'C','L','S','I','D','\\','{','0','0','0','0','0','0','0','0','-', + '0','0','0','0','-','0','0','0','0','-','0','0','0','0','-','0','0','0','0','0','0','0','0', + '0','0','0','0','}','\\','I','n','s','t','a','n','c','e', 0 }; + const WCHAR wszCLSID[] = { 'C','L','S','I','D',0 }; + const WCHAR wszInitPropertyBag[] = + { 'I','n','i','t','P','r','o','p','e','r','t','y','B','a','g',0 }; + WCHAR wszCLSIDInstance[CHARS_IN_GUID]; + CLSID clsidInstance; + HKEY hInstanceKey, hInitPropertyBagKey; + DWORD dwType, cbBytes = sizeof(wszCLSIDInstance); + IPropertyBag *pInitPropertyBag; + HRESULT hr; + LONG res; + + TRACE("(rclsid=%s, riid=%s, ppvClassObject=%p)\n", debugstr_guid(rclsid), debugstr_guid(riid), + ppvClassObj); + + /* Figure if there is an 'Instance' subkey for the given CLSID and aquire a handle. */ + if (!StringFromGUID2(rclsid, wszInstanceKey + 6, CHARS_IN_GUID) || + !(wszInstanceKey[5+CHARS_IN_GUID]='\\') || /* Repair the null-termination. */ + ERROR_SUCCESS != RegOpenKeyExW(HKEY_CLASSES_ROOT, wszInstanceKey, 0, KEY_READ, &hInstanceKey)) + { + /* If there is no 'Instance' subkey, then it's not a Shell Instance Object. */ + return CLASS_E_CLASSNOTAVAILABLE; + } + + if (RegQueryValueExW(hInstanceKey, wszCLSID, NULL, &dwType, (LPBYTE)wszCLSIDInstance, &cbBytes) + != ERROR_SUCCESS || FAILED(CLSIDFromString(wszCLSIDInstance, &clsidInstance))) + { + /* 'Instance' should have a 'CLSID' value with a well-formed clsid-string. */ + FIXME("Failed to infer instance CLSID! %s\n", debugstr_w(wszCLSIDInstance)); + RegCloseKey(hInstanceKey); + return CLASS_E_CLASSNOTAVAILABLE; + } + + /* Try to open the 'InitPropertyBag' subkey. */ + res = RegOpenKeyExW(hInstanceKey, wszInitPropertyBag, 0, KEY_READ, &hInitPropertyBagKey); + RegCloseKey(hInstanceKey); + if (res != ERROR_SUCCESS) { + /* Besides 'InitPropertyBag's, shell instance objects might be initialized by streams. + * So this case might not be an error. */ + TRACE("No InitPropertyBag key found!\n"); + return CLASS_E_CLASSNOTAVAILABLE; + } + + /* If the construction succeeds, the new RegistryPropertyBag is responsible for closing + * hInitProperyBagKey. */ + hr = RegistryPropertyBag_Constructor(hInitPropertyBagKey, &IID_IPropertyBag, + (LPVOID*)&pInitPropertyBag); + if (FAILED(hr)) { + RegCloseKey(hInitPropertyBagKey); + return hr; + } + + /* Construct an Instance Object Factory, which creates objects of class 'clsidInstance' + * and asks them to initialize themselves with the help of the 'pInitiPropertyBag' */ + hr = InstanceObjectFactory_Constructor(&clsidInstance, pInitPropertyBag, riid, ppvClassObj); + IPropertyBag_Release(pInitPropertyBag); /* The factory will hold a reference the bag. */ + + return hr; +} diff --git a/dlls/shdocvw/tests/shortcut.c b/dlls/shdocvw/tests/shortcut.c index e82b1b64e96..073d62d3ae8 100644 --- a/dlls/shdocvw/tests/shortcut.c +++ b/dlls/shdocvw/tests/shortcut.c @@ -1,5 +1,5 @@ /* - * Unit tests to document shdocvw's CLSID_FolderShortcut related functionality + * Unit tests to document shdocvw's 'Shell Instance Objects' features * * Copyright 2005 Michael Jung * @@ -27,7 +27,10 @@ * Technologies". This mechanism would be cool for wine, since we could * map Gnome's virtual devices to FolderShortcuts and have them appear in the * file dialogs. These unit tests are meant to document how this mechanism - * works on windows. */ + * works on windows. + * + * Search MSDN for "Creating Shell Extensions with Shell Instance Objects" for + * more documentation.*/ #include @@ -153,13 +156,15 @@ static void unregister_keys(HKEY hRootKey, const struct registry_key *keys, unsi RegDeleteKeyA(hRootKey, keys[iKey].szName); } } - + static void test_ShortcutFolder() { LPSHELLFOLDER pDesktopFolder, pWineTestFolder; - IPersist *pWineTestPersist; - LPITEMIDLIST pidlWineTestFolder; + IPersistFolder3 *pWineTestPersistFolder; + LPITEMIDLIST pidlWineTestFolder, pidlCurFolder; HRESULT hr; CLSID clsid; + const CLSID CLSID_WineTest = + { 0x9b352ebf, 0x2765, 0x45c1, { 0xb4, 0xc6, 0x85, 0xcc, 0x7f, 0x7a, 0xbc, 0x64 } }; WCHAR wszWineTestFolder[] = { ':',':','{','9','B','3','5','2','E','B','F','-','2','7','6','5','-','4','5','C','1','-', 'B','4','C','6','-','8','5','C','C','7','F','7','A','B','C','6','4','}',0 }; @@ -187,20 +192,27 @@ static void test_ShortcutFolder() { (LPVOID*)&pWineTestFolder); IShellFolder_Release(pDesktopFolder); ILFree(pidlWineTestFolder); - todo_wine { ok (SUCCEEDED(hr), "IShellFolder::BindToObject(WineTestFolder) failed! hr = %08lx\n", hr); } + ok (SUCCEEDED(hr), "IShellFolder::BindToObject(WineTestFolder) failed! hr = %08lx\n", hr); if (FAILED(hr)) goto cleanup; - hr = IShellFolder_QueryInterface(pWineTestFolder, &IID_IPersist, (LPVOID*)&pWineTestPersist); - ok (SUCCEEDED(hr), "IShellFolder::QueryInterface(IPersist) failed! hr = %08lx\n", hr); + hr = IShellFolder_QueryInterface(pWineTestFolder, &IID_IPersistFolder3, (LPVOID*)&pWineTestPersistFolder); + ok (SUCCEEDED(hr), "IShellFolder::QueryInterface(IPersistFolder3) failed! hr = %08lx\n", hr); IShellFolder_Release(pWineTestFolder); if (FAILED(hr)) goto cleanup; /* The resulting folder object has the FolderShortcut CLSID, instead of it's own. */ - hr = IPersist_GetClassID(pWineTestPersist, &clsid); + hr = IPersistFolder3_GetClassID(pWineTestPersistFolder, &clsid); ok (SUCCEEDED(hr), "IPersist::GetClassID failed! hr = %08lx\n", hr); ok (IsEqualCLSID(&CLSID_FolderShortcut, &clsid), "GetClassId returned wrong CLSID!\n"); + + pidlCurFolder = (LPITEMIDLIST)0xdeadbeef; + hr = IPersistFolder3_GetCurFolder(pWineTestPersistFolder, &pidlCurFolder); + ok (SUCCEEDED(hr), "IPersistFolder3::GetCurFolder failed! hr = %08lx\n", hr); + ok (pidlCurFolder->mkid.cb == 20 && ((LPSHITEMID)((BYTE*)pidlCurFolder+20))->cb == 0 && + IsEqualCLSID(&CLSID_WineTest, (REFCLSID)((LPBYTE)pidlCurFolder+4)), + "GetCurFolder returned unexpected pidl!\n"); - IPersist_Release(pWineTestPersist); + IPersistFolder3_Release(pWineTestPersistFolder); cleanup: unregister_keys(HKEY_CLASSES_ROOT, HKEY_CLASSES_ROOT_keys, 1);