淺談Windows API程式設計

iDotNetSpace發表於2008-09-11

 WinSDK是程式設計中的傳統難點,曾經聽有一個技術不是很好的朋友亂說什麼給你API誰都會用,其實並非那麼簡單,個人寫的WinAPI程式也不少了,其實之所以難就難在每個呼叫的API都包含著Windows這個作業系統的潛規則或者是windows內部的執行機制。

  首先來談談控制程式碼,初學習WinSDK的朋友剛看到這個詞頭大了吧?其實我也是了,我們來看看programming windows裡面是怎麼說的,一個控制程式碼僅僅是用來識別某些事情的數字。它唯一的標識這當前的一個例項。這樣說確實不容易懂。那麼我們這麼看,比如你開啟windows自帶的計算器。你多開啟幾次是不是桌面上出現了很多個計算器呢?你使用其中一個計算器的時候當你按下等於按鈕的時候運算結果是否會出現在其他的計算機結果欄裡?不會,那windows怎麼知道讓結果出現在哪裡呢?這就是控制程式碼的作用了,控制程式碼唯一的標識著一個程式,你開啟的每一個視窗(計算器)都有一個不同的控制程式碼你你每一步操作都是指定了在某個控制程式碼下的,所以,他不會出錯。而且你開啟的每一個計算機都共享著同樣的程式碼和記憶體。通過控制程式碼系統會把所需的資源充分的呼叫到當前的某個程式自己的資料區。

  不僅是視窗,各種選單,GDI物件都有自己的控制程式碼,獲取控制程式碼的手段也是多重多樣,不過當然是通過呼叫API函式實現了,如:

  MFC中的hHandle = GetSafeHandle();

  API程式設計中的hBrush = GetStorkObject(BLACK_BRUSH);

  很多操作都需要將控制程式碼新增到引數列表中,當你沒有直接定義控制程式碼變數的時候可能要記憶很多API的返回型別來間接獲取。如:

      hPen = SelectObject(hdc,GetStockObject(&logicpen));
  // SelectObject()這個函式在設定本裝置描述表下的GDI物件時會返回設定前的GDI物件控制程式碼
  MoveToEx(hdc, pt1.x, pt1.y, &apt);
  LineTo(hdc, pt2.x,pt2.y);
  SelectObject(hdc,hPen);


  完成選擇自定義的GDI物件的操作。控制程式碼的種類很多,掌握一種的使用方法所有的不學自通,WinAPI程式設計永遠伴隨的元素中控制程式碼是其中之一。非常重要。由於是淺談,所以就說到這裡了.

  接下來是windows下的訊息對映機制了,呵呵,視窗過程,剛學的朋友難理解吧?WinSDK程式設計基於C,但是和C的理念有著完全的不同,這中間的不同,在我看來最多的也就是來自於這個訊息對映,後面什麼吹的很炫的Hook技術,木馬技術,鍵盤截獲,都是來自於特殊訊息的捕捉,對映自定義的特殊訊息來實現的(當然和我接下來談的稍微有點不同)。

  首先我們應該先明白訊息和事件的區別,Windows是訊息驅動的作業系統,這裡的訊息的產生來自於某個例項化的物件上使用者的操作,來自控制元件,選單,或者是系統本身產生的,而事件是靠訊息觸發的,但這也不是絕對的。可以用一個簡單的例子去解釋,我這裡越寫越覺得自己難表達清楚,就比如這麼一個例子:“某男殺人這條訊息導致被槍斃這個事件”不過最重要的區別是在訊息產生後並不會被直接處理,而是先插入windows系統的訊息佇列,然後系統判斷此訊息產生於哪個程式,送入此程式的訊息迴圈,由LRSULT CALLBACK winprc(hwnd , uint,wParam,lParam)處理。而事件是作業系統處理訊息的過程中反饋的結果。

  使用者操作-> 產生訊息->傳送系統->系統判斷來源->發給相應的視窗過程或者其他Callback函式->訊息處理->等待下一條訊息的產生

  以上為訊息迴圈整個過程。

      LRSULT CALLBACK winprc(hwnd , uint,wParam,lParam);
  int WINAPI WinMain(…)
  {
  MSG msg;
  RegisterClass(…); // 註冊視窗類
  CreateWindow(…); // 建立視窗
  ShowWindow(…); // 顯示視窗
  UpdateWindow(…);
  While(GetMessage(&msg,…)){ // 訊息迴圈
  TranslateMessage(…);
  DispatchMessage(…);
  }
  LRSULT CALLBACK winprc(hwnd , uint,wParam,lParam);
  //視窗過程函式,用於對映switch語句中各個需要被處理的訊息
  {
  While((UINT)message)
  {
  Switch(message)
  Case…
  Case…
  ………
  Default……….
  }
  }


  以上是最基本的WinAPi程式設計的程式碼結構。其實這裡面最重要的結構莫過於while(GetMessage(&msg))和Winproc這個函式,這也是傳統的C程式導向程式設計的區別,win程式設計總等著特定事件觸發對應的訊息對映函式來完成程式碼功能,並不是一條程式碼從頭走到尾。關於特殊訊息的對映,這裡不談,這裡僅是個入門指引。

  最後談一點就是重繪問題。其實在我看來這個東西更多是屬於GDI程式設計裡面的東西,說起來其實難度不大,但是處理起來確實是個難點。先拿剛才的程式碼來說吧。先新增一條關於WM_LBUTTONDOWN的訊息對映:

      Static int apt[2];
  case WM_LBUTTONDOWN:
  hdc = GetDC(hwnd);
  apt[1].x = LOWORD (lParam);
  apt[1].y = HIWORD (lParam);
  hPen = CreatePen(BLACK_PEN,3,RGB(125,125,125));
  SelectObject(hdc,hPen);
  MoveToEx(hdc,apt[0].x,apt[0].y,NULL);
  LineTo(hdc,apt[1].x,apt[1].y);
  apt[0].x = apt[1].x;
  apt[0].y = apt[1].y;
  DeleteObject(hPen);
  ReleaseDC(hwnd,hdc);
  return 0;


  這段程式碼實現一個簡單的畫線功能,當你在你的客戶區胡點一通滑鼠後試著拖動一下視窗大小,或者將其最小化或者被其他視窗覆蓋一下你都會發現你原來畫的線沒了,可是其他視窗為什麼被覆蓋了以後再彈出視窗還會有原來的東西呢?那就是重繪,要重新繪製整個客戶區(準確的說是失效的矩形),以上說的操作都會導致你的客戶區失效,這時會產生重繪訊息WM_PAINT,我們要想儲存這些線那麼我們就必須儲存這些你用滑鼠左鍵點過的點。當然這是重繪技術中最簡單的,當你的客戶區上是一個複雜的畫面的話,就不僅僅需要儲存點,還有各種形狀的圖形,顏色等等……這裡給大家一段我自己寫的程式碼來實現以上的WM_LBUTTONDOWN訊息對映來產生的點。通過單連結串列來動態新增點來實現重繪。

      case WM_PAINT:
  hdc = BeginPaint(hwnd,&ps);
  TextOut(hdc,cxClient/6,cyClient/6,TEXT("圖形重繪"),strlen("圖形重繪"));
  ReDrawLines(&MyList,hdc);
  EndPaint(hwnd,&ps);
  return 0;
  case WM_LBUTTONDOWN:
  hdc = GetDC(hwnd);
  apt[1].x = LOWORD (lParam);
  apt[1].y = HIWORD (lParam);
  hPen = CreatePen(BLACK_PEN,2,RGB(125,0,0));
  SelectObject(hdc,hPen);
  MoveToEx(hdc,apt[0].x,apt[0].y,NULL);
  LineTo(hdc,apt[1].x,apt[1].y);
  MyList.pCurrent->x = apt[0].x;
  MyList.pCurrent->y = apt[0].y;
  MyList.pCurrent->pNext->x = apt[1].x;
  MyList.pCurrent->pNext->y = apt[1].y;
  MyList.m_iCounter = MyList.m_iCounter+2;
  MyList.pCurrent = MyList.pCurrent->pNext->pNext;
  apt[0].x = apt[1].x;
  apt[0].y = apt[1].y;
  DeleteObject(hPen);
  ReleaseDC(hwnd,hdc);
  return 0;


  其中的重繪函式程式碼如下:

      void ReDrawLines(LinkList* pLinkList,HDC hdc)
  {
  pMyPoint p = pLinkList->pHead;
  int iSaver =pLinkList->m_iCounter;
  while(iSaver!=0)
  {
  MoveToEx(hdc,p->x,p->y,NULL);
  LineTo(hdc,p->pNext->x,p->pNext->y);
  p=p->pNext->pNext;
  iSaver=iSaver-2;
  }
  }


  新增了以上的程式碼你會發現再次拖動視窗大小等等你原來畫的線就都能重現出來了。呵呵是不是覺得一個看似簡單的東西其實裡面需要很多程式碼實現呢?也許,這就是windows.

  好了,WinSDK入門的東西就談這麼多,希望能給初學者一定的幫助,這麼多的字,都是我一個一個打出來沒有任何借鑑和摘抄的。相信做為一個過來人能更多的理解大家學習中的困難。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/12639172/viewspace-442876/,如需轉載,請註明出處,否則將追究法律責任。

相關文章