Giới thiệu lập trình Windows API (Win32 API)

Windows API (WinAPI, hay nhiều nơi vẫn dùng tên cũ là Win32 API) là các hàm thư viện và các định nghĩa khác (struct, enum,…) được Windows cung cấp cho người lập trình, để viết các ứng dụng trên nền Windows.

Hệ điều hành Windows là một phần mềm hệ thống, nó thao tác trực tiếp với phần cứng máy tính. Các phần mềm ứng dụng khác chạy trên Windows không thao tác trực tiếp với phần cứng, mà luôn trung gian qua hệ điều hành. Ví dụ khi gõ phím, hệ điều hành là nơi nhận tín hiệu và xử lý xem nó là phím gì, sau đó, hệ điều hành mới xác định xem phím được gõ trên ứng dụng nào, thì mới gởi tín hiệu đến ứng dụng đó để nó xử lý, thông qua các hàm bạn đã viết trong ứng dụng..

Windows API là giao diện lập trình nằm ngay trên nền Windows, cung cấp các hàm thao tác trực tiếp với hệ điều hành và phần cứng máy tính. Các ứng dụng Windows sẽ thông qua Windows API để thao tác với máy tính.

Mô hình lập trình Windows API

Mô hình Windows API

Windows API là cách gọi chung của Win16 API, Win32 API và gần đây là Win64 API. Ở một số chỗ, người ta vẫn hay gọi Win32 API để chỉ lập trình Windows API. Lý do là trước đây và thậm chí cho đến ngày nay, Windows 32 bit được sử dụng rộng rãi. Tuy nhiên, khi nói đến Windows API hay Win32 API, bạn phải hiểu nó bao gồm cả Win64 API, vì các API của Win64 hầu hết giống hệt Win32 API, chỉ có điểm khác chính ở kích thước con trỏ. Ngoài ra, các ứng dụng viết từ Win32 API vẫn chạy tốt trên các máy 64 bit.

Gần đây, Microsoft giới thiệu thêm WinRT (Windows Runtime Library, khác Windows RT cho các máy kiến trúc ARM nhé) cho các ứng dụng metro trên store. Nhưng nó không thay thế hoàn toàn Windows API nhất là khi bạn viết các ứng dụng Desktop, hay cần thao tác trực tiếp với hệ thống.

Windows API được viết chủ yếu bằng C. Cách hiệu quả nhất để sử dụng Windows API là dùng C hoặc C++. Tuy nhiên, bạn cũng có thể sử dụng các ngôn ngữ khác như C#.NET, VB.NET, Delphi, thậm chí cả Java.

Mình tìm hiểu Windows API những năm 2000, lúc đó mình cũng thích lập trình, nên anh gia sư kèm toán chỉ thêm Visual Basic 6 lúc rãnh rỗi. Nhưng Visual Basic lúc đó có nhiều hạn chế, rất nhiều việc phải dùng đến Windows API, như đọc ghi Registry chẳng hạn, còn Visual C++ thì quá khó đối với mình lúc đó. Ngày nay thì .NET Framework đã có cung cấp lớp và phương thức thao tác với Registry, và hầu hết các tính năng Windows API cung cấp, nên lập trình trên Visual Basic .NET và C# mạnh hơn nhiều so với Visual Basic 6 thời đó.

Windows API  và .NET Framework

Ngày nay, để viết các ứng dụng trên Windows nhanh chóng và hiệu quả, chúng ta sử dụng .NET Framework là chủ yếu. .NET Framework cũng cung cấp các thư viện để viết ứng dụng trên nền Windows, và càng ngày càng làm được nhiều việc thay thế Windows API. Nhưng nó khác Windows API ở các điểm chính:

  • .NET Framework chỉ là wrapper gọi lại các hàm Windows API, tức là, nếu bạn gọi một phương thức .NET nào đó, nó sẽ gọi lại các hàm Windows API có chức năng tương ứng để thực hiện, chứ không thao tác trực tiếp đến hệ điều hành.
  • Thông qua Mono, các ứng dụng viết trên NET Framework có thể chạy được trên Linux, Mac,… còn Windows API thì không.
  • Vẫn có một số thao tác cấp thấp với hệ thống không làm được với .NET Framework

Lợi điểm của .NET Framework là nó cung cấp các API dễ dùng hơn Windows API. Nhưng nếu vậy thì chúng ta sẽ dùng đến Windows API khi nào?

Như đã nói, .NET Framework chỉ là wrapper, tức nó là trung gian giữa lập trình viên và Windows API. Khi gọi một phương thức .NET Framework, hệ thống sẽ phải gọi lại một hàm Windows API tương ứng. Do đó, hiệu suất ứng dụng sẽ giảm đi phần nào do phải gọi hàm nhiều lần. Nếu quan tâm đến hiệu suất, bạn nên gọi trực tiếp đến Windows API. Ngoài ra, nếu bạn cần viết driver, hay cần thao tác cấp thấp với hệ thống (như theo dõi gõ phím chẳng hạn),… thì gọi trực tiếp Windows API là giải pháp tốt nhất.

Thành phần của Windows API

Các hàm API có thể được chia thành một số loại chính:

  • Base Services: thao tác với các tài nguyên trên Windows như hệ thống tập tin (file systems), các thiết bị, các tiến trình (processes), các luồng (threads), quản lý bộ nhớ,… Các hàm này nằm trong thư viện kernel32.dll.
  • Advanced Services: truy xuất Windows registry, tắt, mở máy tính, Windows service, tài khoản người dùng,… Các hàm này nằm trong thư viện dvapi32.dll.
  • Graphics Device Interface: xuất dữ liệu đồ họa ra màn hình, máy in,… Các hàm này nằm trong thư viện gdi32.dll.
  • User Interface: cung cấp các thành phần giao diện người dùng như form, nút, textbox, nhận tín hiệu chuột, bàn phím,… Các hàm này nằm trong thư viện shell32.dll, user32.dll, comctl32.dll, comdlg32.dll:
    • Common Dialog Box Library: các hộp thoại cơ bản trong Windows mà hầu như ứng dụng nào cũng sử dụng, như hộp thoại mở file, lưu file,…
    • Common Control Library: nút, status bars, progress bars, toolbars, tabs,…
    • Windows Shell: thu tín hiệu chuột, bàn phím,…
  • Network Services: các thao tác liên quan đến mạng
  • Multimedia: thao tác với các thiết bị đa phương tiện

Ví dụ mẫu

Trong bài này, mình sẽ minh họa cách dùng Windows API với ứng dụng đơn giản là hiển thị một hộp Message Box, hỏi Do you feel happy?, chứa nút Yes và No (mình dùng UI tiếng Việt nên nó là nút Có và Không), có hình icon Question, nút mặc định được chọn là nút thứ nhất, như hình sau:

Khi bấm Yes, một hộp thoại khác hiển thị chỉ chứa nút OK với thông điệp You said Yes. Ngược lại là thông điệp You said No.

Mình sẽ tạo hai minh họa: một bằng project Win32 Visual C++ gọi trực tiếp Windows API, một bằng project Visual C# gọi thông qua wrapper class, cả hai sử dụng Visual Studio 2013.

Trong .NET Framework, bạn có thể tạo một Message Box như vậy với phương thức System.Windows.Forms.MessageBox.Show(). Nhưng khi bạn gọi nó, hệ thống sẽ gọi lại đến hàm Windows API MessageBox() đã định nghĩa trong thư viện user32.dll. Cú pháp C/C++ của hàm này như sau:

int WINAPI MessageBox(
     _In_opt_ HWND hWnd,
      _In_opt_ LPCTSTR lpText,
      _In_opt_ LPCTSTR lpCaption,
      _In_ UINT uType
);

Trong đó:

  • hWnd: là handle của cửa sổ sổ chứa (owner) Message Box này. Ví dụ nếu MS Word hiển thị hộp Message Box hỏi bạn có muốn lưu file hay không thì cửa sổ MS Word đang mở là cửa sổ owner của Message Box. Bạn có thể truyền NULL nếu Message Box không có owner. Trong Windows, mỗi cửa sổ (nút, textbox,… chẳng hạn cũng là một loại cửa sổ) đều được hệ điều hành quản lý bằng một ID, giống CMND của người vậy, ID này gọi là handle. Handle là một struct, tham chiếu đến một cửa sổ, chỉ hệ điều hành mới biết về nó. Trong .NET Framework, kiểu dữ liệu tương ứng là System.IntPtr, trong đa số trường hợp, bạn dùng kiểu int cũng không có vấn đề gì.
  • lpText: chuỗi thông báo trên Message Box
  • lpCaption: chuỗi trên thanh tiêu đề của Message Box. Nếu là NULL, mặc định là Error.
  • uType: hộp sẽ hiển thị nút và icon gì, trong các nút đó, nút nào là nút mặc định,…
  • Hàm trả về một số int, cho biết nút nào đã được bấm.
  • Về các hằng uType và giá trị trả về, bạn tham khảo thêm tại đây: https://msdn.microsoft.com/en-us/library/windows/desktop/ms645505%28v=vs.85%29.aspx

Ví dụ mẫu về Windows API với C/C++

Bước 1: Từ Visual Studio, tạo project mới. Tại hộp thoại New Project, chọn Visual C++ và template Win32 Project, đặt tên tùy ý, bấm OK.

Bước 2: Bấm Next, đến khi gặp hộp thoại Application Settings, phần Application type, chọn Windows application, phần Additional options, chọn Empty project, rồi bấm Finish.

Bước 3: Hệ thống sẽ tạo cho bạn 1 project rỗng, không có gì. Bấm phải vào project, chọn Add > New Item…, chúng ta sẽ thêm vào project một file source C++.

Bước 4: Tại hộp thoại Add New Item, chọn C++ File (.cpp), đặt tên tùy ý rồi bấm Add.

Bước 5: Mở file .cpp rồi thêm đoạn mã sau (mình có chú thích trong mã):

#include <windows.h>
// Include header windows.h. Đây là header chính chứa các header Windows API khác

// Hàm WinMain là điểm vào (entry point) của ứng dụng Windows API,
// giống hàm main() trong C hoặc phương thức Main(), main() trong C# và Java
// Mình sẽ viết bài khác mô tả các tham số của nó, ở đây chưa quan tâm đến chúng
int WINAPI WinMain(
     HINSTANCE hInstance,
     HINSTANCE hPrevInstance,
     LPSTR lpCmdLine,
     int nCmdShow)
{

     int result = MessageBox(
          NULL,      // Không có owner windows
          (LPCSTR)"Do you feel happy?",      // thông điệp chính
          (LPCSTR)"Hello world",     // văn bản trên title bar
          MB_YESNO + MB_ICONQUESTION + MB_DEFBUTTON1);
          // hiển thị nút Yes No, kèm Icon Question và nút Yes là mặc định

     // Nếu người dùng chọn Yes
     if (result == IDYES)
     {
          MessageBox(NULL,
               (LPCSTR)"You said Yes.",
               (LPCSTR)"Hello world",
               MB_OK);     // chỉ hiển thị nút OK
     }
     else
     {
          MessageBox(NULL,
               (LPCSTR)"You said No.",
               (LPCSTR)"Hello world",
               MB_OK);     // chỉ hiển thị nút OK
     }

     return 0;

}

Ví dụ mẫu về Windows API với C# .NET (hoặc VB.NET)

Để có thể gọi Windows API từ ứng dụng .NET, bạn cần sử dụng không gian tên System.Runtime.InteropServices.

Bước 1: Từ Visual Studio, tạo project mới. Tại hộp thoại New Project, chọn Visual C# và template Console Application, đặt tên tùy ý, bấm OK.

Bước 2: Sử dụng đoạn mã sau cho file Program.cs (mình có chú thích trong mã):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
// Cần thêm chỉ thị sau vì chúng ta cần thư viện InteropServices
// để gọi các hàm Windows API
using System.Runtime.InteropServices;

namespace HelloWorldWindowsAPICSharp
{
     class Program
     {
          // Khai báo mẫu cho hàm MessageBox, hàm này phải Import Dll user32.dll
          // Bạn có thể khai báo khác đi một chút, miễn phù hợp với
          // cú pháp C đã trình bày ở trên, ví dụ bạn dùng kiểu int 
          // cho tham số hWnd cũng được, miễn là lúc gọi truyền vào một số nguyên
          [DllImport("user32.dll", CharSet = CharSet.Auto)]
          public static extern MessageBoxResult MessageBox(IntPtr hWnd, String text, String caption, MessageBoxOptions options);

          // Bạn có thể không cần 2 khai báo enum dưới đây
          // mà có thể dùng trực tiếp giá trị, ví dụ nếu muốn
          // hiện nút OK thì có thể dùng thẳng 0x000000 cho tham số thứ 4,
          // tuy nhiên, để dễ xem mã, nên khai báo hằng hoặc enum, với lại,
          // các khai báo này có thể copy từ các trang tham khảo
          // như tài liệu MSDN hoặc http://pinvoke.net không khó khăn
          public enum MessageBoxResult : uint
          {
               Ok = 1,
               Cancel,
               Abort,
               Retry,
               Ignore,
               Yes,
               No,
               Close,
               Help,
               TryAgain,
               Continue,
               Timeout = 32000
          }

          [Flags]
          public enum MessageBoxOptions : uint
          {
               OkOnly = 0x000000,
               OkCancel = 0x000001,
               AbortRetryIgnore = 0x000002,
               YesNoCancel = 0x000003,
               YesNo = 0x000004,
               RetryCancel = 0x000005,
               CancelTryContinue = 0x000006,
               IconHand = 0x000010,
               IconQuestion = 0x000020,
               IconExclamation = 0x000030,
               IconAsterisk = 0x000040,
               UserIcon = 0x000080,
               IconWarning = IconExclamation,
               IconError = IconHand,
               IconInformation = IconAsterisk,
               IconStop = IconHand,
               DefButton1 = 0x000000,
               DefButton2 = 0x000100,
               DefButton3 = 0x000200,
               DefButton4 = 0x000300,
               ApplicationModal = 0x000000,
               SystemModal = 0x001000,
               TaskModal = 0x002000,
               Help = 0x004000,
               NoFocus = 0x008000,
               SetForeground = 0x010000,
               DefaultDesktopOnly = 0x020000,
               Topmost = 0x040000,
               Right = 0x080000,
               RTLReading = 0x100000
          }

          static void Main(string[] args)
          {
               MessageBoxResult result = MessageBox(
                    IntPtr.Zero,     // Không có cửa sổ owner
                    "Do you feel happy?",     // Thông điệp chính
                    "Hello world",     // Thông điệp title bar
                    MessageBoxOptions.YesNo | MessageBoxOptions.IconQuestion | MessageBoxOptions.DefButton1);
                    // hiển thị nút Yes No, kèm Icon Question và nút Yes là mặc định

               if (result == MessageBoxResult.Yes)
               {
                    MessageBox(
                         IntPtr.Zero,
                         "You said Yes.",
                         "Hello world",
                         MessageBoxOptions.OkOnly);     // chỉ hiển thị nút OK
               }
               else
               {
                    MessageBox(
                         IntPtr.Zero,
                         "You said No.",
                         "Hello world",
                         MessageBoxOptions.OkOnly);     // chỉ hiển thị nút OK
               }
          }
     }
}

Nguồn tham khảo:

Bình luận

Bình luận Facebook

lời bình luận