通過COM技術實現Windows Shell編程

Windows環境下,不論是使用Visual C++還是Delphi或是其他一些軟件開發工具開發的應用程序,儘管存在着差別,但有一點是相同的:都是運行於Windows操作系統之下的。在程序開發過程中也經常要在自己的應用程序中加入一些Windows系統本身就有的功能,比如文件的拷貝、刪除、查找以及運行程序等等。而這些功能在Windows操作系統下都是具備的,顯然如果能直接從系統中調用這些功能將不僅僅減少程序的大小和開發人員的工作量,而且由於是直接通過操作系統來完成這些功能,將會大大減小這部分程序出現異常錯誤的概率。Windows系統雖說也存在不少錯誤,但常用功能的錯誤還是比較少的,而且通過補丁程序可以更低限度減少系統錯誤,因此程序員可以將調試檢錯的注意力放在應用程序的其他地方,對於調用系統功能這部分代碼則可以不必投入太大的精力去調試,因爲這部分調試的工作在操作系統發佈的時候就已經由微軟做好了。本文通過外殼編程,實現了搜尋文件、運行程序、控制工具條、最大最小化窗口的功能,程序的運行界面如圖一所示:


圖一、Windows外殼編程序界面效果圖


  一、實現方法

  前面所說的直接使用Windows操作系統部分功能的編程方法就是針對Windows操作系統外殼的編程,可以通過對操作系統提供的幾個編程接口對操作系統的部分功能進行調用,甚至可以按照自己的意圖在應用程序中對部分功能進行修改、擴展。但這方面的資料介紹不是特別多,講的也大都語焉不詳,而且用通常的編程方法去進行外殼編程是非常麻煩的,動輒就要對相關的結構對象進行設置,而這樣的結構裏的數據成員少則十來個多則幾十個,因此配置起來非常煩瑣,下面就以一個比較簡單的外殼操作--拷貝文件進行舉例說明:

……
SHFILEOPSTRUCT FileOp; //外殼的文件操作結構
FileOp.hwnd=m_hWnd; //設置句柄
//設置操作方式,拷貝用FO_COPY,刪除用 FO_DELETE
FileOp.wFunc=FO_COPY;
FileOp.pFrom=m_source; //源文件路徑
FileOp.pTo=m_detect; //目標文件路徑
FileOp.fFlags=FOF_ALLOWUNDO; //允許恢復
FileOp.hNameMappings=NULL;
FileOp.lpszProgressTitle=strTitle; //設置標題
SHFileOperation(&FileOp); //執行外殼拷貝
if(FileOp.fAnyOperationsAborted) //監測有無中止
TRACE("An Operation was aborted!!!/n");
……


  上述代碼實現起來雖然效果還是不錯的,但然實現起來卻是比較麻煩的,這僅僅是一個比較簡單的外殼操作,對於一些比較複雜的外殼操作比如系統托盤、任務條等等的編程,更是尤爲嚴重,而且象此類編程,MFC裏並沒有提供封裝好的程序類庫,提供的只有系統的WinAPI 應用程序接口,因此在程序開發過程中往往會有一種在進行SDK編程的感覺。

  COM (Component Object Model,組件對象模型)是Microsoft創建的一種二進制和網絡標準,也是Microsoft大力推廣並已取得廣泛認可的一種組件標準。在COM標準中,COM對象被很好的封裝起來,客戶無法訪問對象的實現細節,提供給用戶的唯一的訪問途徑是通過COM接口來訪問。對於COM接口有兩方面的含義:首先它是一組可供調用的函數,由此客戶可以讓該對象做某些事情;其次,也是更爲重要的,接口是組件及其客戶程序之間的協議。也就是說接口不但定義了可用什麼函數,也定義了當調用這些函數時對象要做什麼。Windows操作系統本身作爲一個大的COM組件對象,也提供了一些必要的COM接口給客戶程序,因此我們可以通過這些COM接口來直接對Windows外殼進行編程。

  在程序進行正式編寫設計之前有一點是肯定的:程序裏需要用到COM接口,要對COM對象進行操作。因此首先要加入初始化COM和終止COM的代碼。一般是在應用程序類的InitInstance()函數的開始處和返回前添加初始化COM和終止COM代碼的: 

……
CoInitialize(NULL); //初始化COM
……
CoUninitialize(); //終止COM代碼
…… 


  以上兩個函數在MFC程序和非MFC程序中都可以很好的使用。另外,如果程序框架是以MFC爲基礎的,那麼只需簡單的調用AfxOleInit()函數就可以達到同樣的目的。而且不必顯式調用終止COM的代碼。在COM標準中,訪問COM對象的唯一途徑是COM接口,因此在編寫操縱Windows 系統外殼程序首先要得到其提供的COM接口。所用的COM接口是IShellDispatch,它是從IDispatch接口派生來的,在VC安裝目錄的VC98/Include/Exdisp.h頭文件中有定義,下面節選了一些將要用到的接口定義:

……
EXTERN_C const IID IID_IShellDispatch;
#if defined(__cplusplus) && !defined(CINTERFACE)
interface DECLSPEC_UUID("D8F015C0-C278-11CE-A49E-444553540000")
IShellDispatch : public Idispatch
{
 public:
  ……
  virtual HRESULT STDMETHODCALLTYPE MinimizeAll( void) = 0;
  virtual HRESULT STDMETHODCALLTYPE UndoMinimizeALL( void) = 0;
  virtual HRESULT STDMETHODCALLTYPE FileRun( void) = 0;
  virtual HRESULT STDMETHODCALLTYPE CascadeWindows( void) = 0;
  virtual HRESULT STDMETHODCALLTYPE TileVertically( void) = 0;
  virtual HRESULT STDMETHODCALLTYPE TileHorizontally( void) = 0;
  virtual HRESULT STDMETHODCALLTYPE ShutdownWindows( void) = 0;
  virtual HRESULT STDMETHODCALLTYPE Suspend( void) = 0;
  virtual HRESULT STDMETHODCALLTYPE SetTime( void) = 0;
  virtual HRESULT STDMETHODCALLTYPE TrayProperties( void) = 0;
  virtual HRESULT STDMETHODCALLTYPE Help( void) = 0;
  virtual HRESULT STDMETHODCALLTYPE FindFiles( void) = 0;
  virtual HRESULT STDMETHODCALLTYPE FindComputer( void) = 0;
};
……


  該接口在CoCreateInstance()函數創建COM對象時將會得到指向其的指針,通過這個函數客戶程序可以避免顯式同類廠打交道,其實該函數內部也調用了CoGetClassObject()函數來獲取COM對象的類廠,只不過它把通過類廠創建對象的過程封裝起來了,只需用戶指定對象類的CLSID和待輸出的接口指針及接口ID,顯然這樣直接創建COM對象是非常便捷的,在獲取到COM對象指針之後就可以通過這個指針去訪問調用COM對象裏的方法來實現Windows 外殼的種種功能調用了。下面是實現該功能的部分關鍵代碼:

……
HRESULT sc;//返回結果
IShellDispatch *pShellDisp = NULL; //初始化接口指針
//直接創建COM對象
sc = CoCreateInstance( CLSID_Shell,//指定待創建的COM對象標識符
NULL, //指定被聚合時的外部對象的接口指針
CLSCTX_SERVER, //指定組件類別,可以指定進程內組件進程外組件或者進程內控制對象。
IID_IDispatch, //指定接口ID,需要注意的是這裏指的是待
//創建的COM對象的接口ID,而非類廠對象的接口標識符
(LPVOID *) &pShellDisp );//存放函數返回的對象的接口指針
/* 在上述代碼中,CoCreateInstance首先調用CoGetClassObject函數創建類廠對象,然後用得到的類廠對象的接口指針創建真正的COM對象,最後把類廠對象釋放並返回,這樣就很好的把類廠屏蔽起來,使用戶用起來更爲簡單。*/
if( FAILED(sc) )//必須用FAILED 或SUCCECCED來判斷COM對象是否創建成功
 return;
pShellDisp->FindFiles(); //調用COM對象裏的方法
pShellDisp->Release(); //釋放申請到的接口指針
……


  在這裏通過pShellDisp接口指針調用了COM對象的FindFiles()方法去進行查找文件的系統外殼操作。同樣,可以根據實際需要靈活調用響應的方法來執行相應的外殼操作,主要有以下幾個方法:MinimizeAll:所有窗口最小化、UndoMinimizeALL:恢復窗口最小化、   FileRun:開始菜單的"運行…"、CascadeWindows:層疊窗口、TileVertically:垂直平鋪、TileHorizontally:水平平鋪、ShutdownWindows:關閉Windows、Suspend 掛起計算機、SetTime:設定時間、TrayProperties:任務欄屬性、Help Windows:幫助、FindFiles:查找文件、FindComputer:查找計算機等。

  這些接口均在VC安裝目錄的VC98/Include/Exdisp.h頭文件中有定義,可以通過對該文件的查看來編寫響應的外殼操作代碼。

  二、編程步驟

  1、 啓動Visual C++6.0,生成一個Win32應用程序,項目命名爲"Shell";

  2、 添加應用程序圖標資源APP_ICON和對話框資源DLG_MAIN,對話框界面按上圖一設計;

  3、 使用Class Wizard爲對話框上的各個按鈕添加鼠標單擊處理函數;

  4、 添加代碼,編譯運行程序。

  三、程序代碼

/////////////////////////////////////
#define INC_OLE2
#define WIN32_LEAN_AND_MEAN
#define STRICT
#include <windows.h>
#include <windowsx.h>
#include <commctrl.h>
#include <shlguid.h>
#include <exdisp.h>
#include "taskbar.h"
#include "resource.h"
// data
static WNDPROC g_pfnOldProc;
static HWND g_hwndButton=NULL;
static HWND g_hDlg=NULL;
static HINSTANCE g_hInstance;
static HICON g_hIconLarge;
static HICON g_hIconSmall;
// functions
static VOID OnRunPrograms( VOID );
static VOID OnFindFiles( VOID );
static VOID OnMinimizeAll( VOID );
static VOID OnUndoMinimize( VOID );
static VOID OnTaskbarProperties( VOID );
static VOID OnAddTab( HWND );
static VOID OnDeleteTab( VOID );
static VOID OnInitDialog( HWND );
static VOID OnButtonActivation( VOID );
// callbacks
LRESULT CALLBACK APP_DlgProc( HWND, UINT, WPARAM, LPARAM );
LRESULT CALLBACK ButtonProc( HWND, UINT, WPARAM, LPARAM );
INT APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevious,
LPSTR lpsz, INT iCmd )
{
 BOOL b;
 g_hIconLarge = (HICON) LoadImage( hInstance, "APP_ICON", IMAGE_ICON,
GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CXICON), 0 );
 g_hIconSmall = (HICON) LoadImage( hInstance, "APP_ICON", IMAGE_ICON,
GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CXSMICON), 0 );
 // initialize OLE libraries
 CoInitialize(NULL);
 InitCommonControls();
 // run main dialog
 g_hInstance = hInstance;
 b = DialogBox( hInstance, "DLG_MAIN", NULL, (DLGPROC)APP_DlgProc );
 // exit
 DestroyIcon( g_hIconLarge );
 DestroyIcon( g_hIconSmall );
 // free the objects used by ITaskbarList
 DestroyWindow( g_hwndButton );
 OnDeleteTab();
 CoUninitialize();
 return b;
}

LRESULT CALLBACK APP_DlgProc( HWND hDlg, UINT uiMsg, WPARAM wParam, LPARAM lParam )
{
 switch( uiMsg )
 {
  case WM_INITDIALOG:
   OnInitDialog( hDlg );
   break;
  case WM_COMMAND:
   switch( wParam )
   {
    case IDC_FINDFILES:
     OnFindFiles();
     return 1;
    case IDC_RUNPROGRAMS:
     OnRunPrograms();
     return 1;
    case IDC_MINIMIZE:
     OnMinimizeAll();
     return 1;
    case IDC_UNDOMINIMIZE:
     OnUndoMinimize();
     return 1;
    case IDC_PROPERTIES:
     OnTaskbarProperties();
     return 1;
    case IDC_ADDTAB:
     OnAddTab( hDlg );
     return 1;
    case IDC_DELETETAB:
     OnDeleteTab();
     return 1;
    case IDCANCEL:
     EndDialog( hDlg, FALSE );
     return 0;
   }
   break;
  }
  return 0;
 }

 VOID OnFindFiles( VOID )
 {
  HRESULT sc;
  IShellDispatch *pShellDisp = NULL;
  sc = CoCreateInstance( CLSID_Shell, NULL, CLSCTX_SERVER, IID_IDispatch, (LPVOID *) &pShellDisp );
  if( FAILED(sc) )
   return;
  pShellDisp->FindFiles();
  pShellDisp->Release();
  return;
 }

 VOID OnTaskbarProperties( VOID )
 {
  HRESULT sc;
  IShellDispatch *pShellDisp = NULL;
  sc = CoCreateInstance( CLSID_Shell, NULL, CLSCTX_SERVER,IID_IDispatch, (LPVOID *) &pShellDisp );
  if( FAILED(sc) )
   return;
  pShellDisp->TrayProperties();
  pShellDisp->Release();
  return;
 }

 VOID OnRunPrograms( VOID )
 {
  HRESULT sc;
  IShellDispatch *pShellDisp = NULL;
  sc = CoCreateInstance( CLSID_Shell, NULL, CLSCTX_SERVER, IID_IShellDispatch, (LPVOID *) &pShellDisp );
  if( FAILED(sc) )
   return;
  pShellDisp->FileRun();
  pShellDisp->Release();
  return;
 }

 VOID OnMinimizeAll( VOID )
 {
  HRESULT sc;
  IShellDispatch *pShellDisp = NULL;
  sc = CoCreateInstance( CLSID_Shell, NULL, CLSCTX_SERVER,
  IID_IDispatch, (LPVOID *) &pShellDisp );
  if( FAILED(sc) )
   return;
  pShellDisp->MinimizeAll();
  pShellDisp->Release();
  return;
 }

 VOID OnUndoMinimize( VOID )
 {
  HRESULT sc;
  IShellDispatch *pShellDisp = NULL;
  sc = CoCreateInstance( CLSID_Shell, NULL, CLSCTX_SERVER,
  IID_IDispatch, (LPVOID *) &pShellDisp );
  if( FAILED(sc) )
   return;
  pShellDisp->UndoMinimizeALL();
  pShellDisp->Release();
  return;
 }

 VOID OnInitDialog( HWND hDlg )
 {
  // set the icons (T/F as to Large/Small icon)
  g_hDlg = hDlg;
  SendMessage( hDlg, WM_SETICON, FALSE, (LPARAM)g_hIconSmall );
  SendMessage( hDlg, WM_SETICON, TRUE, (LPARAM)g_hIconLarge );
 }

 VOID OnAddTab( HWND hWnd )
 {
  static BOOL g_bFirstTime=TRUE;
  HRESULT sc;
  ITaskbarList *pDisp = NULL;
  sc = CoCreateInstance( CLSID_TaskbarList, NULL, CLSCTX_SERVER, IID_ITaskbarList, (LPVOID *) &pDisp );
  if( FAILED(sc) )
   return;
  // call the first time only
  if( g_bFirstTime )
  {
   g_bFirstTime = FALSE;
   pDisp->HrInit();
   // create a new button window
   g_hwndButton = CreateWindow( "button", "My Button", WS_CLIPSIBLINGS|BS_PUSHBUTTON,0, 0, 58, 14, hWnd, NULL, g_hInstance, NULL );  
   g_pfnOldProc = (WNDPROC) SubclassWindow( g_hwndButton, ButtonProc );
  }
  pDisp->AddTab( g_hwndButton );
  pDisp->Release();
  return;
 }

 VOID OnDeleteTab( VOID )
 {
  HRESULT sc;
  ITaskbarList *pDisp = NULL;
  sc = CoCreateInstance( CLSID_TaskbarList, NULL, CLSCTX_SERVER, IID_ITaskbarList, (LPVOID *) &pDisp );
  if( FAILED(sc) )
   return;
  pDisp->DeleteTab( g_hwndButton );
  pDisp->Release();
  return;
 }

 LRESULT CALLBACK ButtonProc( HWND hwnd, UINT uiMsg, WPARAM wParam, LPARAM lParam )
 {
  switch( uiMsg )
  {
  case WM_NCACTIVATE:
   if( wParam==TRUE )
    OnButtonActivation();
    break;
  }
  return CallWindowProc( g_pfnOldProc, hwnd, uiMsg, wParam, lParam );
 }

 VOID OnButtonActivation( VOID )
 {
  HMENU hmenu;
  RECT r;
  LONG x, y;
  // get some window handles
  HWND h0=FindWindow("Shell_TrayWnd", NULL );
  HWND h1=FindWindowEx(h0,NULL,"RebarWindow32", NULL);
  HWND h2=FindWindowEx(h1,NULL,"MSTaskSwWClass", NULL);
  HWND h3=FindWindowEx(h2,NULL,"SysTabControl32", NULL);
  GetWindowRect( h3, &r );
  // get the currently selected button and
  // create a new popup menu
  hmenu = CreatePopupMenu();
  INT i=TabCtrl_GetCurSel( h3 );
  if( i==-1 )
  {
   AppendMenu( hmenu, MF_STRING, IDC_DELETETAB,"&Close" );
  }
  else
  {
   AppendMenu( hmenu, MF_STRING, IDC_MINIMIZE,"&Minimize All" );
   AppendMenu( hmenu, MF_STRING, IDC_UNDOMINIMIZE,"&Undo Minimize All" );
   AppendMenu( hmenu, MF_SEPARATOR, 0, NULL );
   AppendMenu( hmenu, MF_STRING, IDC_PROPERTIES,"&Taskbar Properties" );
  }
  // set and immediately reset its size to get
  // the current width and height
  LONG l = TabCtrl_SetItemSize( h3, 0, 0 );
  TabCtrl_SetItemSize( h3, LOWORD(l), HIWORD(l) );
  // have the menu to appear just above the button
  if( i==-1 )
  {
   POINT pt;
   GetCursorPos( &pt );
   x = pt.x;
   y = pt.y;
  }
  else
  {
   x = r.left + LOWORD(l)*i+3;
   y = GetSystemMetrics(SM_CYSCREEN)-(HIWORD(l)+1);
  }
  TrackPopupMenu( hmenu, TPM_BOTTOMALIGN, x, y, 0, g_hDlg, 0);
  DestroyMenu( hmenu );
  return;
 }


  四、小結

  本實例介紹了一種利用COM技術實現Windows系統外殼程序的簡便實用的方法,對Windows系統外殼的程序設計和COM程序設計的方法和思想做了闡述。在掌握了本文編程的中心思想前提下,不僅可以對Windows系統外殼進行程序設計,而且對於其他一些提供COM接口的應用程序進行編程,比如可以在自己的應用程序中用類似的方法加入對Office辦公套件的支持等等。因此重點不應放在具體的程序代碼中,而是在於程序的設計思想與方法。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章