[C / C++] Build a Windows Explorer Extension without ATL

Discussion in 'Mixed Languages' started by Michaela Joy, Mar 26, 2015.

  1. Michaela Joy

    Michaela Joy MDL Crazy Lady

    Jul 26, 2012
    4,068
    4,649
    150
    #1 Michaela Joy, Mar 26, 2015
    Last edited by a moderator: Apr 20, 2017
    Hi All,

    In this post, I'm going to show you how to use C to build a Windows Explorer Context Menu handler. We're not going to use ATL, because it is proprietary, and most free compiler products do not support it. To be honest, once you understand the basics of COM programming, you'll realize that you don't need ATL. ATL is a framework. COM was accomplished without ATL. Therefore, ATL is a convenience, not a necessity.

    If you know nothing about COM programming, I suggest you take a look at these articles to get yourself acquainted with the concepts. I'm not going to delve heavily into the theory; I'm going to provide you with a useable framework that will be a great place to start.

    Of course, MSDN has the definitive guide to COM. Everything you could ever need to know.

    You can find their guide here: https://msdn.microsoft.com/en-us/library/dn720897(v=vs.85).aspx

    For those of you who wish to "cut to the chase", there is a very good article from Jeff Glatt showing the basics of COM programming without ATL.

    That article can be found here: http://www.codeproject.com/Articles/13601/COM-in-plain-C

    At this point, I'm going to assume that you looked at the MSDN guide, became overwhelmed by the deluge of information, looked at the article and assumed (As I did) that the article shows you -nothing- about using COM to create a Windows Explorer extension. In fact, it tells you just about -everything- you need to know to create an Explorer Extension.

    So let's break down what Jeff says in His article.

    To create a COM control, You must create a Dynamic Link Library, generate a GUID (Globally Unique Identifier) which Windows uses to load and use your COM component, and write this GUID to the Windows registry. You will need to add registry entries when you register the COM component, and remove them when you unregister it.

    So let's begin with the DLL framework. I'll break this down into digestable chunks.

    Here is main.h. This file contains the GUID definition that is unique to our application. The Strings "SZ_GUID" and "SZ_EXTNAME" are for convenience, and are used in our registration routines.

    Code:
    #ifndef __MAIN_H__
    #define __MAIN_H__
    
    #include <windows.h>
    
    // {8801AD25-F79A-469f-A245-A380AC27AB80}
    
    DEFINE_GUID(CLSID_ShellTest_C,
     0x8801ad25, 0xf79a, 0x469f, 0xa2, 0x45, 0xa3, 0x80, 0xac, 0x27, 0xab, 0x80);
    
    #define SZ_GUID _T("{8801AD25-F79A-469f-A245-A380AC27AB80}")
    #define SZ_EXTNAME _T("ShellTest_C")
    
    #endif // __MAIN_H__
    
    

    DLLMain is the entry point for just about every Windows DLL. It gets called when the DLL is loaded or unloaded.
    There are 4 conditions when DLLMain is called. These conditions are specified in the DWORD parameter called dwReason.

    They are:
    DLL_PROCESS_ATTACH, DLL_PROCESS_DETACH, DLL_THREAD_ATTACH, and DLL_THREAD_DETACH.

    You can read about them Here: https://msdn.microsoft.com/en-us/library/windows/desktop/ms682583(v=vs.85).aspx

    For our purposes, we only need to respond to DLL_PROCESS_ATTACH. We need to pick up the DLL Instance handle for subsequent processing. We don't need to be concerned about the other cases. That is the purpose for the call to DisableThreadLibraryCalls(). This prevents Windows from calling our DLL with DLL_THREAD_ATTACH or DLL_THREAD_DETACH.

    So, we store the DLL Instance handle in a global variable that we can get to later on.

    The next global variable that We have is called "g_uiRefThisDll". This is a reference counter. Our DLL can be unloaded when (and only when) this global variable is zero.

    Of course, we include header files that will make important definitions available to us.

    Code:
    #include <windows.h>
    #include <objbase.h>
    #include <initguid.h>
    #include <shlobj.h>
    #include <tchar.h>
    #include <stdio.h>
    
    #include "main.h"
    #include "regutils.h"
    #include "ClassFactory.h"
    
    HINSTANCE g_hInst;
    UINT g_uiRefThisDll = 0;
    
    BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason,LPVOID lpvReserved)
    {
    if (fdwReason == DLL_PROCESS_ATTACH)
    {
     g_hInst = hinstDLL;
     DisableThreadLibraryCalls((HMODULE)hinstDLL);
    }
    return TRUE;
    }
    
    What follows are the four exported subroutines that Windows will use to manipulate our COM extension. The prefix
    "__declspec(dllexport)" tells the compiler to export this routine when the DLL is compiled. We will (of course) tell the compiler to automatically create a DEF file and an import library.

    DllGetClassObject is called by Windows when we want to create or get a pointer to an IClassFactory struct. We must have one handy, so that Windows can create our extension. IsEqualCLSID checks the passed in CLSID to our CLSID, and provides an interface accordingly.

    Code:
     
    
    __declspec(dllexport) HRESULT WINAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID *ppv)
    {
    HRESULT hr = CLASS_E_CLASSNOTAVAILABLE;
    IClassFactory *pClassFactory = NULL;
    *ppv = NULL;
    if (!IsEqualCLSID(rclsid,&CLSID_ShellTest_C))
       return hr;
    pClassFactory = CClassFactory_Create();
    if (pClassFactory != NULL)
    {
     hr = pClassFactory->lpVtbl->QueryInterface(pClassFactory,riid,ppv);
     pClassFactory->lpVtbl->Release(pClassFactory);
    }
    else
     hr = E_OUTOFMEMORY;
    return hr;
    }
    
    DllCanUnloadNow is called by Windows to see if the COM component can be unloaded. We use our reference counter to accomplish this.

    Code:
    __declspec(dllexport) HRESULT WINAPI DllCanUnloadNow (void)
    {
    return ResultFromScode((g_uiRefThisDll == 0) ? S_OK : S_FALSE);
    }
    
    DllRegisterServer and DllUnregisterServer are called when Windows registers or unregisters the COM component.
    The registration routines are regutils.c. I'll go over that in a bit.

    Code:
    
    __declspec(dllexport) HRESULT WINAPI DllRegisterServer(VOID)
    {
    if (RegisterShellExtension(SZ_EXTNAME,SZ_GUID,_T("ShellTest_C Extension"),g_hInst) == FALSE)
     return E_UNEXPECTED;
    if (RegisterExtensionCategory(SZ_ALLFSOBJSCONTEXTMENUEXT,SZ_GUID,SZ_EXTNAME) != S_OK)
     return E_UNEXPECTED;
    if (RegisterExtensionCategory(SZ_DIRBKGNDCONTEXTMENUEXT,SZ_GUID,SZ_EXTNAME) != S_OK)
     return E_UNEXPECTED;
    if (RegisterExtensionCategory(SZ_DIRCONTEXTMENUEXT,SZ_GUID,SZ_EXTNAME) != S_OK)
     return E_UNEXPECTED;
    return S_OK;
    }
    
    __declspec(dllexport) HRESULT WINAPI DllUnregisterServer(VOID)
    {
    if (UnregisterShellExtension(SZ_EXTNAME,SZ_GUID) == FALSE)
     return E_UNEXPECTED;
    UnRegisterExtensionCategory(SZ_ALLFSOBJSCONTEXTMENUEXT,SZ_GUID,SZ_EXTNAME);
    UnRegisterExtensionCategory(SZ_DIRBKGNDCONTEXTMENUEXT,SZ_GUID,SZ_EXTNAME);
    UnRegisterExtensionCategory(SZ_DIRCONTEXTMENUEXT,SZ_GUID,SZ_EXTNAME);
    return S_OK;
    }
     
    
    Here is regutils.h. This file has the definitions and function prototypes for the registration routines.

    Code:
    #ifndef REGUTILS_H_INCLUDED
    #define REGUTILS_H_INCLUDED
    
    #define SZ_DIRBKGNDCONTEXTMENUEXT _T("Directory\\Background\\shellex\\ContextMenuHandlers")
    #define SZ_DIRCONTEXTMENUEXT  _T("Directory\\shellex\\ContextMenuHandlers")
    #define SZ_DRIVECONTEXTMENUEXT  _T("Drive\\shellex\\ContextMenuHandlers")
    #define SZ_ALLFSOBJSCONTEXTMENUEXT _T("AllFileSystemObjects\\shellex\\ContextMenuHandlers")
    #define SZ_FILECONTEXTMENUEXT  _T("*\\shellex\\ContextMenuHandlers")
    #define SZ_FOLDERCONTEXTMENUEXT  _T("Folder\\shellex\\ContextMenuHandlers")
    
    HRESULT RegisterExtensionCategory(LPCTSTR lpCat,LPCTSTR lpGUID,LPCTSTR lpExtName);
    HRESULT UnRegisterExtensionCategory(LPCTSTR lpCat,LPCTSTR lpGUID,LPCTSTR lpExtName);
    BOOL RegisterShellExtension(LPCTSTR lpExtName,LPCTSTR lpGUID,LPCTSTR lpDesc,HINSTANCE hInstDLL);
    BOOL UnregisterShellExtension(LPCTSTR lpExtName,LPCTSTR lpGUID);
    #endif
    
    
    And now, regutils.c

    Code:
    #include <windows.h>
    #include <stdio.h>
    #include <tchar.h>
    #include "regutils.h"
    
    HRESULT RegisterExtensionCategory(LPCTSTR lpCat,LPCTSTR lpGUID,LPCTSTR lpExtName)
    {
    HRESULT hr = E_UNEXPECTED;
    HKEY hk = NULL;
    TCHAR szCat[MAX_PATH];
    _tcscpy(szCat,lpCat);
    _tcscat(szCat,_T("\\"));
    _tcscat(szCat,lpExtName);
    if (RegCreateKey(HKEY_CLASSES_ROOT,szCat,&hk) == ERROR_SUCCESS)
    {
     if (RegSetValueEx(hk,_T(""),0,REG_SZ,(const BYTE *)lpGUID,(lstrlen(lpGUID) + 1) * sizeof(TCHAR)) == ERROR_SUCCESS)
      hr = S_OK;
     RegCloseKey(hk);
    }
    return hr;
    }
    
    HRESULT UnRegisterExtensionCategory(LPCTSTR lpCat,LPCTSTR lpGUID,LPCTSTR lpExtName)
    {
    HRESULT hr = E_UNEXPECTED;
    HKEY hk = NULL;
    TCHAR szCat[MAX_PATH];
    _tcscpy(szCat,lpCat);
    _tcscat(szCat,_T("\\"));
    _tcscat(szCat,lpExtName);
    LONG lRet = RegOpenKeyEx(HKEY_CLASSES_ROOT,szCat,0,KEY_SET_VALUE,&hk);
    if (lRet != ERROR_SUCCESS)
    {
     if (lRet == ERROR_ACCESS_DENIED)
      return E_UNEXPECTED;
     else if (lRet == ERROR_FILE_NOT_FOUND)
      ;
    }
    if (hk != NULL)
    {
     if (RegDeleteValue(hk,lpGUID) == ERROR_SUCCESS)
      hr = S_OK;
     RegCloseKey(hk);
     RegDeleteKey(HKEY_CLASSES_ROOT,szCat);
    }
    return hr;
    }
    
    BOOL RegisterShellExtension(LPCTSTR lpExtName,LPCTSTR lpGUID,LPCTSTR lpDesc,HINSTANCE hInstDLL)
    {
    TCHAR szFilePath[MAX_PATH],szCLSID[MAX_PATH],szInproc[MAX_PATH];
    HKEY hKeyCLSID = NULL,hkeyInprocServer32 = NULL,hKeyApproved = NULL;
    BOOL rval = FALSE;
    GetModuleFileName(hInstDLL,szFilePath,MAX_PATH);
    _tcscpy(szCLSID,_T("CLSID\\"));
    _tcscat(szCLSID,lpGUID);
    _tcscpy(szInproc,szCLSID);
    _tcscat(szInproc,_T("\\InprocServer32"));
    if (RegCreateKey(HKEY_CLASSES_ROOT,szCLSID,&hKeyCLSID) != ERROR_SUCCESS)
     return rval;
    if (RegSetValueEx(hKeyCLSID,_T(""),0,REG_SZ,(const BYTE *)lpDesc,(lstrlen(lpDesc) + 1) * sizeof(TCHAR)) != ERROR_SUCCESS)
     return rval;
    RegCloseKey(hKeyCLSID);
    if (RegCreateKey(HKEY_CLASSES_ROOT,szInproc,&hkeyInprocServer32) == ERROR_SUCCESS)
    {
    static TCHAR szApartment[] = _T("Apartment");
     if (RegSetValueEx(hkeyInprocServer32,_T(""),0,REG_SZ,(const BYTE *)szFilePath,(lstrlen(szFilePath) + 1) * sizeof(TCHAR)) == ERROR_SUCCESS)
     {
      if (RegSetValueEx(hkeyInprocServer32,_T("ThreadingModel"),0,REG_SZ,(const BYTE *)szApartment,(lstrlen(szApartment) + 1) * sizeof(TCHAR)) == ERROR_SUCCESS)
       rval = TRUE;
     }
     RegCloseKey(hkeyInprocServer32);
    }
    LONG lRet = RegOpenKeyEx(HKEY_LOCAL_MACHINE,_T("Software\\Microsoft\\Windows\\CurrentVersion\\Shell Extensions\\Approved"),0,KEY_SET_VALUE,&hKeyApproved);
    if (lRet == ERROR_ACCESS_DENIED)
     return FALSE;
    else if (lRet == ERROR_FILE_NOT_FOUND)
    {
     // Doesn't exist
     rval = TRUE;
    }
    if (hKeyApproved)
    {
     if (RegSetValueEx(hKeyApproved,lpGUID,0,REG_SZ,(const BYTE *)lpDesc,(lstrlen(lpDesc) + 1) * sizeof(TCHAR)) == ERROR_SUCCESS)
      rval = TRUE;
     else
      rval = FALSE;
    }
    return rval;
    }
    
    BOOL UnregisterShellExtension(LPCTSTR lpExtName,LPCTSTR lpGUID)
    {
    TCHAR szCLSID[MAX_PATH],szInproc[MAX_PATH];
    HKEY hKeyApproved = NULL;
    BOOL rval = FALSE;
    _tcscpy(szCLSID,_T("CLSID\\"));
    _tcscat(szCLSID,lpGUID);
    _tcscpy(szInproc,szCLSID);
    _tcscat(szInproc,_T("\\InprocServer32"));
    RegDeleteKey(HKEY_CLASSES_ROOT,szInproc);
    if (RegDeleteKey(HKEY_CLASSES_ROOT,szCLSID) == ERROR_SUCCESS)
     rval = TRUE;
    LONG lRet = RegOpenKeyEx(HKEY_LOCAL_MACHINE,_T("Software\\Microsoft\\Windows\\CurrentVersion\\Shell Extensions\\Approved"),0,KEY_SET_VALUE,&hKeyApproved);
    if (lRet == ERROR_ACCESS_DENIED)
     rval = FALSE;
    else if (lRet == ERROR_FILE_NOT_FOUND)
     rval = TRUE;
    if (hKeyApproved)
    {
     if (RegDeleteValue(hKeyApproved,lpGUID) != ERROR_SUCCESS)
      rval = FALSE;
     RegCloseKey(hKeyApproved);
    }
    return rval;
    }
     
    
    In my next post, I will explain the Class factory and the actual interface itself.

    :MJ
     
    Stop hovering to collapse... Click to collapse... Hover to expand... Click to expand...
  2. qewlpal

    qewlpal MDL Addicted

    Jun 25, 2010
    575
    2,014
    30
    Thank you so much for sharing this..:)
     
    Stop hovering to collapse... Click to collapse... Hover to expand... Click to expand...
  3. Erik B

    Erik B MDL Member

    Oct 10, 2008
    105
    26
    10
    #3 Erik B, Jul 23, 2015
    Last edited by a moderator: Apr 20, 2017
    Thanks for sharing! Just a reflection of coding taste (don't take it as criticism). :)

    You're using TCHAR, tchar.h macros and UNICODE macros in a classic MS spirit. There's nothing wrong with that IMO, but an alternative would be to use explicit ie the wide versions of both the C library and the WIN32 API. The wide versions are faster if using a lot of WIN32 calls and of course if using COM. I guess most compilers do support tchar.h though.

    Example what I mean of explicit version choice

    Code:
    LSTATUS lRet;
    HKEY hk = nullptr;
    wchar_t szCat[MAX_PATH] = {0};
    
    lRet = RegOpenKeyExW(HKEY_CLASSES_ROOT, szCat, 0, KEY_SET_VALUE, &hk);
    
    I guess if using a lot of web sources, you would want to use the narrow versions and UTF8 conversions.
     
  4. Michaela Joy

    Michaela Joy MDL Crazy Lady

    Jul 26, 2012
    4,068
    4,649
    150
    Hi Eric_B,
    Thanks for your reply. You are right. TCHARs are not the best way to go. wide char is faster and better. But when you're writing prototype code, You go for the easiest path to success.

    Which is what I did. :)

    I hope that people will look at this thread and realize that My example is -not- production code. It's simply a starting point for their own COM controls. Take the code, use it to set up a project in whatever C compiler you have, get it working, and then preen and optimize it.

    Oh, and constructive criticism is always welcome. :)

    :MJ

    P.S. Welcome to MDL. :)
     
    Stop hovering to collapse... Click to collapse... Hover to expand... Click to expand...
  5. Erik B

    Erik B MDL Member

    Oct 10, 2008
    105
    26
    10
    :hug2::biggrin: Hi Michaela and thank you for the kindness! Looking forward to the next post in the series.
     
  6. Michaela Joy

    Michaela Joy MDL Crazy Lady

    Jul 26, 2012
    4,068
    4,649
    150
    Stop hovering to collapse... Click to collapse... Hover to expand... Click to expand...