在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
#include
#include
#include
#include
#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辦公套件的支援等等。因此重點不應放在具體的程式碼中,而是在於程式的設計思想與方法。