65.9K
CodeProject 正在变化。 阅读更多。
Home

用纯 C++ 编写外壳扩展

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.53/5 (8投票s)

2013年9月28日

CPOL

3分钟阅读

viewsIcon

48297

downloadIcon

1851

本文描述了如何在没有 ATL 的情况下用纯 C++ 编写外壳扩展

引言

Shell 扩展只是具有特定 shell 接口的 COM 组件。CodeProject 上有很多关于如何使用 ATL 编写 shell 扩展的文章。虽然 ATL 是构建 COM 对象的绝佳工具,但我们必须在没有 ATL 或 MFC 的情况下,仅使用纯 C++ 来完成。这样,我们可以更深入地了解 Shell 扩展的工作原理,甚至可以在没有 Microsoft 商业库的情况下构建它。

在本文中,简单的 shell 扩展示例 编写 Shell 扩展的完整白痴指南 - 第一部分 将在没有 ATL 的情况下重写。它的名称是 HelloExtNoATL。  

使用 ATL,大多数流程都由项目或类向导完成。我们甚至不需要编写一行代码来创建 Shell 扩展项目的骨架。只有 COM 对象的方法需要我们自己实现。 

如果没有 ATL,COM 对象的大部分代码都是相同的,但是 IClassFactory 和注册必须在我们自己的代码中完成。  我们可以按以下步骤创建它:  

  1. 添加 GUID
  2. 实现 IClassFactory
  3. 实现 CShellExt 
  4. 实现注册函数

步骤 1:为 COM 对象添加 GUID

全局唯一标识符 (GUID) 对于所有 COM 对象都是强制性的。在 Visual Studio 中,ATL 通过类向导完成。我们可以使用 Visual Studio 中的 guidgen.exe 或一些在线 guid 生成器来为 HelloExtNoATL 创建一个 GUID,而无需类向导的帮助。  

// 
// guid.h
//
// {CBF88FC2-F150-4F29-BC80-CE30EFD1B62C}
DEFINE_GUID(CLSID_HelloExtNoAtl, 0xcbf88fc2, 0xf150, 0x4f29, 0xbc, 0x80, 0xce, 0x30, 0xef, 0xd1, 0xb6, 0x2c);
 
  

步骤 2:实现 IClassFactory   

这是纯 C++ 和 ATL 之间 COM 对象的主要区别之一。我们必须实现我们自己的 ClassFactory。在 ATL 中,它可以通过 IDL 文件自动创建。

在 CClassFactory 中,需要实现接口 IUnknow 和 IClassFactory。因此,可以查询和调用 COM 对象接口方法。可以使用方法 CreateInstance 及其 ID 创建其实例。

 相反,ATL 默认实现了 IUnknown。  不需要更多代码。 

// 
// ClassFactory.h: interface for the CClassFactory class.
//

#if !defined(AFX_CLASSFACTORY_H__3B4B8B30_5B16_4B69_ACC8_25C7259A9F74__INCLUDED_)
#define AFX_CLASSFACTORY_H__3B4B8B30_5B16_4B69_ACC8_25C7259A9F74__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

extern HINSTANCE  g_hInst;
extern UINT       g_DllRefCount;

class CClassFactory : public IClassFactory  
{
protected:
	DWORD m_ObjRefCount;
public:
	CClassFactory();
	virtual ~CClassFactory();

   //IUnknown methods
   STDMETHODIMP QueryInterface(REFIID, LPVOID*);
   STDMETHODIMP_(DWORD) AddRef();
   STDMETHODIMP_(DWORD) Release();

   //IClassFactory methods
   STDMETHODIMP CreateInstance(LPUNKNOWN, REFIID, LPVOID*);
   STDMETHODIMP LockServer(BOOL) { return E_NOTIMPL; };
};

#endif // !defined(AFX_CLASSFACTORY_H__3B4B8B30_5B16_4B69_ACC8_25C7259A9F74__INCLUDED_) 

 
大多数方法都是通用的,并且对于所有 COM 对象类都相同。应该为 HelloExtNoATL 相应地实现方法 QueryInterface 和  CreateInstance。

在 CClassFactory 中,当 ID 等于 IID_IClassFactory 时,this 指针将转换为 IClassFactory *

//
// CClassFactory::QueryInterface
//
STDMETHODIMP CClassFactory::QueryInterface( REFIID riid, LPVOID *ppReturn )
{
	*ppReturn = NULL;

	if( IsEqualIID(riid, IID_IUnknown) )
	    *ppReturn = this;
	else 
	    if( IsEqualIID(riid, IID_IClassFactory) )
		*ppReturn = (IClassFactory*)this;


	if( *ppReturn )
	{
		LPUNKNOWN pUnk = (LPUNKNOWN)(*ppReturn);
		pUnk->AddRef();
		return S_OK;
	}

	return E_NOINTERFACE;
} 

在 CClassFactory::CreateInstance 中,创建 COM 对象 CShellExt,  然后其接口指针通过其 ID 返回。
//
// CClassFactory::CreateInstance
//
STDMETHODIMP CClassFactory::CreateInstance( LPUNKNOWN pUnknown, REFIID riid, LPVOID *ppObject )
{
	*ppObject = NULL;
	if( pUnknown != NULL )
		return CLASS_E_NOAGGREGATION;

	// creates the namespace's main class
	CShellExt *pShellExt = new CShellExt();
	if( NULL==pShellExt ) 
		return E_OUTOFMEMORY;

	// query interface for the return value
	HRESULT hResult = pShellExt->QueryInterface(riid, ppObject);
	pShellExt->Release();
	return hResult;
}  

步骤 3:实现 COM 类

HelloExtNoAtl 只是扩展了 Windows 资源管理器的上下文菜单。IShellExtInit 和 IContextMenu 应在 COM 类中实现。  由于在纯 C++ 中,CShellExt 直接继承 IShellExtInit 和 IContextMenu。大多数代码可以直接从 ATL 示例中复制。

 它还实现了 IUknown,这对于所有 COM 对象都是常见的。  相反,ATL 默认实现了 IUnknown。我们必须为类 CShellExt 自己编写代码。 

// 
// ShellExt.h: interface for the CShellExt class.
//

#if !defined(AFX_SHELLEXT_H__38EFB68B_5591_485A_9E50_409E8CDE10E2__INCLUDED_)
#define AFX_SHELLEXT_H__38EFB68B_5591_485A_9E50_409E8CDE10E2__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

class CShellExt : public IShellExtInit, IContextMenu
{
protected:
	DWORD m_ObjRefCount;
public:
	CShellExt();
	virtual ~CShellExt();

	// IUnknown methods
	STDMETHOD (QueryInterface) (REFIID riid, LPVOID * ppvObj);
	STDMETHOD_ (ULONG, AddRef) (void);
	STDMETHOD_ (ULONG, Release) (void);
	

    // IShellExtInit
    STDMETHODIMP Initialize(LPCITEMIDLIST, LPDATAOBJECT, HKEY);

    // IContextMenu
    STDMETHODIMP GetCommandString(UINT_PTR, UINT, UINT*, LPSTR, UINT);
    STDMETHODIMP InvokeCommand(LPCMINVOKECOMMANDINFO);
    STDMETHODIMP QueryContextMenu(HMENU, UINT, UINT, UINT, UINT);
protected:
    TCHAR m_szFile [MAX_PATH];
};

#endif // !defined(AFX_SHELLEXT_H__38EFB68B_5591_485A_9E50_409E8CDE10E2__INCLUDED_)

步骤 4:注册

在 ATL 中,注册脚本包含在项目中。它可以用于注册和取消注册 shell 扩展。但是,使用纯 C++,我们必须手动实现所有注册表操作。

除了为常见 COM 对象添加注册表条目外,还需要为 shell 扩展创建额外的条目。对于 HelloExtNoAtl,创建了 HKEY_CLASSES_ROOT\Folder\ShellEx\ContextMenuHandlers\HelloExtNoAtl。因为使用它将扩展资源管理器的上下文菜单功能。正如该条目所示,当弹出文件夹的上下文菜单时,将附加一个菜单条目。 

// 
// HelloExtNoAtl.cpp: register and unregister
//
#include <windows.h>
#include <shlobj.h>

#include "HelloExtNoAtl.h"

#include "ClassFactory.h"
#include "Utility.h"

#include <olectl.h>

// data
HINSTANCE   g_hInst;
UINT        g_DllRefCount;

#pragma data_seg(".text")
#define INITGUID
#include <initguid.h>
#include <shlguid.h>
#include "Guid.h"
#pragma data_seg()

BOOL APIENTRY DllMain( HANDLE hModule, 
                       DWORD  ul_reason_for_call, 
                       LPVOID lpReserved
					 )
{
    switch (ul_reason_for_call)
	{
		case DLL_PROCESS_ATTACH:
			g_hInst = (HINSTANCE)hModule;
		case DLL_THREAD_ATTACH:
		case DLL_THREAD_DETACH:
		case DLL_PROCESS_DETACH:
			break;
    }
    return TRUE;
}

/*---------------------------------------------------------------*/
// DllCanUnloadNow()
/*---------------------------------------------------------------*/
STDAPI DllCanUnloadNow( VOID )
{
	return (g_DllRefCount ? S_FALSE : S_OK);
}


/*---------------------------------------------------------------*/
// DllGetClassObject()
/*---------------------------------------------------------------*/
STDAPI DllGetClassObject( REFCLSID rclsid, REFIID riid, LPVOID *ppReturn )
{
	*ppReturn = NULL;

	// do we support the CLSID?
	if( !IsEqualCLSID(rclsid, CLSID_HelloExtNoAtl) )
	   return CLASS_E_CLASSNOTAVAILABLE;
   
	// call the factory
	CClassFactory *pClassFactory = new CClassFactory();
	if( pClassFactory==NULL )
	   return E_OUTOFMEMORY;
   
	// query interface for the a pointer
	HRESULT hResult = pClassFactory->QueryInterface(riid, ppReturn);
	pClassFactory->Release();
	return hResult;
}


/*---------------------------------------------------------------*/
// DllGetRegisterServer()
/*---------------------------------------------------------------*/

typedef struct{
   HKEY  hRootKey;
   LPTSTR lpszSubKey;
   LPTSTR lpszValueName;
   LPTSTR lpszData;
}REGSTRUCT, *LPREGSTRUCT;

STDAPI DllRegisterServer( VOID )
{
	INT i;
	HKEY hKey;
	LRESULT lResult;
	DWORD dwDisp;
	TCHAR szSubKey[MAX_PATH];
	TCHAR szCLSID[MAX_PATH];
	TCHAR szModule[MAX_PATH];
	LPWSTR pwsz;

	// get the CLSID in string form
	StringFromIID( CLSID_HelloExtNoAtl, &pwsz );

	if( pwsz )
	{
		WideCharToLocal( szCLSID, pwsz, ARRAYSIZE(szCLSID) );
        LPMALLOC pMalloc;
        CoGetMalloc(1, &pMalloc);
		if( pMalloc )
		{
	      pMalloc->Free(pwsz);
		  pMalloc->Release();
		}
    }

	// get this DLL's path and file name
	GetModuleFileName( g_hInst, szModule, ARRAYSIZE(szModule) );

	// CLSID entries
	REGSTRUCT ClsidEntries[] = {  
		HKEY_CLASSES_ROOT, TEXT("CLSID\\%s"),                  NULL,                   TEXT("HelloExtNoAtl"),
		HKEY_CLASSES_ROOT, TEXT("CLSID\\%s"),                  TEXT("InfoTip"),        TEXT("HelloExtNoAtl."),
		HKEY_CLASSES_ROOT, TEXT("CLSID\\%s\\InprocServer32"),  NULL,                   TEXT("%s"),
        HKEY_CLASSES_ROOT, TEXT("CLSID\\%s\\InprocServer32"),  TEXT("ThreadingModel"), TEXT("Apartment"),
        HKEY_CLASSES_ROOT, TEXT("CLSID\\%s\\DefaultIcon"),     NULL,                   TEXT("%s,0"),
        NULL,              NULL,                               NULL,                   NULL};

	for( i=0; ClsidEntries[i].hRootKey; i++ )
    {
		// create the sub key string.
		wsprintf( szSubKey, ClsidEntries[i].lpszSubKey, szCLSID );
        lResult = RegCreateKeyEx(  ClsidEntries[i].hRootKey,
                              szSubKey,
                              0,
                              NULL,
                              REG_OPTION_NON_VOLATILE,
                              KEY_WRITE,
                              NULL,
                              &hKey,
                              &dwDisp );
   
		if( lResult==NOERROR )
		{
			 TCHAR szData[MAX_PATH];
			 wsprintf(szData, ClsidEntries[i].lpszData, szModule);
			 lResult = RegSetValueEx( hKey,
                            ClsidEntries[i].lpszValueName,
                            0,
                            REG_SZ,
                            (LPBYTE)szData,
                            lstrlen(szData) + 1);
      
			 RegCloseKey(hKey);
		}
		else
			return SELFREG_E_CLASS;
    }

	// Context Menu
	lstrcpy( szSubKey, TEXT("Folder\\ShellEx\\ContextMenuHandlers\\HelloExtNoAtl"));
	lResult = RegCreateKeyEx(HKEY_CLASSES_ROOT,
		szSubKey,
		0,
		NULL,
		REG_OPTION_NON_VOLATILE,
		KEY_WRITE,
		NULL,
		&hKey,
		&dwDisp);

	if( lResult==NOERROR )
	{
		TCHAR szData[MAX_PATH];
		lstrcpy(szData, szCLSID);
		lResult = RegSetValueEx( hKey,
			NULL,
			0,
			REG_SZ,
			(LPBYTE)szData,
			lstrlen(szData) + 1);

		RegCloseKey(hKey);
	}
	else
		return SELFREG_E_CLASS;

   // register the extension as approved by NT
   OSVERSIONINFO  osvi;
   osvi.dwOSVersionInfoSize = sizeof(osvi);
   GetVersionEx( &osvi );

   if( VER_PLATFORM_WIN32_NT == osvi.dwPlatformId )
   {
	   lstrcpy( szSubKey, TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Shell Extensions\\Approved"));
	   lResult = RegCreateKeyEx(  HKEY_LOCAL_MACHINE,
                              szSubKey,
                              0,
                              NULL,
                              REG_OPTION_NON_VOLATILE,
                              KEY_WRITE,
                              NULL,
                              &hKey,
                              &dwDisp);

       if( lResult==NOERROR )
       {
		   TCHAR szData[MAX_PATH];
	       lstrcpy(szData, TEXT("HelloExtNoAtl"));
           lResult = RegSetValueEx( hKey,
                                 szCLSID,
                                 0,
                                 REG_SZ,
                                 (LPBYTE)szData,
                                 lstrlen(szData) + 1);
      
	      RegCloseKey(hKey);
      }
	  else
		  return SELFREG_E_CLASS;
   }

   return S_OK;
}

/*---------------------------------------------------------------*/
// DllUnregisterServer()
/*---------------------------------------------------------------*/
STDAPI DllUnregisterServer( VOID )
{
	INT i;
	//HKEY hKey;
	LRESULT lResult;
	//DWORD dwDisp;
	TCHAR szSubKey[MAX_PATH];
	TCHAR szCLSID[MAX_PATH];
	TCHAR szModule[MAX_PATH];
	LPWSTR pwsz;

	// get the CLSID in string form
	StringFromIID( CLSID_HelloExtNoAtl, &pwsz );

	if( pwsz )
	{
		WideCharToLocal( szCLSID, pwsz, ARRAYSIZE(szCLSID) );
        LPMALLOC pMalloc;
        CoGetMalloc(1, &pMalloc);
		if( pMalloc )
		{
	      pMalloc->Free(pwsz);
		  pMalloc->Release();
		}
    }

	// get this DLL's path and file name
	GetModuleFileName( g_hInst, szModule, ARRAYSIZE(szModule) );

	// CLSID entries
	REGSTRUCT ClsidEntries[] = {  
		HKEY_CLASSES_ROOT, TEXT("CLSID\\%s"),                  NULL,                   TEXT("HelloExtNoAtl"),
		HKEY_CLASSES_ROOT, TEXT("CLSID\\%s"),                  TEXT("InfoTip"),        TEXT("HelloExtNoAtl."),
		HKEY_CLASSES_ROOT, TEXT("CLSID\\%s\\InprocServer32"),  NULL,                   TEXT("%s"),
        HKEY_CLASSES_ROOT, TEXT("CLSID\\%s\\InprocServer32"),  TEXT("ThreadingModel"), TEXT("Apartment"),
        HKEY_CLASSES_ROOT, TEXT("CLSID\\%s\\DefaultIcon"),     NULL,                   TEXT("%s,0"),
        NULL,              NULL,                               NULL,                   NULL};

	for( i=0; ClsidEntries[i].hRootKey; i++ )
        {
		// create the sub key string.
		wsprintf( szSubKey, ClsidEntries[i].lpszSubKey, szCLSID );
		lResult = RegDeleteKey(ClsidEntries[i].hRootKey, szSubKey);
        }


	// Context Menu
	lstrcpy( szSubKey, TEXT("Folder\\ShellEx\\ContextMenuHandlers\\HelloExtNoAtl"));
	lResult = RegDeleteKey(HKEY_CLASSES_ROOT, szSubKey);


   // register the extension as approved by NT
   OSVERSIONINFO  osvi;
   osvi.dwOSVersionInfoSize = sizeof(osvi);
   GetVersionEx( &osvi );

   if( VER_PLATFORM_WIN32_NT == osvi.dwPlatformId )
   {
	   lResult = RegDeleteKey(HKEY_LOCAL_MACHINE, szSubKey);
   }

	return S_OK;
}   

 

安装

对于所有实现了标准注册/取消注册方法的 COM 对象,安装步骤都是相同的。只需使用 regsvr32.exe 安装或卸载它。  

这一切看起来像什么?  

 

 

 

结论

在阅读本文并尝试该代码后,我们可以发现使用 ATL 和纯 C++ 构建 Shell 扩展的主要区别。您还可以阅读 CodeProject 上有关使用纯 C 或 C++ 创建 COM 对象的文章。

 

参考文献

纯 C 中的 COM 

编写外壳扩展的 Complete Idiot's Guide - 第 I 部分

历史

  2013 年 10 月 28 日:文章首次发布。


© . All rights reserved.