Windows SDK編程——啓動窗口

原文來自:http://hi.baidu.com/combojiang/item/a972f9149e7ceffcdceeca0d

windows sdk編程系列文章 ---- 啓動畫面

上一章我們學習了位圖的使用.在這一章我們要用 上帝賦予我們的創造力來融會貫通上一章我們學到的知識.那就是研究如何用位圖來創建啓動畫面. 
理論:     首先,我們先要搞清楚什麼是啓動畫面.舉個簡單的例子:我們啓動某些作 的專業一點的程序時(比如Netscape,Adobe Acrobat等)會先跳出一個啓動畫面.上面通常有一 些版權信息,版本號等.與此同時,程序後臺正做着一些程序的加載或初始化工作.這個啓動畫面有別於 一般的窗口.它沒有標題欄,沒有系統菜單,也沒有邊框.只有一張位圖在屏幕上顯示一會兒,然後消失 .在這一章我們來試試自己做一個. 
     第一步你可能會想到把要顯示的位圖包含到資源文件中去.但是這樣做 有一個缺點.你的程序只在啓動的時候顯示這張位圖,可是它卻至始至終存在於你的內存中,直到你 把程序關掉.這不能不說是對內存的極大浪費.好辦法是:創建一個"資源"DLL(動態連接庫)來包含位圖 和它獨特的顯示代碼.這樣,你就可以在想顯示啓動畫面的時候加載他,用完了就卸載它.所以,我們的 程序需要2個模塊:主程序和啓動畫面DLL.我們要把位圖放到這個DLL的資源中去. 
     基本步驟如下:
  1. 把位圖作爲一個位圖資源放到DLL中去.
  2. 主程序調用 LoadLibrary 把 dll 加載到內存中去.
  3. DLL 被調用後,它會建立一個定時器用於管理啓動畫面顯示的時間.然後,註冊並創建一個沒有 標題和邊框的窗口.同時在窗口的客戶區顯示位圖.
  4. 等啓動畫面的顯示時間長度到達你預先的設定值,啓動畫面消失,控制權回到主程序手中.
  5. 主程序調用 FreeLibrary 從內存中卸載 DLL .然後,做它該做的事去.
     下面我們來研究細節部分加載/卸載 DLL你可以用 LoadLibrary 靈活的加載一個 DLL ,它的格式如下:
HMODULE LoadLibrary( LPCTSTR lpFileName );
它只有一個參數: 你想要加載的 DLL 的名稱所在的地址.調用成功返回指向該DLL模塊的句柄,反之返回NULL. 要卸載 DLL, 則調用 FreeLibrary:
BOOL FreeLibrary( HMODULE hModule );
它也只有一個參數: 你想要卸載的 DLL 模塊的句柄(通常就是上面那個函數返回的啦).怎樣使用定時器首先, 你要用 SetTimer 創建一個定時器:
UINT_PTR SetTimer(      
    HWND hWnd,      UINT_PTR nIDEvent,      UINT uElapse,      TIMERPROC lpTimerFunc );

hWnd接受這個定時器消息的窗口的句柄.如果,你的定時器不需要窗口接受它的消息,你也可以 用NULL作爲參數
nIDEvent 定時器的 ID 值. 由你自己定義. 
uElapse 定時器定的時間.以ms(千分之一秒)爲單位. 
lpTimerFunc 處理該定時器消息的函數所在的地址.如果你用NULL作爲該參數,那麼定時器的消息會被送給 hWnd 參數所指定的窗口.

SetTimer 如果成功則返回定時器的 ID 否則返回 NULL. 所以最好不要把定時器的ID設爲0

你可以用2種方法創建定時器:
  • 如果你有一個窗口並且定時器把消息傳給這個窗口.那麼你需要把所有的4個參數都傳送給 Settimer 函數 (lpTimerFunc參數必須爲NULL).
  • 如果你沒有窗口或者你不想讓窗口處理定時器的消息,那麼你必須在窗口句柄中傳送一個NULL.同時你要指定 用於處理定時器消息的函數的地址.
在這個例子中我們要使用第一種方法. 
當你設定的時間到了, 與定時器相連的窗口會收到 WM_TIMER 消息.例如,你指定 uElapse 的值爲 1000, 你的窗口每 過一秒都會收到 WM_TIMER 消息.等到你再也不需要這個定時器了,就用 KillTimer 來去除定時器.
BOOL KillTimer( HWND hWnd,      UINT_PTR uIDEvent );
例子: 見光盤FirstWindow24
;----------------------------------------------------------------------- 
;                         主程序 
;----------------------------------------------------------------------- 
#include "windows.h"
#include "tchar.h"
TCHAR ClassName[] = _T("SplashDemoClass");
TCHAR Libname[] = _T("Splash.dll");
TCHAR AppName[] = _T("Splash Screen Example");

LRESULT CALLBACK WindowProc(          HWND hwnd,
    UINT uMsg,
    WPARAM wParam,
    LPARAM lParam
)
{
    switch(uMsg)
    {
    case WM_DESTROY:
        PostQuitMessage(NULL);
        break;
    
    default:
        return DefWindowProc(hwnd,uMsg,wParam,lParam);
    }

    return 0;
}
int WINAPI WinMain(          HINSTANCE hInstance,
    HINSTANCE hPrevInstance,
    LPSTR lpCmdLine,
    int nCmdShow
)
{
    WNDCLASSEX wc;
    MSG msg;
    HWND hWnd;
    HMODULE hMod = LoadLibrary(Libname);
    if(hMod)
      FreeLibrary(hMod);

    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = WindowProc;
    wc.cbClsExtra = NULL;
    wc.cbWndExtra = NULL;
    wc.hInstance = hInstance;
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wc.lpszClassName = ClassName;
    wc.lpszMenuName = NULL;
    wc.hIcon = wc.hIconSm = LoadIcon(NULL,IDI_APPLICATION);
    wc.hCursor = LoadCursor(NULL,IDC_ARROW);
    RegisterClassEx(&wc);
    hWnd = CreateWindowEx(NULL,ClassName,AppName,WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,CW_USEDEFAULT,
        CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,hInstance,NULL);
    
    ShowWindow(hWnd,SW_SHOWNORMAL);
    while(GetMessage(&msg,NULL,0,0))
    {
         TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return msg.wParam;
    
}
;-------------------------------------------------------------------- 
;                         位圖 DLL 
;-------------------------------------------------------------------- 
#include "windows.h"
#include "tchar.h"

TCHAR BitmapName[] = _T("MySplashBMP");
TCHAR ClassName[] = _T("SplashWndClass");
HBITMAP hBitmap;
DWORD TimerID;
HINSTANCE g_hInstance;

LRESULT CALLBACK WindowProc(          HWND hwnd,
    UINT uMsg,
    WPARAM wParam,
    LPARAM lParam
)
{
    PAINTSTRUCT ps;
    HDC hdc;
    HDC hMemoryDC;
    HBITMAP hOldBmp;
    BITMAP bitmap;
    DWORD DlgHeight;
    DWORD DlgWidth;
    RECT DlgRect;
    RECT DesktopRect;

    switch(uMsg)
    {
    case WM_DESTROY:
        if(hBitmap)
            DeleteObject(hBitmap);
        PostQuitMessage(NULL);
        break;
    case WM_CREATE:
        GetWindowRect(hwnd,&DlgRect);
        GetWindowRect(GetDesktopWindow(),&DesktopRect);
        DlgHeight = DlgRect.bottom - DlgRect.top;
        DlgWidth = DlgRect.right - DlgRect.left;
        MoveWindow(hwnd,(DesktopRect.right - DlgWidth)/2,(DesktopRect.bottom - DlgHeight)/2,DlgWidth,DlgHeight,FALSE);
        hBitmap = LoadBitmap(g_hInstance,BitmapName);
        TimerID = SetTimer(hwnd,1,2000,NULL);
        break;
    case WM_LBUTTONDOWN:
        DestroyWindow(hwnd);
        break;
    case WM_PAINT:
        hdc = BeginPaint(hwnd,&ps);
        hMemoryDC = CreateCompatibleDC(hdc);
        hOldBmp = (HBITMAP)SelectObject(hMemoryDC,hBitmap);
        GetObject(hBitmap,sizeof(BITMAP),&bitmap);
        StretchBlt(hdc,0,0,250,250,hMemoryDC,0,0,bitmap.bmWidth,bitmap.bmHeight,SRCCOPY);
        SelectObject(hMemoryDC,hOldBmp);
        DeleteDC(hMemoryDC);
        EndPaint(hwnd,&ps);
        break;
    case WM_TIMER:
        SendMessage(hwnd,WM_LBUTTONDOWN,NULL,NULL);
        KillTimer(hwnd,TimerID);
        break;
    default:
        return DefWindowProc(hwnd,uMsg,wParam,lParam);
    }

    return 0;
}
DWORD ShowBitMap()
{
    WNDCLASSEX wc;
    MSG msg;
    HWND hWnd;

    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = WindowProc;
    wc.cbClsExtra = NULL;
    wc.cbWndExtra = NULL;
    wc.hInstance = g_hInstance;
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wc.lpszMenuName = NULL;
    wc.lpszClassName = ClassName;
    wc.hIcon = wc.hIconSm = LoadIcon(NULL,IDI_APPLICATION);
    wc.hCursor = LoadCursor(NULL,IDC_ARROW);
    RegisterClassEx(&wc);
    hWnd = CreateWindowEx(NULL,ClassName,NULL,WS_POPUP,CW_USEDEFAULT,CW_USEDEFAULT,
        250,250,NULL,NULL,g_hInstance,NULL);
    
    ShowWindow(hWnd,SW_SHOWNORMAL);
    while(GetMessage(&msg,NULL,0,0))
    {
         TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return msg.wParam;
    
}

BOOL WINAPI DllMain(
    HINSTANCE hModule,
    DWORD dwReason,
    LPVOID lpvReserved
    )
{
    if(dwReason == DLL_PROCESS_ATTACH)
    {
        g_hInstance = hModule;
        ShowBitMap();
    }
    return TRUE;
}
分析:我們首先要再主程序中檢驗這段代碼.
    HMODULE hMod = LoadLibrary(Libname);
   if(hMod)
      FreeLibrary(hMod);

我們調用 LoadLibrary 讀入名稱爲 "splash.dll" 的 DLL. 然後, 用 FreeLibrary 卸載. 一直到 DLL 完成初始化, LoadLibrary纔會返回. 主程序的任務到此爲止. 更有趣的部分再 DLL裏.

   if(dwReason == DLL_PROCESS_ATTACH)
   {
       g_hInstance = hModule;
       ShowBitMap();
   }

DLL 被加載後, Windows 調用它的有 DLL_PROCESS_ATTACH 標記的入口函數. 我們借這個機會顯示啓動畫面. 首先,我們 保存 DLL 事例的句柄以供將來使用. 然後, 調用一個叫 ShowBitMap 的函數進行真正的工作. ShowBitMap 註冊一個窗口, 創建這個窗口和顯示它.就像我們以前創建窗口一樣. 有趣的是這個 CreateWindowEx 調用:

           hWnd = CreateWindowEx(NULL,ClassName,NULL,WS_POPUP,CW_USEDEFAULT,CW_USEDEFAULT,
       250,250,NULL,NULL,g_hInstance,NULL);

注意, 這裏的窗口風格僅僅使用了 WS_POPUP . 所以窗口即沒有標題欄,也沒有邊界. 我們同時也限定窗口的寬高爲 250x250個像素.現在窗口創建好了. 在 WM_CREATE 的消息處理代碼裏我們把這個窗口移到屏幕的中央.代碼如下:

   case WM_CREATE:
       GetWindowRect(hwnd,&DlgRect);
       GetWindowRect(GetDesktopWindow(),&DesktopRect);
       DlgHeight = DlgRect.bottom - DlgRect.top;
       DlgWidth = DlgRect.right - DlgRect.left;
       MoveWindow(hwnd,(DesktopRect.right - DlgWidth)/2,(DesktopRect.bottom - DlgHeight)/2,DlgWidth,DlgHeight,FALSE);
      

它先找到桌面和窗口的大小. 然後,計算出一個窗口左上角的座標. 使這個窗口能位於屏幕中央.

       hBitmap = LoadBitmap(g_hInstance,BitmapName);
       TimerID = SetTimer(hwnd,1,2000,NULL);
       break;

下一步,它用 LoadBitmap 從資源中讀入位圖並且創建一個定時器.定時器的 ID 爲 1 時間間隔爲 2 秒. 定時器 將每 2 秒 向窗口發送 WM_TIMER 消息.

     case WM_PAINT:
       hdc = BeginPaint(hwnd,&ps);
       hMemoryDC = CreateCompatibleDC(hdc);
       hOldBmp = (HBITMAP)SelectObject(hMemoryDC,hBitmap);
       GetObject(hBitmap,sizeof(BITMAP),&bitmap);
       StretchBlt(hdc,0,0,250,250,hMemoryDC,0,0,bitmap.bmWidth,bitmap.bmHeight,SRCCOPY);
       SelectObject(hMemoryDC,hOldBmp);
       DeleteDC(hMemoryDC);
       EndPaint(hwnd,&ps);
       break;

當窗口收到 WM_PAINT 消息, 它創建一個內存DC( 在win32編程中你會經常遇到DC這個詞. 它是 Device Context 的縮寫, 官方譯爲"設備描述表". 如果你研究過vc, 你應該對它不陌生. 不過如果你不明白它是什麼 也不要緊. 你可以把它看作一個句柄. 就是某個設備或某塊內存的名稱.),然後把位圖選進內存DC. 再用 GetObject 函數獲得 位圖的尺寸, 然後用 StretchBlt 把位圖顯示在窗口上. StretchBlt的作用和 BitBlt 一樣,但它可以拉伸或壓縮位圖到我們 希望的大小. 在這裏我們希望位圖能適合窗口的大小,所以我們 StretchBlt 代替 BitBlt. 之後我們刪除內存DC.

    case WM_LBUTTONDOWN:
       DestroyWindow(hwnd);
       break;

如果你的程序的使用者每次都要看到啓動畫面消失才能用, 他們一定會厭煩. 我們可以爲用戶提供多一種選擇. 當他單擊啓動畫面, 它就會消失. 這就是爲什麼我們要在DLL裏處理 WM_LBUTTONDOWN 消息. 收到這個消息後立即就用 DestroyWindow 關掉窗口.

    case WM_TIMER:
       SendMessage(hwnd,WM_LBUTTONDOWN,NULL,NULL);
       KillTimer(hwnd,TimerID);
       break;

如果用戶選擇等待, 那麼啓動畫面會在定時器到了指定的時間後消失. (在本例中, 是 2 秒). 我們可以通過處理 WM_TIMER 消息達到這一目的. 在收到這一消息後,我們可以對窗口傳送 WM_LBUTTONDOWN 消息來關掉窗口. 這是爲了避免 代碼重複. 現在, 我們不再需要這個定時器了,所以我們用 KillTimer 刪除它. 
窗口關閉後,DLL 把控制權還給主程序.


根據原文自學:

VS2012下新建工程,Win32 Application,先建DLL模塊:沒有選擇空工程,將自動生成的dllmain.cpp中的入口函數DllMain註釋掉,把原文的dll模塊代碼複製粘貼,如下:

// dllmain.cpp : Defines the entry point for the DLL application.
#include "stdafx.h"

#include "windows.h"
#include "tchar.h"
#include "resource.h"

#define IDB_Bitmap 3
//TCHAR BitmapName[] = _T("MySplashBMP.bmp");
TCHAR ClassName[] = _T("SplashWndClass");
HBITMAP hBitmap;
DWORD TimerID;
HINSTANCE g_hInstance;


LRESULT CALLBACK WindowProc(          HWND hwnd,
    UINT uMsg,
    WPARAM wParam,
    LPARAM lParam
)
{
    PAINTSTRUCT ps;
    HDC hdc;
    HDC hMemoryDC;
    HBITMAP hOldBmp;
    BITMAP bitmap;
    DWORD DlgHeight;
    DWORD DlgWidth;
    RECT DlgRect;
    RECT DesktopRect;

    switch(uMsg)
    {
    case WM_DESTROY:
        if(hBitmap)
            DeleteObject(hBitmap);
        PostQuitMessage(NULL);
        break;
    case WM_CREATE:
        GetWindowRect(hwnd,&DlgRect);
        GetWindowRect(GetDesktopWindow(),&DesktopRect);
        DlgHeight = DlgRect.bottom - DlgRect.top;
        DlgWidth = DlgRect.right - DlgRect.left;
        MoveWindow(hwnd,(DesktopRect.right - DlgWidth)/2,(DesktopRect.bottom - DlgHeight)/2,DlgWidth,DlgHeight,FALSE);
        
		//hBitmap = LoadBitmap(g_hInstance,MAKEINTRESOURCE(IDB_Bitmap));
		//hBitmap = LoadBitmap(g_hInstance,BitmapName);
		hBitmap = (HBITMAP)LoadImage(g_hInstance,MAKEINTRESOURCE(IDB_Bitmap),IMAGE_BITMAP,0,0,LR_DEFAULTSIZE);
		//有些爲.bmp,但是實際可能不是bmp,用windows畫圖打開再另存爲bmp纔可以
		//hBitmap = (HBITMAP)LoadImage(g_hInstance,_T("zy.jpg"),IMAGE_BITMAP,0,0,LR_LOADFROMFILE);jpg格式不行,好像只可以是bmp
		if (!hBitmap)   //排查錯誤時用的,檢查程序是否執行到這一步,最後發現是LoadBitmap不成功,於是乎就找到“程序運行正確,
			//也成功調用dll,程序也正常運行不報錯,但就是沒有看到啓動界面的圖片”的原因
		{
			MessageBox(NULL,_T("load fail!"),NULL,MB_OK);
		}

        TimerID = SetTimer(hwnd,1,5000,NULL);
        break;
    case WM_LBUTTONDOWN:
        DestroyWindow(hwnd);
        break;
    case WM_PAINT:
        hdc = BeginPaint(hwnd,&ps);
        hMemoryDC = CreateCompatibleDC(hdc);
        hOldBmp = (HBITMAP)SelectObject(hMemoryDC,hBitmap);
        GetObject(hBitmap,sizeof(BITMAP),&bitmap);

		//MessageBox(NULL,_T("at DLLMain before show"),NULL,MB_OK);排查錯誤時用的,檢查程序是否執行到這一步
		
		//BitBlt(hdc,0,0,250,250,hMemoryDC,bitmap.bmWidth,bitmap.bmHeight,SRCCOPY);
        StretchBlt(hdc,0,0,bitmap.bmWidth,bitmap.bmHeight,hMemoryDC,0,0,bitmap.bmWidth,bitmap.bmHeight,SRCCOPY);
        SelectObject(hMemoryDC,hOldBmp);
        DeleteDC(hMemoryDC);
        EndPaint(hwnd,&ps);
        break;
    case WM_TIMER:
        SendMessage(hwnd,WM_LBUTTONDOWN,NULL,NULL);
        KillTimer(hwnd,TimerID);
        break;
    default:
        return DefWindowProc(hwnd,uMsg,wParam,lParam);
    }

    return 0;
}
DWORD ShowBitMap()
{
    WNDCLASSEX wc;
    MSG msg;
    HWND hWnd;

    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = WindowProc;
    wc.cbClsExtra = NULL;
    wc.cbWndExtra = NULL;
    wc.hInstance = g_hInstance;
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wc.lpszMenuName = NULL;
    wc.lpszClassName = ClassName;
    wc.hIcon = wc.hIconSm = LoadIcon(NULL,IDI_APPLICATION);
    wc.hCursor = LoadCursor(NULL,IDC_ARROW);
    RegisterClassEx(&wc);
    hWnd = CreateWindowEx(NULL,ClassName,NULL,WS_POPUP,CW_USEDEFAULT,CW_USEDEFAULT,
        899,505,NULL,NULL,g_hInstance,NULL);  //899,505根據圖片尺寸自己設定的
    
    ShowWindow(hWnd,SW_SHOWNORMAL);
    while(GetMessage(&msg,NULL,0,0))
    {
         TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return msg.wParam;
    
}

BOOL WINAPI DllMain(
    HINSTANCE hModule, //HINSTANCE(此處也可爲HMODULE)在win32下與HMODULE是相同的東西,
	//在Win32下還存在主要是因爲Win16 程序使用HINSTANCE來區別任務。
    DWORD dwReason,
    LPVOID lpvReserved
    )
{
    if(dwReason == DLL_PROCESS_ATTACH)
    {
        g_hInstance = hModule;
        ShowBitMap();
    }
    return TRUE;
}

/*BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
					 )
{
	switch (ul_reason_for_call)
	{
	case DLL_PROCESS_ATTACH:
	case DLL_THREAD_ATTACH:
	case DLL_THREAD_DETACH:
	case DLL_PROCESS_DETACH:
		break;
	}
	return TRUE;
}*/
第二個新建工程爲主程序,選擇的空工程,新建cpp文件,複製粘貼原文代碼,調用成功,啓動界面也成功顯示。如下圖:


最開始的時候出現的狀況是:dll工程成功生成,主程序成功運行,但是就是不顯示啓動界面的圖片。通過MessageBox一步步找原因,先確定是否調用了DLL;再確認是否執行了DLL中的WM_PAINT消息;再確認是否成功執行了LoadBitmap函數。最後確認LoadBitmap處出問題了。(可以嘗試用Assert)。

還有從網上找的bmp格式的圖片可能不是真正bmp格式的,要經過其他軟件(如微軟的畫圖)另存爲。

初學者的編程總是出錯,然後艱難地查找。在不解中求解。慢慢積累!


發佈了28 篇原創文章 · 獲贊 7 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章