Bài giảng Lập trình Windows - Bài 1: Tổng quan lập trình Windows
Bạn đang xem 20 trang mẫu của tài liệu "Bài giảng Lập trình Windows - Bài 1: Tổng quan lập trình Windows", để tải tài liệu gốc về máy bạn click vào nút DOWNLOAD ở trên
Tài liệu đính kèm:
- bai_giang_lap_trinh_windows_chuong_1_tong_quan_lap_trinh_win.doc
Nội dung text: Bài giảng Lập trình Windows - Bài 1: Tổng quan lập trình Windows
- Bài 1 TỔNG QUAN VỀ LẬP TRÌNH WINDOWS Không cần nói nhiều hơn, tất cả chúng ta đều khá quen thuộc với việc sử dụng các ứng dụng trên hệ điều hành Microsoft Windows. Bài học đầu tiên này sẽ giúp các bạn hiểu rõ các đặc điểm chính của hệ điều hành này, đồng thời trình bày cách xây dựng ứng dụng cụ thể dạng Win32 Application trên công cụ Visual C++ 6.0. 1.1. LẬP TRÌNH TRÊN MICROSOFT WINDOWS Trong tất cả các môn học trước, hầu như chúng ta chỉ cài đặt các chương trình, minh họa các thuật toán, dưới dạng các ứng dụng xử lý tuần tự theo lô (batch-oriented) hay giao tác (transaction-oriented) gắn liền với hệ điều hành MS-DOS. Tuy nhiên, các ứng dụng trên Windows thì hoàn toàn khác. Vì thế, phần này sẽ tóm tắt giúp các bạn các đặc điểm chính của hệ điều hành Windows, và cũng chính là các đặc điểm của các ứng dụng khi lập trình trên môi trường này. 1.1.1. Thông điệp và xử lý thông điệp Khi chúng ta viết một chương trình dạng MS-DOS bằng ngôn ngữ C, yêu cầu duy nhất là ta phải viết một hàm main. Hệ điều hành gọi hàm main khi chương trình được thực thi, và thực hiện theo các cấu trúc lệnh đã được lập trình từ trên xuống dưới.
- Lập trình Windows Tuy nhiên, với Windows thì khác hẳn. Hệ điều hành Windows cũng gọi một hàm chính là WinMain khi bắt đầu tiến trình để thực hiện việc tạo lập cửa sổ ứng dụng, ứng dụng này được hệ điều hành quản lý thông qua địa chỉ của tiến trình và vùng nhớ đối tượng cửa sổ. Các thao tác của người dùng - hoặc do các đối tượng khác trên Windows - tác động lên ứng dụng đều được chuyển thành một dạng thông tin gọi là thông điệp (message) và được hệ điều hành quản lý. Hệ điều hành lần lượt chuyển các thông điệp đến các cửa sổ nhận thao tác vừa thực hiện. Khi lập trình, các thông điệp này đều được gởi vào hàm xử lý của cửa sổ - thường đặt tên là WndProc đối với cửa sổ chính của ứng dụng, và ta chỉ việc viết code để xử lý cho ứng dụng thông qua các giá trị thông điệp (tương ứng các thao tác) đã được Windows định nghĩa sẵn. 1.1.2. Giao tiếp người dùng đồ họa GUI Vấn đề xử lý thông điệp là đặc điểm quan trọng nhất, tuy nhiên khi đề cập đến lập trình trên Windows người ta lại thường chú ý tới đặc trưng giao diện (visual interface) của các ứng dụng hơn. Trên môi trường Windows, các ứng dụng giao tiếp (trao đổi thông tin) với người dùng thông qua các đối tượng trực quan như cửa sổ (window), thanh trình đơn (menu), hộp hội thoại (dialog box), các đối tượng điều khiển (controls), . Khi lập trình, chúng ta sử dụng các hàm API (Application Programming Interface), cơ chế liên kết động DLL (Dynamic Link Library) và hệ thống tài nguyên (resource-based programming) của IDE (Intergrated Development 2
- Bài 1. Tổng quan Environment) để xây dựng giao diện các ứng dụng theo các định dạng mà Windows hỗ trợ. 1.1.3. Giao tiếp thiết bị đồ họa GDI Khi lập trình trên MS-DOS, để giao tiếp với các thiết bị - đưa dữ liệu ra vùng nhớ màn hình, cổng máy in chẳng hạn - thì ta phải biết cơ chế của các thiết bị phần cứng tương ứng, rồi ghi dữ liệu ra thẳng thiết bị. Thế nhưng, trên Windows, với sự hỗ trợ của hệ điều hành, các ứng dụng không cần phải truy xuất thiết bị vật lý. Dữ liệu được xuất ra một đối tượng thiết bị ảo, gọi là thiết bị ngữ cảnh (device context), sau đó thông qua chương trình điều khiển thiết bị đã cài đặt trên hệ điều hành (tức là các driver của các thiết bị phần cứng), dữ liệu sẽ được chuyển đến các thiết bị vật lý khác nhau mà người lập trình ứng dụng trên Windows không cần phải biết về chúng. 1.1.4. Quản lý vùng nhớ và tập tin Với các phiên bản mới của hệ điều hành, việc quản lý các vùng nhớ và tập tin của các ứng dụng trên Windows ngày càng dễ dàng và hiệu quả hơn. Chúng ta không phải thật sự khó khăn khi cấp phát, truy xuất, giải phóng, các khối nhớ, vì đã có hệ điều hành lo giúp. Với cơ chế cấp phát và quản lý động, quản lý vùng nhớ ảo và ánh xạ vùng nhớ lên hệ thống lưu trữ phụ, việc thao tác các đối tượng vùng nhớ lớn, phức tạp giờ đây chỉ còn là việc gọi các hàm Windows cung cấp và việc tổ chức các cấu trúc dữ liệu phú hợp cho ứng dụng mà thôi. 3
- Lập trình Windows 1.1.5. Thư viện liên kết động Trên môi trường MS-DOS, các module của các đối tượng trong mỗi chương trình chỉ được liên kết tĩnh trong quá trình tạo (build) ứng dụng. Trong khi trên môi trường Windows, các hàm, thư viện còn có thể được gọi khi chương trình đang thực thi (run-time), gọi là cơ chế liên kết động. Các tiến trình đang thực thi có thể cùng chia sẻ các thư viện này, điều này giúp giảm thiểu vùng nhớ và kích thước các tập tin. Thật ra bản thân hệ điều hành Windows là tập hợp các thư viện liên kết động, với 3 đơn vị chính là Kernel, User, và GDI liên quan đến các hàm, thủ tục thể hiện các chức năng quản lý các tiến trình, vùng nhớ, tập tin, (kernel), giao diện người dùng, các cửa sổ, (user) và thiết bị đồ họa (GDI) và các DLL gắn liền với các ứng dụng khác được cài đặt trên máy tính. 1.2. ỨNG DỤNG WIN32 APPLICATION TRÊN MS VISUAL C++ 6.0 Sau khi nắm được các đặc điểm chính của các ứng dụng trên môi trường hệ điều hành MS Windows, phần này sẽ giúp các bạn hiểu và xây dựng ứng dụng cụ thể dạng Win32 Application trên công cụ MS Visual C++ 6.0. 1.2.1. Project ứng dụng Win32 Application MS Visual C++ 6.0 hỗ trợ lập trình nhiều dạng ứng dụng khác nhau như Win32 Console Application, Win32 Application, MFC AppWizard, Win32 Dynamic Link Library, trong đó 4
- Bài 1. Tổng quan Win32 Application lập trình trực tiếp với các hàm API là dạng mà chúng ta sẽ tìm hiểu trong môn học này. Hình 1.1. Tạo lập project dạng Win32 Application Sử dụng Wizard của Visual C++ 6.0, chúng ta tạo lập project đầu tiên như sau: Bước 1: Từ menu File, chọn menu item New Ctrl+N. Bước 2: Trên hộp thoại New chọn dạng Projects Win32 Apllication, gõ tên project (Project name) và đường dẫn (Location) cần tạo – xem hình 1.1. Chọn OK. Bước 3: Visual C hỗ trợ cho phép tạo lập một số mẫu project. Ta chọn dạng A typical “Hello World!” 5
- Lập trình Windows application – xem hình 1.2. Chọn Finish. Với cách chọn này, Visual C++ 6.0 phát sinh sẵn một số file code, tài nguyên – kết quả thể hiện trên hình 1.3. Sau khi chọn OK để kết thúc việc tạo project mới, chúng ta sẽ tìm hiểu giao diện của IDE MS Visual C++ 6.0 và cách viết chương trình trên công cụ này. Hình 1.2. Chọn project A typical “Hello World!” application 1.2.2. Các thành phần chính trên IDE MS Visual C++ 6.0 Được xây dựng dựa theo công cụ Visual Workbench, IDE này gồm các cửa sổ, thanh công cụ, editor có thể cấu hình, kết hợp hài hòa với nhau giúp các lập trình viên dễ dàng biên soạn code, tạo lập các tài nguyên, biên dịch, debug lỗi . 6
- Bài 1. Tổng quan Hình 1.3. Thông báo sau khi tạo project của Visual C++ 6.0 Nếu đã từng sử dụng các IDE, có lẽ bạn sẽ dễ dàng để hiểu cách tổ chức và hoạt động của Visual C++ 6.0. Tuy nhiên, nếu mới lập trình trên một IDE như thế này thì bạn cần phải hiểu project là gì. Một project là một tập hợp các file source có liên hệ với nhau mà trình biên dịch có thể biên dịch và liên kết để tạo các file khả thi dạng Windows hoặc các DLL. Các file source trong mỗi project thường được lưu trữ trong một thư 7
- Lập trình Windows mục riêng biệt. Ngoài ra, một project còn phụ thuộc vào một số file liên kết như file header, file thư viện khác. Cửa sổ Workspace Mặc định thì cửa sổ bên trái mà chúng ta thấy trên IDE Visual C++ 6.0 là một treeview cho phép chúng ta chọn các tập tin source code, resource, để soạn thảo. Cửa sổ Workspace Source code Thanh WizardBar Các thông điệp biên dịch và liên kết Hình 1.4. Giao diện của MS Visual C++ 6.0 FileView liệt kê các tập tin trong project, tổ chức như cây thư mục phân theo nhóm file chức năng: các file source code (.c 8
- Bài 1. Tổng quan hoặc .cpp), các file header (.h và .hpp), các file resource như icon (.ico), bitmap (.bmp), ResourceView liệt kê các tài nguyên của ứng dụng. Visual C++ 6.0 hỗ trợ các công cụ trực quan cho phép các lập trình viên thao tác các resource một cách hiệu quả. Khi chọn menu, hộp thoại, IDE sẽ thể hiện các editor tương ứng. ClassView giúp lập trình viên dễ dàng chuyển đến các lớp đối tượng (đặc biệt cho project MFC), các hàm, các biến trong project. Cửa sổ soạn thảo source code Visual C++ 6.0 cung cấp một cửa sổ soạn code khá tuyệt vời, hỗ trợ nhiều chức năng khi lập trình như thể hiện màu chữ theo dạng code, chế độ tự động chuyển tab, và nhất là chức năng AutoComplete (tự động thể hiện các tên hàm, biến, đã định nghĩa). Cửa sổ thể hiện kết quả build (Output window) Khi lập trình, việc biên dịch, sửa lỗi, là thao tác rất quan trọng. Trình biên dịch kiểm tra các lỗi cú pháp, ngữ nghĩa, và xuất các thông báo liên quan cho lập trình viên. Căn cứ vào thông báo lỗi, các lập trình viên có thể lần theo các vết thông báo để sửa lại chương trình Các cửa sổ khác trên MS Visual C++ 6.0 Ngoài các cửa sổ vừa đề cập ở trên, Visual C++ 6.0 còn cung cấp các đối tượng trực quan khác để hỗ trợ các lập trình viên như thanh WizardBar, MenuBar, ToolsBar, Các cửa sổ trên 9
- Lập trình Windows IDE này đều có thể kéo trôi nổi (docking) và sắp xếp theo ý thích của người sử dụng. 1.2.3. Quy trình build ứng dụng Quy trình build project tạo file khả thi dạng EXE hoặc DLL được Visual C++ thực hiện theo hai công đoạn chính - xem hình 1.5 - như sau: Visual C++ Biên dịch code Biên dịch resource Các files Các files Resource header source script file (RC) resource.h Các files Bitmaps, icons, và header các resources runtime khác Compiler Resource Compiler Các files OBJ File resource (RES) Các thư viện Windows File khả thi và runtime Linker (EXE) Hình 1.5. Quy trình build một project ứng dụng - Biên dịch (compile) các file source code và resource sang dạng mã trung gian OBJ, RES, - Liên kết (link) các file OBJ, RES với các file thư viện Windows và run-time để tạo file khả thi. 10
- Bài 1. Tổng quan Với project đơn giản đã tạo lập ở trên, chúng ta có thể thực hành xem kết quả build file BaiTap.exe. Bằng cách chọn chức năng Build BaiTap.exe F7 từ menu Build, Visual C++ sẽ thực hiện biên dịch và liên kết các file có trong project và tạo các file OBJ, RES, EXE, tương ứng. Tuỳ theo dạng build được cấu hình cho project (Win32 Release hoặc Win32 Debug), các file mã phát sinh trong quá trình build sẽ được tạo ra trong một thư mục con mặc định có tên là Release hoặc Debug - nằm trong thư mục chứa các file nguồn của project. Nếu tạo lập như ở trên, chúng ta sẽ có các file mã phát sinh trong thư mục D:\Study\BaiTap\Debug chẳng hạn. Để ý là khi copy code của một project đem sang một máy tính khác để build, chúng ta không cần phải copy thư mục này (vì khá lớn)!!! Hình 1.6. Thể hiện của ứng dụng BaiTap.exe 11
- Lập trình Windows Nếu quá trình build thành công – được thông báo trên cửa sổ Output, xem phần 1.2.2. - chúng ta có thể xem kết quả thực hiện bằng cách chọn chức năng Execute Program (Ctrl+F5). Kết quả project này thể hiện như ở hình 1.6. 1.3. PHÂN TÍCH SOURCE CODE CỦA PROJECT Trong project BaiTap dạng Win32 Application được tạo như ở trên, file BaiTap.cpp (workspace FileView) chính là file source chính cài đặt các thao tác tạo lập và xử lý các chức năng của cửa sổ ứng dụng này. Dưới đây là phần code do MS Visual C++ 6.0 phát sinh, với các chú thích được viết lại bằng tiếng Việt cho dễ hiểu. 1 // BaiTap.cpp : Tập tin cài đặt chính của ứng dụng BaiTap 2 #include "stdafx.h" 3 #include "resource.h" 4 #define MAX_LOADSTRING 100 5 // Định nghĩa các biến toàn cục 6 HINSTANCE hInst; // Instance của ứng dụng 7 TCHAR szTitle[MAX_LOADSTRING]; // Thể hiện trên thanh tiêu đề 8 TCHAR szWindowClass[MAX_LOADSTRING]; // Tên lớp cửa sổ 9 // Định nghĩa trước các hàm sẽ được gọi trong module này 10 ATOM MyRegisterClass(HINSTANCE hInstance); 11 BOOL InitInstance(HINSTANCE, int); 12 LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); 13 LRESULT CALLBACK About(HWND, UINT, WPARAM, LPARAM); 14 int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, 15 LPSTR lpCmdLine, int nCmdShow) 16 { 17 // Các biến cục bộ trong hàm WinMain 18 MSG msg; // Biến kiểu dữ liệu thông điệp 19 HACCEL hAccelTable; // Bảng phím nóng 20 // Khởi tạo bằng cách nạp các chuỗi từ resource (tài nguyên) 21 LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING); 22 LoadString(hInstance, IDC_BAITAP, szWindowClass, MAX_LOADSTRING); 23 MyRegisterClass(hInstance); 12
- Bài 1. Tổng quan 24 // Thực hiện việc khởi tạo ứng dụng 25 if (!InitInstance (hInstance, nCmdShow)) 26 { 27 return FALSE; 28 } 29 Nạp bảng phím nóng từ resource 30 hAccelTable = LoadAccelerators(hInstance, (LPCTSTR)IDC_BAITAP); 31 // Vòng lặp xử lý thông điệp của ứng dụng – xem lại phần 1.1.1 32 while (GetMessage(&msg, NULL, 0, 0)) 33 { 34 if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) 35 { 36 TranslateMessage(&msg); 37 DispatchMessage(&msg); 38 } 39 } 40 return msg.wParam; 41 } 42 // Hàm MyRegisterClass() - Đăng ký một lớp cửa sổ. 43 ATOM MyRegisterClass(HINSTANCE hInstance) 44 { 45 WNDCLASSEX wcex; // Lớp cửa sổ 46 wcex.cbSize = sizeof(WNDCLASSEX); 47 wcex.style = CS_HREDRAW | CS_VREDRAW; 48 wcex.lpfnWndProc = (WNDPROC)WndProc; 49 wcex.cbClsExtra = 0; 50 wcex.cbWndExtra = 0; 51 wcex.hInstance = hInstance; 52 wcex.hIcon = LoadIcon(hInstance, (LPCTSTR)IDI_BAITAP); 53 wcex.hCursor = LoadCursor(NULL, IDC_ARROW); 54 wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); 55 wcex.lpszMenuName = (LPCSTR)IDC_BAITAP; 56 wcex.lpszClassName = szWindowClass; 57 wcex.hIconSm = LoadIcon(wcex.hInstance, 58 (LPCTSTR)IDI_SMALL); 59 return RegisterClassEx(&wcex); 60 } 61 // Hàm InitInstance(HANDLE, int) – Lưu định danh ứng dụng và tạo cửa sổ 62 BOOL InitInstance(HINSTANCE hInstance, int nCmdShow) 63 { 64 HWND hWnd; // Biến định danh cửa sổ 65 hInst = hInstance; // Lưu định danh ứng dụng vào biến toàn cục ở trên 66 // Tạo lập cửa sổ 13
- Lập trình Windows 67 hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW, 68 CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL); 69 if (!hWnd) 70 { 71 return FALSE; 72 } 73 // Hiển thị cửa sổ với dạng hiển thị là nCmdShow 74 ShowWindow(hWnd, nCmdShow); 75 UpdateWindow(hWnd); 76 return TRUE; 77 } 78 // Hàm WndProc(HWND, unsigned, WORD, LONG) 79 // Hàm xử lý các thông điệp sau của cửa sổ ứng dụng: 80 // WM_COMMAND - Xử lý các thao tác liên quan đến menu 81 // WM_PAINT - Xử lý thao tác vẽ lại cửa sổ 82 // WM_DESTROY - Gởi thông điệp kết thúc ứng dụng khi đóng cửa sổ 83 LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, 84 LPARAM lParam) 85 { 86 // Định nghĩa các biến cục bộ 87 int wmId, wmEvent; 88 PAINTSTRUCT ps; 89 HDC hdc; 90 TCHAR szHello[MAX_LOADSTRING]; 91 LoadString(hInst, IDS_HELLO, szHello, MAX_LOADSTRING); 92 switch (message) 93 { 94 case WM_COMMAND: // Thông điệp liên quan tới menu và các control 95 wmId = LOWORD(wParam); 96 wmEvent = HIWORD(wParam); 97 // Chuyển thao tác ứng với menu item được kích hoạt 98 switch (wmId) 99 { 100 case IDM_ABOUT: 101 DialogBox(hInst, (LPCTSTR)IDD_ABOUTBOX, 102 hWnd, (DLGPROC)About); 103 break; 104 case IDM_EXIT: 105 DestroyWindow(hWnd); 106 break; 107 default: 108 return DefWindowProc(hWnd, message, wParam, lParam); 109 } 110 break; 111 case WM_PAINT: // Thông điệp vẽ lại cửa sổ 14
- Bài 1. Tổng quan 112 hdc = BeginPaint(hWnd, &ps); 113 RECT rt; 114 GetClientRect(hWnd, &rt); 115 DrawText(hdc, szHello, strlen(szHello), &rt, DT_CENTER); 116 EndPaint(hWnd, &ps); 117 break; 118 case WM_DESTROY: 119 PostQuitMessage(0); 120 break; 121 default: 122 return DefWindowProc(hWnd, message, wParam, lParam); 123 } 124 return 0; 125 } 126 // Xử lý các thông điệp gởi cho hộp thoại IDD_ABOUTBOX 127 LRESULT CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, 128 LPARAM lParam) 129 { 130 switch (message) 131 { 132 case WM_INITDIALOG: 133 return TRUE; 134 case WM_COMMAND: 135 if (LOWORD(wParam) == IDOK || 136 LOWORD(wParam) == IDCANCEL) 137 { 138 EndDialog(hDlg, LOWORD(wParam)); 139 return TRUE; 140 } 141 break; 142 } 143 return FALSE; 144 } Chúng ta sẽ tìm hiểu hai hàm chính luôn phải có trong một chương trình dạng Win32 Application là WinMain và WndProc. 1.3.1. Hàm WinMain Như đã giới thiệu ở phần 1.1.1, hệ điều hành khởi gọi thực thi một ứng dụng dạng Windows (Windows-based) thông qua hàm WinMain có dạng: 15
- Lập trình Windows int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow); Trong đó: o hInstance là con trỏ được Windows quản lý xác định tiến trình ứng dụng đang thực thi, gọi là định danh ứng dụng (handle to instance). o hPrevInstance là định danh của tiến trình kế trước của ứng dụng này, thường bằng NULL. o lpCmdLine trỏ đến chuỗi ký tự zero xác định dòng command line gọi ứng dụng (không chứa tên chương trình). o nCmdShow là tham số xác định cách thức hiển thị cửa sổ sẽ được tạo lập. Trong một ứng dụng thông thường, hàm WinMain thực hiện các chức năng chính sau: Định nghĩa và đăng ký một lớp cửa sổ: Đầu tiên của viêc xây dựng một ứng dụng Windows là phải định nghĩa một lớp cửa sổ cho ứng dụng. Windows cung cấp một cấu trúc WNDCLASS (mở rộng là WNDCLASSEX) gọi là lớp cửa sổ, lớp này chứa những thuộc tính tạo thành một cửa sổ. typedef struct _WNDCLASSEX { UINT cbSize; UINT style; WNDPROC lpfnWndProc; int cbClsExtra; int cbWndExtra; HINSTANCE hInstance; 16
- Bài 1. Tổng quan HICON hIcon; HCURSOR hCursor; HBRUSH hbrBackground; LPCTSTR lpszMenuName; LPCTSTR lpszClassName; HICON hIconSm; } WNDCLASSEX, *PWNDCLASSEX; Sau khi khởi gán giá trị các tham số của đối tượng lớp cửa sổ, ta dùng hàm RegisterClass hoặc RegisterClassEx để đăng ký lớp cửa sổ này với hệ thống. ATOM RegisterClassEx(CONST WNDCLASSEX *lpwcx); Tuỳ theo cửa sổ ứng dụng muốn thể hiện, ta sẽ xác lập các giá trị khác nhau cho cấu trúc lớp của sổ ở trên. Ở đây chúng tôi không trình bày chi tiết các thuộc tính của lớp cửa sổ, sinh viên cần tham khảo thêm trong MSDN để tạo lập các dạng cửa sổ theo ý mình. Trong các tham số ở trên, cần lưu ý thuộc tính lpfnWndProc. Đây chính là tên hàm xử lý thông điệp của ứng dụng. Trong ví dụ ở trên, tham số này có giá trị là WndProc, điều đó có nghĩa là với cách tạo lập này thì mọi thao tác xử lý của ứng dụng sẽ được viết trong một hàm có tên là WndProc. Chúng ta sẽ tìm hiểu kỹ hơn về hàm này trong phần 1.3.2. Tạo lập và hiện thị cửa sổ ứng dụng: Sau khi đăng ký lớp cửa sổ, ở thao tác tiếp theo ta dùng hàm CreateWindow hoặc CreateWindowEx để tạo lập cửa sổ ứng dụng theo cấu trúc cửa sổ đã định nghĩa. 17
- Lập trình Windows HWND CreateWindow( LPCTSTR lpClassName, // Tên lớp cửa sổ đã đăng ký LPCTSTR lpWindowName, // Tên cửa sổ sẽ hiển thị DWORD dwStyle, // Các kiểu cửa sổ int x, // Vị trí ngang ban đầu int y, // Vị trí dọc ban đầu int nWidth, // Chiều rộng ban đầu int nHeight, // Chiều cao ban đầu HWND hWndParent, // Định danh của cửa sổ cha HMENU hMenu, // Định danh của menu HINSTANCE hInstance, // Định danh tiến trình ứng dụng LPVOID lpParam // Dữ liệu gởi vào app khi tạo lập ); Bằng cách thiết lập các thông số về lớp cửa sổ, tên cửa sổ, kích thước, ta có thể tạo lập các cửa sổ khác nhau. Nếu tạo lập thành công, hàm này trả về định danh của cửa sổ; nếu không, giá trị trả về bằng NULL. Các thao tác trên ứng dụng sau này thường tham chiếu đến biến định danh cửa sổ vừa được tạo lập. Trong các ứng dụng thông thường – như ở ví dụ trên – thao tác tiếp theo là hiển thị cửa sổ vừa tạo lập. BOOL ShowWindow( HWND hWnd, // Định danh của cửa sổ int nCmdShow // Dạng hiển thị ); Trong ví dụ ở trên, nCmdShow chính là giá trị được gởi vào từ hàm WinMain. Tuy nhiên, ta có thể thay đổi với các giá trị khác nhau để hiển thị cửa sổ dưới nhiều cách thức khác nhau. Sau đây là một số dạng hiển thị đơn giản: SW_HIDE: Ẩn cửa sổ này và kích hoạt cửa sổ khác. SW_MAXIMIZE: Phóng lớn cửa sổ ra đầy màn hình. 18
- Bài 1. Tổng quan SW_MINIMIZE: Thu nhỏ cửa sổ về task bar, đồng thời kích hoạt cửa sổ ứng dụng kế tiếp theo thứ tự đã chọn. SW_RESTORE: Hiển thị theo đúng kích thước đã tạo lập. Vòng lặp xử lý thông điệp Sau khi cửa sổ được hiển thị trên màn hình, chương trình nhận các thao tác của người dùng (từ bàn phím, mouse, ) hoặc từ các ứng dụng khác, gọi là thông điệp. Các thông điệp này được Windows quản lý – dưới dạng một hàng đợi – và chuyển cho ứng dụng qua hàm GetMessage trong hàm WinMain này. BOOL GetMessage( LPMSG lpMsg, // Cấu trúc nhận thông tin thông điệp HWND hWnd, // Định danh của cửa sổ UINT wMsgFilterMin, // Giá trị thông điệp nhỏ nhất UINT wMsgFilterMax // và lớn nhất cần nhận ); Với thông điệp nhận được, nếu là thông điệp phím nóng (accelerator) ta dùng hàm TranslateAccelerator để chuyển các thông tin thành dạng thông điệp ứng các thao tác của menu, control (thông điệp này có tên là WM_COMMAND và WM_SYSCOMMAND sẽ được học trong bài nói về menu và control). Tương tự nếu là thông điệp về phím ảo (virtual key) ta dùng hàm TranslateMessage để chuyển thành các thông điệp ký tự (sẽ được học trong bài nói về mouse và keyboard). Sau đó các thông điệp nhận được này sẽ chuyển cho hàm xử lý thông điệp WndProc qua hàm DispatchMessage. LRESULT DispatchMessage( CONST MSG * lpmsg // Thông tin thông điệp ); 19
- Lập trình Windows Trong ví dụ ở trên, msg là một biến cấu trúc kiểu MSG được định nghĩa trong tập tin tiêu đề WINUSER.H. typedef struct tagMSG { HWND hwnd; UINT message; WPARAM wParam; LPARAM lParam; DWORD time; POINT pt; } MSG, *PMSG; Kiểu dữ liệu POINT là một kiểu cấu trúc khác, được định nghĩa trong tập tin tiêu đề WINDEF.H, và có mô tả : typedef struct tagPOINT { LONG x; LONG y; } POINT, *PPOINT; Ý nghĩa của các trường trong cấu trúc MSG hwnd : Định danh của cửa sổ mà thông điệp phát sinh. message : Định danh của thông điệp, ví dụ như thông điệp phát sinh khi bấm nút chuột trái là WM_LBUTTONDOWN có giá trị 0x0201. wParam : Tham số 32-bit chứa các thông tin phụ thuộc vào từng thông điệp cụ thể. lParam : Tham số 32-bit phụ thuộc vào thông điệp. time : Thời gian đặt thông điệp trong hàng đợi. pt : Tọa độ của chuột khi đặt thông điệp vào hàng đợi. Hàm GetMessage sẽ trả về 0 nếu msg chứa thông điệp có định danh WM_QUIT (0x0012), khi đó vòng lặp thông điệp 20
- Bài 1. Tổng quan ngưng và ứng dụng kết thúc. Ngược lại thì hàm sẽ trả về một giá trị khác 0 với các thông điệp khác. 1.3.2. Hàm xử lý cửa sổ ứng dụng WndProc Như đã nói ở trên, mỗi lần ứng dụng nhận được một thông điệp trong hàm WinMain thì chuyển qua cho hàm xử lý WndProc. Và như thế, ta chỉ cần lập trình cho từng thao tác ứng với các thông điệp khác nhau. Ta có cấu trúc hàm WndProc. LRESULT CALLBACK WndProc( HWND hWnd, // Định danh cửa sổ nhận thông điệp UINT message, // Giá trị thông điệp gởi vào WPARAM wParam, // Hai giá trị thông tin gởi kèm LPARAM lParam // tuỳ thuộc vào từng thông điệp ); Hàm có kiểu thực hiện là CALLBACK có nghĩa là hàm sẽ được gọi lặp đi lặp mỗi khi có một tác động từ người dùng hay từ một ứng dụng khác lên cửa sổ. Giá trị trả về có kiểu long (LRESULT) sẽ do người lập trình viết ứng với mỗi xử lý của các thông điệp. Và như vậy, đối với các ứng dụng dạng Win32 Application, hầu như chúng ta chỉ cần tìm hiểu các thông điệp, với các giá trị gởi kèm là có thể lập trình để xử lý cho các thao tác cần thiết. Ví dụ để xử lý menu, control thì ta cần hiểu về thông điệp WM_COMMAND. Các bài học tiếp theo sẽ giúp sinh viên tìm hiểu tuần tự các đặc trưng chính của Windows đồng thời cài đặt một vài dạng thông điệp liên quan. 21
- Lập trình Windows 1.3.3. Một số lưu ý Để kết thúc bài giới thiệu này, chúng tôi xin nêu lên một số lưu ý mà các bạn cần quan tâm khi học lập trình bằng ngôn ngữ C trên MS Windows. So với các môn học khác thì môn học này đòi hỏi các bạn thực hành nhiều. Sau khi nắm được từng đặc điểm chính của các ứng dụng trên Windows, chúng ta cần phải cài đặt chúng thông qua một số ví dụ, bài tập – từ đó sẽ dễ dàng hiểu rõ lại về lý thuyết, và như vậy có thể xây dựng các ứng dụng khác một cách nhanh chóng. Thứ hai là vấn đề áp dụng lý thuyết vào việc xây dựng các ứng dụng cụ thể, việc tham khảo các tài liệu,v.v . Giáo trình này chỉ giúp các bạn nắm được cách lập trình trên Windows một cách cơ bản. Và đây không phải là tài liệu tham chiếu. Sau khi nắm được các điểm chính, khi lập trình, các bạn nên tham chiếu với thư viện MSDN của Microsoft để có những hướng dẫn đầy đủ nhất. Và như vậy, nếu đã làm quen được với việc lập trình các ứng dụng trên Windows chỉ với các hàm API, các bạn có thể tự tìm hiểu để có thể lập trình bằng MFC hay một ngôn ngữ nào khác (hiện tại bên cạnh việc xây dựng các ứng dụng Windows-based, việc tìm hiểu và xây dựng các ứng dụng NET- based, ví dụ VB.NET, C#, là rất cần thiết). Cuối cùng là một vấn đề rất cơ bản, cách thức trình bày code khi lập trình. Chúng ta đã làm quen với việc lập trình qua nhiều môn học, tuy nhiên theo những gì chúng tôi nhận thấy, phần đông sinh viên vẫn rất hạn chế trong việc trình bày source code. Việc trình bày source code không phải là yếu tố quyết 22
- Bài 1. Tổng quan định tính đúng sai hay hiệu quả của chương trình, nhưng lại là yếu tố quyết định kết quả và khả năng lập trình của các lập trình viên. Hầu như rằng các lập trình viên giỏi luôn trình bày source code của họ rất rõ ràng, logic. Trong các ứng dụng viết bằng C trên MS Windows, người ta thường dùng cú pháp Hungary (Hungarian Notation) để đặt tên biến, hàm, trong chương trình. Các biến thường bắt đầu bằng các ký tự chữ thường thể hiện cho kiểu dữ liệu sau đó là tên biến, như nCmdLine là một biến kiểu số nguyên int (number), hwndEdit là một biến định danh (handle) của cửa sổ edit kiểu HWND. Về tên biến và tên hàm sẽ viết hoa ký tự đầu tiên trong mỗi từ (word), các từ viết liền nhau. Ví dụ biến szWindowClass, hàm InitInstance, hàm CreateWindow, . Tương tự, việc canh lề các dòng code cũng cần lưu ý. Bằng cách đặt cùng lề cho các dòng lệnh cùng cấp, ta dễ dàng tìm ra các lệnh nào liên hệ với nhau như thế nào, Và như vậy rất dễ dàng khi chỉnh sửa các lỗi về phú pháp. Ví dụ như ở chương trình mẫu ở trên, ở hàm About (dòng 127 đến 144) việc rẽ nhánh các thông điệp (lệnh switch ở dòng 130) cùng cấp với lệnh return (dòng 143), các trường hợp (case) của các thông điệp thì cùng cấp (dòng 132, 134),.v.v . 1.4. TÓM TẮT Trong bài học đầu tiên này, chúng ta đã hiểu được thế nào là một ứng dụng được lập trình trên Windows, so với một ứng dụng dạng console (MS-DOS), trong đó tập trung giới thiệu về cấu trúc một project dạng Win32 Application gồm hai hàm chính WinMain (1.3.1) và WndProc (1.3.2). 23
- Lập trình Windows Chúng ta cũng đã tìm hiểu về cách thức tạo lập dạng project này trên IDE MS Visual C++ 6.0 (1.2.1), phân tích giao diện IDE (1.2.2) và quy trình build tạo file khả thi của project (1.2.3). Phần lý thuyết chung của bài học (1.1) trình bày các đặc điểm chính của MS Windows và các ứng dụng trên hệ điều hành này, gồm cơ chế xử lý thông điệp, lập trình giao diện đồ họa GUI, lập trình giao tiếp thiết bị đồ họa GDI, cơ chế quản lý vùng nhớ động và thư viện liên kết động DLL. Trong đó, xử lý thông điệp (liên quan tới hàm vòng lặp nhận thông điệp trong hàm WinMain rồi chuyển cho hàm WndProc xử lý) là quan trọng nhất. Ở các bài học tiếp theo chúng ta sẽ tìm hiểu lần lượt các đặc điểm nêu trên qua các kiến thức lý thuyết cơ bản, và áp dụng trên các ví dụ cụ thể. 1.5. CÂU HỎI ÔN TẬP – BÀI TẬP 1.5.1. Trình bày các đặc điểm chính của các ứng dụng trên MS Windows? Các đặc điểm này khác với trên môi trường MS- DOS như thế nào? 1.5.2. Cho biết quy trình biên dịch và liên kết để tạo file khả thi dạng Win32 Application. Quy trình này khác quy trình build thông thường trên MS-DOS như thế nào? 1.5.3. Hàm WinMain của một ứng dụng thông thường dạng Win32 Application gồm những thao tác gì? Việc gọi vòng lặp thông điệp có ý nghĩa như thế nào? 24
- Bài 1. Tổng quan 1.5.4. Tạo lập project Win32 Application theo 3 dạng khác nhau: “An empty project.”, “A simple Win32 Application.” và “A typical “Hello World!” application.” - xem bước 3 phần 1.2.1. Tìm hiểu cấu trúc thể hiện khác nhau của các project trên cửa sổ Workspace, cũng như các file được tạo lập theo từng chọn lựa (trên thư mục chứa project tương ứng). 1.5.5. Với project “A typical “Hello World!” application.”, liệt kê các kiểu dữ liệu và các hàm trong ứng dụng. Sau đó tìm hiểu về chúng trong MSDN. 25