Bài giảng Lập trình Windows (Phần 2)
Bạn đang xem 20 trang mẫu của tài liệu "Bài giảng Lập trình Windows (Phần 2)", để 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_phan_2.pdf
Nội dung text: Bài giảng Lập trình Windows (Phần 2)
- Bài giảng môn học: Lâp̣ triǹ h Windows Chƣơng 3: Hê ̣thố ng CSDL Registry 1. Khái niệm và vai trò của CSDL Registry 1.1 Các khóa, các hive Registry là nơi lƣu trữ tất cả các các loại cấu trúc dữ liệu. Cấu hình hệ thống Windows, cấu hình phần cứng máy tính, cấu hình thông tin về các chƣơng trình ứng dụng dựa trên Win32, và các thiết lập ngƣời dùng khác đều đƣợc lƣu trong Registry. Ví dụ, bất cứ một phần cứng máy tính nào thay đổi đều làm chức năng Plug and Play (Cắm và chạy) khởi tạo ngay và làm thay đổi luôn cấu hình trong Registry. Registry lƣu trữ tất cả các thiết lập về cấu trúc bộ nhớ, phần cứng, thiết bị ngoại vi, và các thành phần liên quan đến mạng. Bạn sẽ tìm thấy ở đó nhiều hơn những thiết lập cần thiết trong các tệp khởi tạo ban đầu Từ Win98 về sau, Windows có sử dụng Registry Checker để tự quét Registry, nếu không thấy gì, nó tự lƣu backup một lần trong ngày, nếu tìm thấy lỗi sẽ sửa có thể sửa bằng cách thay thế bản Registry đã backup gần nhất còn tốt. Registry Checker tối ƣu hoá và nén file backup thành công mỗi lần khởi động máy. Nó còn làm một loạt các việc linh tinh nhƣ loại bỏ những khoảng trống không dùng trong Registry, tối ƣu hoá 41
- Bài giảng môn học: Lâp̣ triǹ h Windows Các tệp Registry của Windows. Registry hiện tại bao gồm 3 tệp chính: 1. Tệp USER.DAT Dùng để lƣu trữ những xác lập ngƣời sử dụng đối với các phần mềm. 2. Tệp SYSTEM.DAT Dùng để lƣu trữ những xác lập liên quan tới máy tính và phần cứng. 3. Tệp Policy.pol System policies đƣợc thiết kế để chuẩn bị cho việc ghi đè bất cứ thiết lập đã đƣợc chứa trong 2 thành phần registry khác nhau. System policies có thể chứa dữ liệu bổ sung đặc trƣng tới mạng hay môi trƣờng tổ hợp nhƣ đã đƣợc cài đặt bởi network administrator. Bản thân System policies cũng đã đƣợc chứa trong tệp Policy.pol. Không nhƣ SYSTEM.DAT và USER.DAT, Policy.pol không phải là thành phần bắt buộc của phần cài đặt Windows. Các khóa chính trong một CSDL Registry: + HKEY_LOCAL_MACHINE chứa các thông tin về cấu hình vật lý của hệ thống cùng với các phần mềm đã đƣợc cài đặt trên hệ thống. + HKEY_USERS: chứa các thông tin cấu hình của tài khoản ngƣời dùng + HKEY_CURRENT_CONFIG: chứa các thông tin thiết lập của hệ thống hiện tại chẳng hạn nhƣ độ phân giải màn hình hay font chữ. + HKEY_CLASS_ROOT: chứa các thông tin ánh xạ từ các kiểu file sang các ứng dụng mở chúng. HKEY_CURRENT_USER: chứa các thông tin về các tài khoản trên hệ thống, chẳng hạn nhƣ các biến môi trƣờng, các máy in và các tùy chọn ứng dụng khác. 1.2 Các kiểu dữ liệu Lời khuyên của Microsoft về những công cụ xử lý registry 42
- Bài giảng môn học: Lâp̣ triǹ h Windows Phƣơng pháp Thiết lập Phần lớn thiết lập hệ thống SYSTEM. Ví dụ bạn sử dụng Display Control Panel Properties để sửa các thành phần của mục appearance System Policy Editor Thiết lập ngƣời dùng, vài thiết lập hệ thống. Các chƣơng trình tiện ích thứ 3 Thiết lập chi tiết ứng dụng Bạn có thể đã sử dụng Registry Editor để thay đổi Registry bằng tay. Tôi thƣờng dùng Norton Registry Editor vì nó còn có thêm chức năng khác, ví dụ nhƣ tìm và thay thế đối với các thành phần của Registry. Từ các phần mềm Registry Editor trên, ta nhận thấy registry đƣợc bố trí thành các nhánh lớn. Tại mỗi nhánh có các khoá SUBKEY. Tại các SUBKEY dữ liệu đƣợc lƣu ở các dạng: 1. String (Dạng chuỗi) 2. Numeric (Dạng số) 3. Binary (Dạng nhị phân) 4. Expanded String (Dạng chuỗi mở rộng) 5. MultiString (Dạng chuỗi tổng hợp) (Nếu bạn dùng Registry Editor - REGEDIT. EXE thì sẽ gọi tên khác là DWORD) 2. Quản lý CSDL Registry Khi lập trình đối với Registry, bạn phải thực hiện hết sức thận trọng, sao lƣu các tệp này thƣờng xuyên để tránh lỗi đáng tiếc, phải mất công cài lại thì cũng rất mất thời gian.Sử dụng các hàm API đối với Registry cũng xin hết sức thận trọng. 2.1 Thay đổi khó a Để thay đổi giá trị của một khóa trong CSDL Registry chúng ta sử dụng hàm LONG RegSetValueEx(HKEY hKey, LPCTSTR lpValueName, DWORD reserved, DWORD dwType, CONST BYTE * lpData, CONST cbData). 2.2 Thêm mớ i khó a Để làm việc với các khóa trong CSDL Registry đầu tiên chúng ta sẽ quan tâm tới các hàm thêm khóa mới: LONG RegOpenKeyEx(HKEY hKey, LPCTSTR lpSubKey, DWORD ulOptions, REGSAM samDesired, PHKEY phkResult). Trong đó tham số thứ nhất là handle trỏ tới khóa đang mở, tham số phkResult trỏ tới một biến có kiểu HKEY cho khóa có thểmớ mới, lpSubKey là tên của subkey mà chúng ta muốn mở, thông thƣờng có thể là một đƣờng dẫn chăng hạn nhƣ “Microsoft\WindowsNT\CurrentVersion”. Giá trị NULL cho biến này có nghĩa là một khóa bằng giá trị hKey sẽ đƣợc sinh ra. Biến ulOptions là biến dự trữ và có giá trị bằng 0. Biến samDesired là mặt nạ truy cập mô tả giá trị bảo mật cho khóa mới, có thể là kết hợp của các giá trị hằng số KEY_ALL_ACCESS, KEY_WRITE, KEY_QUERY_VALUE, KEY_ENUMERATE_SUBKEYS. Giá trị trả về của hàm thƣờng là ERROR_SUCCESS. Cũng có thể dùng hàm LONG RegCreateKeyEx( HKEY hKey, LPCTSTR lpSubKey, DWORD Reserved, LPTSTR lpClass, DWORD dwOptions, REGSAM samDesired, PSECURITY_ATTRIBUTES lpSecurityAttributes, PHKEY phkResult, LPDWORD lpdwDisposition) để tạo khóa mới. 43
- Bài giảng môn học: Lâp̣ triǹ h Windows 2.3 Liêṭ kê cá c khó a Hàm liệt kê các khóa trong CSDL Registry là hàm LONG RegEnumKeyEx(HKEY hKey, DWORD dwIndex, LPTSTR lpName, LPDWORD lpcbName, LPDWORD lpReserved, LPTSTR lpClass, LPDWORD lpcbClass, PFILETIME lpftLastWriteTime). 3. Can thiêp̣ Windows qua Registry Hầu nhƣ tất cả các thay đổi, thiết lập của Windows đều có thể đƣợc thực hiện thông qua việc thiết lập các giá trị trong CSDL Registry, vấn đề cốt lõi là chúng ta cần nắm đƣợc khóa và giá trị cần thay đổi tƣơng ứng. 3.1 Thay đổi giao diêṇ Để ngăn cấm không cho ngƣời dùng thay đổi Wallpaper chúng ta có thể thay đổi giá trị khóa:“HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/Windows/policies/ActiveDeskto p” với giá trị DWORD là 1. Để chỉ định file Wallpaper của Windows ta có thể sửa cáckhóa “HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Policies\ System” với các khóa là Wallpaper, WallpaperStyle và giá trị kiểu REG_SZ (String) là đƣờng dẫn tới file ảnh. 3.2 Thay đổi cá c thiết lâp̣ đố i vớ i cá c ổ điã Để thay đổi thiết lập với các ổ đĩa ta thực hiện với cáckhóa HKEY_LOCAL_MACHINE\SYSTEM\MountedDevices có tên là MountedDevices nhƣ hình sau: 3.3 Thay đổi cá c thiết lâp̣ vớ i ngƣời dùng Các biến môi trƣờng của một ngƣời dùng nằm trong phần “HKEY_CURRENT_USER\Environment”, còn cho cả hệ thống nằm trong phần “HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment”. Bài tập: Bài tập 1: Viết chƣơng trình thay đổi giao diện Windows bằng cách sử dụng can thiệp qua Registry. Bài tập 2: Viết chƣơng trình thay đổi các biến môi trƣờng của Windows bằng cách sử dụng can thiệp qua Registry. 44
- Bài giảng môn học: Lâp̣ triǹ h Windows Chƣơng 4: Quản lý các tiến triǹ h và luồng 1. Các tiến trình và luồng trên Windows Tất cả các tiến trình (process) của Windows đều có một hoặc nhiều luồng (thread) và luồng chính là đơn vị thực thi cơ sở nhất của Windows. Các luồng đƣợc lập lịch dựa trên các nhân tố: sự sẵn sàng của các tài nguyên nhƣ CPU và bộ nhớ vật lý, độ ƣu tiên. Windows hỗ trợ kiến trúc đa xử lý đối xứng SMP (Symmetric MultiProcessing) bắt đầu từ phiên bản NT4, do đó các luồng có thể chạy trên cac CPU riêng rẽ trong cùng một hệ thống. Trên quan điểm của lập trình viên mỗi tiến trình sẽ bao gồm các tài nguyên: + Một hoặc nhiều luồng + Một không gian bộ nhớ ảo riêng. + Một hoặc nhiều đoạn mã, bao gồm của các đoạn mã trong các file DLL. + Một hoặc nhiều phân đoạn dữ liệu chứa các dữ liệu toàn cục. + Các giá trị biến môi trƣờng + Vùng nhớ Heap + Các tài nguyên khác chẳng hạn nhƣ các handle và các heap đã mở sẵn. Mỗi luồng trong một tiến trình sẽ chia sẻ mã chƣơng trình, các biến toàn cục, các biến môi trƣờng, các tài nguyên. Mỗi luồng sẽ đƣợc lập lịch một cách riêng rẽ và có các thành phần sau: + Một ngăn xếp các lời gọi tới các thủ tục, các ngắt, các quản lý biệt lệ, và bộ nhớ + Một mảng TLS (Threa Local Storage) các con trỏ để có thể cấp phát bộ nhớ lƣu trữ dữ liệu cho luồng. + Một tham số trên stack, từ lúc bắt đầu tạo ra luồng, đƣợc sử dụng riêng chomỗi luồng. + Một cấu trúc ngữ cảnh, đƣợc quản lý bởi nhân. 45
- Bài giảng môn học: Lâp̣ triǹ h Windows 2. Các thao tác với tiến trình 2.1. Tạo tiến trình Hàm cơ bản để quản lý các tiến trình của Windows là hàm CreateProcess(), hàm này tạo ra một tiến trình với một luồng đơn. Tham số mà hàm cần là tên file chƣơng trình sẽ thực hiện. Chúng ta có thể thấy có nhiều đề cập tới khái niệm tiến trình cha, tiến trình con nhƣng thực sự thì quan hệ này không đƣợc quản lý bởi Windows. Windows đơn thuần chỉ tham chiếu tới các tiến trình tại ra một tiến trình con mà nó là tiến trình cha. Hàm CreateProcess() có 10 tham số: BOOL CreateProcess ( LPCTSTR lpApplicationName, LPTSTR lpCommandLine, LPSECURITY_ATTRIBUTES lpsaProcess, LPSECURITY_ATTRIBUTES lpsaThread, BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment, 46
- Bài giảng môn học: Lâp̣ triǹ h Windows LPCTSTR lpCurDir, LPSTARTUPINFO lpStartupInfo, LPPROCESS_INFORMATION lpProcInfo) 2.2. Kết thú c và thoá t khỏi môṭ tiến triǹ h Sau khi tiến trình kết thúc, tiến trình, hay chính xác hơn là một luồng chạy trong tiến trình sẽ gọi tới hàm ExitProcess() để kết thúc tiến trình. Hàm này không trả về giá trị mà thay vào đó sẽ kết thúc tất cả các luồng của tiến trình. Hàm có thể gọi tới để lấy mã kết thúc một tiến trình là GetExitCodeProcess(). 2.3. Các thao tác với biến môi trƣờng của Windows Để thao tác với các biến môi trƣờng của Windows ta dùng hai hàm sau: DWORD GetEnvironmentVariable ( LPCTSTR lpName, LPTSTR lpValue, DWORD cchValue) BOOL SetEnvironmentVariable ( LPCTSTR lpName, LPCTSTR lpValue) 2.4. Ví dụ: Ghi nhâṭ ký thời gian thƣc̣ hiêṇ củ a cá c tiến triǹ h #include "EvryThng.h" int _tmain (int argc, LPTSTR argv []) { STARTUPINFO StartUp; PROCESS_INFORMATION ProcInfo; union { /* Structure required for file time arithmetic. */ LONGLONG li; FILETIME ft; } CreateTime, ExitTime, ElapsedTime; FILETIME KernelTime, UserTime; SYSTEMTIME ElTiSys, KeTiSys, UsTiSys, StartTimeSys, ExitTimeSys; LPTSTR targv = SkipArg (GetCommandLine ()); OSVERSIONINFO OSVer; BOOL IsNT; HANDLE hProc; OSVer.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); 47
- Bài giảng môn học: Lâp̣ triǹ h Windows GetVersionEx (&OSVer); IsNT = (OSVer.dwPlatformId == VER_PLATFORM_WIN32_NT); /* NT (all versions) returns VER_PLATFORM_WIN32_NT. */ GetStartupInfo (&StartUp); GetSystemTime (&StartTimeSys); /* Execute the command line; wait for process to complete. */ CreateProcess (NULL, targv, NULL, NULL, TRUE, NORMAL_PRIORITY_CLASS, NULL, NULL, &StartUp, &ProcInfo); /* Assure that we have all REQUIRED access to the process. */ DuplicateHandle (GetCurrentProcess (), ProcInfo.hProcess, GetCurrentProcess (), &hProc, PROCESS_QUERY_INFORMATION | SYNCHRONIZE, FALSE, 0); WaitForSingleObject (hProc, INFINITE); GetSystemTime (&ExitTimeSys); if (IsNT) { /* W NT. Elapsed, Kernel, & User times. */ GetProcessTimes (hProc, &CreateTime.ft, &ExitTime.ft, &KernelTime, &UserTime); ElapsedTime.li = ExitTime.li - CreateTime.li; FileTimeToSystemTime (&ElapsedTime.ft, &ElTiSys); FileTimeToSystemTime (&KernelTime, &KeTiSys); FileTimeToSystemTime (&UserTime, &UsTiSys); _tprintf (_T ("Real Time: %02d:%02d:%02d:%03d\n"), ElTiSys.wHour, ElTiSys.wMinute, ElTiSys.wSecond, ElTiSys.wMilliseconds); _tprintf (_T ("User Time: %02d:%02d:%02d:%03d\n"), UsTiSys.wHour, UsTiSys.wMinute, UsTiSys.wSecond, UsTiSys.wMilliseconds); _tprintf (_T ("Sys Time: %02d:%02d:%02d:%03d\n"), KeTiSys.wHour, KeTiSys.wMinute, KeTiSys.wSecond, KeTiSys.wMilliseconds); } else { 48
- Bài giảng môn học: Lâp̣ triǹ h Windows /* Windows 9x and CE. Elapsed time only. */ } CloseHandle (ProcInfo.hThread); CloseHandle (ProcInfo.hProcess); CloseHandle (hProc); return 0; } 3. Quản lý luồng (thread) trên Windows 3.1. Các khái niệm cơ bản Trong phần trƣớc chúng ta đã xem xét cách thức một luồng thực hiện kết thúc một tiến trình. Các luồng trong một tiến trình chia sẻ chung dữ liệu và mã lệnh, vì thế về bản chất các luồng đó cũng có vùng nhớ riêng của chúng. Windows đáp ứng điều này bằng một số cách sau: + Mỗi luồng có một stack của riêng nó cho các lời gọi hàm và các xử lý khác + Lời gọi tiến trình có thể truyền một biến, con trò, tới thời gian tạo ra luồng. + Mỗi luồng có thể cấp phát TLS của riêng nó. 3.2. Mô hiǹ h Boss/Worker và cá c mô hiǹ h khá c Lệnh grepMT minh họa cho mô hình Boss/Worker. Luồng boss (luồng chính) sẽ gán các tác vụ cho luồng worker để thực hiện. Mỗi luồng worker sẽ đƣợc cho một file để tìm kiếm và worker sẽ trả về giá trị của nó cho boss thread. Xem thêm ví dụ sắp xếp trộn để hiểu rõmô hình này. 3.3. Bô ̣nhớ dành cho luồng Các luồng có thể cần phải cấp phát và quản lý bộ nhớ của riêng nó và bảo vệ các vùng nhớ đó khỏi các luồng khác. Điều này đƣợc thực hiện qua các TLS, có thể minh họa bằng hình vẽ sau: 49
- Bài giảng môn học: Lâp̣ triǹ h Windows 3.4. Độ ƣu tiên và các trạng thái của luồng Các luồng thuộc về nhân của Windows luôn có độ ƣu tiên cao nhất khi sẵn sàng để thực hiện. Một luồng đƣợc coi là không sẵn sàng để thực hiện nếu nó ở trạng thái chờ, treo, hay bị block bởi một lý do nào đó. 4. Môṭ số ví du ̣về tiến triǹ h và luồng 4.1. Tìm kiếm song song với các tiến trình #include "EvryThng.h" int _tmain (DWORD argc, LPTSTR argv []) /* Create a separate process to search each file on the command line. Each process is given a temporary file, in the current directory, to receive the results. */ { HANDLE hTempFile; SECURITY_ATTRIBUTES StdOutSA = /* SA for inheritable handle. */ {sizeof (SECURITY_ATTRIBUTES), NULL, TRUE}; TCHAR CommandLine [MAX_PATH + 100]; STARTUPINFO StartUpSearch, StartUp; PROCESS_INFORMATION ProcessInfo; DWORD iProc, ExCode; HANDLE *hProc; /* Pointer to an array of proc handles. */ typedef struct {TCHAR TempFile [MAX_PATH];} PROCFILE; PROCFILE *ProcFile; /* Pointer to array of temp file names. */ GetStartupInfo (&StartUpSearch); GetStartupInfo (&StartUp); ProcFile = malloc ((argc - 2) * sizeof (PROCFILE)); hProc = malloc ((argc - 2) * sizeof (HANDLE)); /* Create a separate "grep" process for each file. */ for (iProc = 0; iProc < argc - 2; iProc++) { _stprintf (CommandLine, _T ("%s%s %s"), _T ("grep "), argv [1], argv [iProc + 2]); GetTempFileName (_T ("."), _T ("gtm"), 0, ProcFile [iProc].TempFile); /* For search results. */ hTempFile = /* This handle is inheritable */ 50
- Bài giảng môn học: Lâp̣ triǹ h Windows CreateFile (ProcFile [iProc].TempFile, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, &StdOutSA, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); StartUpSearch.dwFlags = STARTF_USESTDHANDLES; StartUpSearch.hStdOutput = hTempFile; StartUpSearch.hStdError = hTempFile; StartUpSearch.hStdInput = GetStdHandle (STD_INPUT_HANDLE); /* Create a process to execute the command line. */ CreateProcess (NULL, CommandLine, NULL, NULL, TRUE, 0, NULL, NULL, &StartUpSearch, &ProcessInfo); /* Close unwanted handles. */ CloseHandle (hTempFile); CloseHandle (ProcessInfo.hThread); hProc [iProc] = ProcessInfo.hProcess; } /* Processes are all running. Wait for them to complete. */ for (iProc = 0; iProc 3) _tprintf (_T ("%s:\n"), argv [iProc + 2]); fflush (stdout); /* Multiple processes use stdout. */ _stprintf (CommandLine, _T ("%s%s"), _T ("cat "), ProcFile [iProc].TempFile); CreateProcess (NULL, CommandLine, NULL, NULL, TRUE, 0, NULL, NULL, &StartUp, &ProcessInfo); WaitForSingleObject (ProcessInfo.hProcess, INFINITE); CloseHandle (ProcessInfo.hProcess); 51
- Bài giảng môn học: Lâp̣ triǹ h Windows CloseHandle (ProcessInfo.hThread); } CloseHandle (hProc [iProc]); DeleteFile (ProcFile [iProc].TempFile); } free (ProcFile); free (hProc); return 0; } 4.2. Thuâṭ toá n sắ p xếp trôṇ bằng đa luồng #include "EvryThng.h" #define DATALEN 56 /* Key: 8 bytes; Data: 56 bytes. */ #define KEYLEN 8 typedef struct _RECORD { CHAR Key [KEYLEN]; TCHAR Data [DATALEN]; } RECORD; #define RECSIZE sizeof (RECORD) typedef RECORD * LPRECORD; typedef struct _THREADARG { /* Thread argument */ DWORD iTh; /* Thread number: 0, 1, 2, */ LPRECORD LowRec; /* Low record */ LPRECORD HighRec; /* High record */ } THREADARG, *PTHREADARG; static int KeyCompare (LPCTSTR, LPCTSTR); static DWORD WINAPI ThSort (PTHREADARG pThArg); static DWORD nRec; /* Total number of records to be sorted. */ static HANDLE * ThreadHandle; int _tmain (int argc, LPTSTR argv []) { HANDLE hFile; LPRECORD pRecords = NULL; 52
- Bài giảng môn học: Lâp̣ triǹ h Windows DWORD FsLow, nRead, LowRecNo, nRecTh, NPr, ThId, iTh; BOOL NoPrint; int iFF, iNP; PTHREADARG ThArg; LPTSTR StringEnd; iNP = Options (argc, argv, _T ("n"), &NoPrint, NULL); iFF = iNP + 1; NPr = _ttoi (argv [iNP]); /* Number of threads. */ hFile = CreateFile (argv [iFF], GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); FsLow = GetFileSize (hFile, NULL); nRec = FsLow / RECSIZE; /* Total number of records. */ nRecTh = nRec / NPr; /* Records per thread. */ /* Allocate thread args and handle array and space for the file. Read the complete file. */ ThArg = malloc (NPr * sizeof (THREADARG)); /* Thread args. */ ThreadHandle = malloc (NPr * sizeof (HANDLE)); pRecords = malloc (FsLow + sizeof (TCHAR)); ReadFile (hFile, pRecords, FsLow, &nRead, NULL); CloseHandle (hFile); LowRecNo = 0; /* Create the sorting threads. */ for (iTh = 0; iTh < NPr; iTh++) { ThArg [iTh].iTh = iTh; ThArg [iTh].LowRec = pRecords + LowRecNo; ThArg [iTh].HighRec = pRecords + (LowRecNo + nRecTh); LowRecNo += nRecTh; ThreadHandle [iTh] = (HANDLE) _beginthreadex (NULL, 0, ThSort, &ThArg [iTh], CREATE_SUSPENDED, &ThId); } 53
- Bài giảng môn học: Lâp̣ triǹ h Windows for (iTh = 0; iTh iTh; First = pThArg->LowRec; RecsInGrp = pThArg->HighRec - First; qsort (First, RecsInGrp, RECSIZE, KeyCompare); while ((MyNumber % GrpSize) == 0 && RecsInGrp < nRec) { /* Merge with the adjacent sorted array. */ WaitForSingleObject ( ThreadHandle [MyNumber + TwoToI], INFINITE); MergeArrays (First, First + RecsInGrp); RecsInGrp *= 2; GrpSize *= 2; TwoToI *= 2; } _endthreadex (0); 54
- Bài giảng môn học: Lâp̣ triǹ h Windows return 0; /* Suppress a warning message. */ } static VOID MergeArrays (LPRECORD p1, LPRECORD p2) { DWORD iRec = 0, nRecs, i1 = 0, i2 = 0; LPRECORD pDest, p1Hold, pDestHold; nRecs = p2 - p1; pDest = pDestHold = malloc (2 * nRecs * RECSIZE); p1Hold = p1; while (i1 = nRecs) memcpy (pDest, p2, RECSIZE * (nRecs - i2)); else memcpy (pDest, p1, RECSIZE * (nRecs - i1)); memcpy (p1Hold, pDestHold, 2 * nRecs * RECSIZE); free (pDestHold); return; } Bài tập: Bài tập 1: Viết chƣơng trình hiển thị tất cả các thông tin về tiến trình và luồng của một tiến trình đang chạy. 55
- Bài giảng môn học: Lâp̣ triǹ h Windows Chƣơng 5: Các dịch vụ của Windows 1. Tổng quan về dic̣ h vu ̣trên Windows Các dịch vụ của Windows, ban đầu đƣợc gọi là các dịch vụ NT, cung cấp khả năng quản lý đòi hỏi các chƣơng trình theo kiểu server phải đƣợc chạy trên dòng lệnh, hoặc vào thời điểm hệ thống khởi động, trƣớc khi bất cứ ngƣời dùng nào đăng nhập, và cũng có thể thực hiện tạm dừng, khôi phục, kết thúc. Các dịch vụ đƣợc đƣa ra nhằm đáp ứng yêu cầu này. Để quản lý các dịch vụ chúng ta có thể dùng công cụ Service Control Manager đƣợ cung cấp sẵn bởi Windows, công cụ này sẽ thực hiện ba bƣớc để quản lý một dịch vụ: + Gọi tới hàm main() của dịch vụ + Chuyển đổi từ hàm main() sang hàm ServiceMain() + Ghi các con trỏ quản lý để có thể đáp ứng lại các lệnh từ SCM 2. Các thành phần của một dịch vụ 2.1 Hàm main() Hàm này đƣợc gọi bởi SCM, có nhiệm vụ là khai báo dịch vụ với SCM và bắt đầu thực hiện công việc của một dịch vụ. 2.2 Hàm ServiceMain() Hàm ServiceMain() là một dạng tƣơng tự nhƣ hàm main() với các tham số tƣơng tự, nó thực hiện các tác vụ chính của một dịch vụ. 2.3 Kiểm soá t dic̣ h vu ̣qua cá c Handler Để kiểm soát dịch vụ, chúng ta sử dụng các hàm sau: DWORD WINAPI HandlerEx ( DWORD dwControl, DWORD dwEventType, 56
- Bài giảng môn học: Lâp̣ triǹ h Windows LPVOID lpEventData, LPVOID lpContext) 3. Ví du: dịch vụ đơn giản trên Windows #include "EvryThng.h" #include "ClntSrvr.h" #define UPDATE_TIME 1000 /* One second between updates. */ VOID LogEvent (LPCTSTR, DWORD, BOOL); void WINAPI ServiceMain (DWORD argc, LPTSTR argv []); VOID WINAPI ServerCtrlHandlerEx(DWORD, DWORD, LPVOID, LPVOID); void UpdateStatus (int, int); /* Calls SetServiceStatus. */ int ServiceSpecific (int, LPTSTR *); /* Former main program. */ volatile static BOOL ShutDown = FALSE, PauseFlag = FALSE; static SERVICE_STATUS hServStatus; static SERVICE_STATUS_HANDLE hSStat; /* Handle to set status. */ static LPTSTR ServiceName = _T ("SocketCommandLineService"); static LPTSTR LogFileName = _T ("CommandLineServiceLog.txt"); /* Main routine that starts the service control dispatcher. */ VOID _tmain (int argc, LPTSTR argv []) { SERVICE_TABLE_ENTRY DispatchTable [] = { { ServiceName, ServiceMain }, { NULL, NULL } }; StartServiceCtrlDispatcher (DispatchTable); return 0; } /* ServiceMain entry point, called when the service is created. */ void WINAPI ServiceMain (DWORD argc, LPTSTR argv []) 57
- Bài giảng môn học: Lâp̣ triǹ h Windows { DWORD i, Context = 1; /* Set the current directory and open a log file, appending to an existing file. */ /* Set all server status data members. */ hServStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; hServStatus.dwCurrentState = SERVICE_START_PENDING; hServStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_PAUSE_CONTINUE; hServStatus.dwWin32ExitCode = ERROR_SERVICE_SPECIF0C_ERROR; hServStatus.dwServiceSpecificExitCode = 0; hServStatus.dwCheckPoint = 0; hServStatus.dwWaitHint = 2 * CS_TIMEOUT; hSStat = RegisterServiceCtrlHandlerEx (ServiceName, ServerCtrlHandler, &Context); SetServiceStatus (hSStat, &hServStatus); /* Start service-specific work; generic work is complete. */ if (ServiceSpecific (argc, argv) != 0) { hServStatus.dwCurrentState = SERVICE_STOPPED; hServStatus.dwServiceSpecificExitCode = 1; /* Server initialization failed. */ SetServiceStatus (hSStat, &hServStatus); return; } /* We will only return here when the ServiceSpecific function completes, indicating system shutdown. */ UpdateStatus (SERVICE_STOPPED, 0); return; } 58
- Bài giảng môn học: Lâp̣ triǹ h Windows void UpdateStatus (int NewStatus, int Check) /* Set a new service status and checkpoint either specific value or increment. */ { if (Check = 0) hServStatus.dwCurrentState = NewStatus; SetServiceStatus (hSStat, &hServStatus); return; } /* Control handler function, invoked by the SCM to run */ /* in the same thread as the main program. */ /* The last three parameters are not used, and the pre-NT5 */ /* handlers would also work in this example. */ VOID WINAPI ServerCtrlHandlerEx (DWORD Control, DWORD EventType, LPVOID lpEventData, LPVOID lpContext) { swsitch (Control) { case SERVICE_CONTROL_SHUTDOWN: case SERVICE_CONTROL_STOP: ShutDown = TRUE; /* Set the global shutdown flag. */ UpdateStatus (SERVICE_STOP_PENDING, -1); break; case SERVICE_CONTROL_PAUSE: PauseFlag = TRUE; /* Interrogated periodically. */ break; case SERVICE_CONTROL_CONTINUE: PauseFlag = FALSE; break; case SERVICE_CONTROL_INTERROGATE: break; default: if (Control > 127 && Control < 256) /* User defined. */ 59
- Bài giảng môn học: Lâp̣ triǹ h Windows break; } UpdateStatus (-1, -1); /* Increment checkpoint. */ return; } /* This is the service-specific function, or "main," and is called from the more generic ServiceMain. In general, you can take any server, such as ServerNP.c, and rename "main" as "ServiceSpecific"; putting code right here. But some changes are required to update status. */ int ServiceSpecific (int argc, LPTSTR argv []) { UpdateStatus (-1, -1); /* Increment the checkpoint. */ /* Initialize system */ /* Be sure to update the checkpoint periodically. */ return 0; } 4. Quản lý các dịch vụ của Windows 4.1 Các phƣơng pháp kiểm soát các dịch vụ của Windows Để quản lý các dịch vụ của Windows ta có hai cách: một là dùng công cụ SCM, hailà gõ trực tiếp các lệnh trên dòng lệnh. 4.2 Ví dụ : Điều khiển cá c dic̣ h vu ̣củ a Windows #include "EvryThng.h" static SC_HANDLE hScm; static BOOL Debug; int _tmain (int argc, LPTSTR argv []) { BOOL Exit = FALSE; TCHAR Command [MAX_COMMAND_LINE + 10], *pc; 60
- Bài giảng môn học: Lâp̣ triǹ h Windows DWORD i, LocArgc; /* Local argc. */ TCHAR argstr [MAX_ARG] [MAX_COMMAND_LINE]; LPTSTR pArgs [MAX_ARG]; /* Prepare the local "argv" array as pointers to strings. */ for (i = 0; i < MAX_ARG; i++) pArgs [i] = argstr [i]; /* Open the SC Control Manager on the local machine. */ hScm = OpenSCManager (NULL, NULL, SC_MANAGER_ALL_ACCESS); /* Main command processing loop. */ _tprintf (_T ("\nWindows Service Management")); while (!Exit) { _tprintf (_T ("\nSM$")); _fgetts (Command, MAX_COMMAND_LINE, stdin); Similar to JobShell if (_tcscmp (argstr [0], _T ("create")) == 0) { Create (LocArgc, pArgs, Command); } Similarly for all commands } CloseServiceHandle (hScm); return 0; } int Create (int argc, LPTSTR argv [], LPTSTR Command) { /* Create a new service as a "demand start" service: argv [1]: service Name argv [2]: display Name argv [3]: binary executable */ SC_HANDLE hSc; TCHAR CurrentDir [MAX_PATH + 1], Executable [MAX_PATH + 1]; 61
- Bài giảng môn học: Lâp̣ triǹ h Windows hSc = CreateService (hScm, argv [1], argv [2], SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL, Executable, NULL, NULL, NULL, NULL, NULL); return 0; } /* Delete a service argv [1]: ServiceName to delete. */ int Delete (int argc, LPTSTR argv [], LPTSTR Command) { SC_HANDLE hSc; hSc = OpenService (hScm, argv [1], DELETE); DeleteService (hSc); CloseServiceHandle (hSc); return 0; } /* Start a named service argv [1]: service name to start. */ int Start (int argc, LPTSTR argv [], LPTSTR Command) { SC_HANDLE hSc; TCHAR WorkingDir [MAX_PATH + 1]; LPTSTR pWorkingDir = WorkingDir; LPTSTR argvStart [] = {argv [1], WorkingDir}; GetCurrentDirectory (MAX_PATH + 1, WorkingDir); hSc = OpenService(hScm, argv [1], SERVICE_ALL_ACCESS); /* Start the service with one arg, the working directory. */ /* Note: The service name agrees, by default, with the name */ /* associated with the handle, hSc, by OpenService. */ /* But, the ServiceMain function does not verify this. */ StartService (hSc, 2, argvStart); CloseServiceHandle (hSc); 62
- Bài giảng môn học: Lâp̣ triǹ h Windows return 0; } /* Control a named service. argv [1]: service name to control. argv [2]: Control command: stop, pause, resume, interrogate. */ static LPCTSTR Commands [] = {"stop," "pause," "resume," "interrogate," "user"}; static DWORD Controls [] = { SERVICE_CONTROL_STOP, SERVICE_CONTROL_PAUSE, SERVICE_CONTROL_CONTINUE, SERVICE_CONTROL_INTERROGATE, 128}; int Control (int argc, LPTSTR argv [], LPTSTR Command) { SC_HANDLE hSc; SERVICE_STATUS ServiceStatus; DWORD dwControl, i; BOOL Found = FALSE; for (i= 0; i < sizeof (Controls)/sizeof (DWORD) && !Found; i++) Found = (_tcscmp (Commands [i], argv [2]) == 0); if (!Found) { _tprintf (_T ("\nIllegal Control Command %s"), argv [1]); return 1; } dwControl = Controls [i - 1]; hSc = OpenService(hScm, argv [1], SERVICE_INTERROGATE | SERVICE_PAUSE_CONTINUE | SERVICE_STOP | SERVICE_USER_DEFINED_CONTROL | SERVICE_QUERY_STATUS); ControlService (hSc, dwControl, &ServiceStatus); if (dwControl == SERVICE_CONTROL_INTERROGATE) { QueryServiceStatus (hSc, &ServiceStatus); 63
- Bài giảng môn học: Lâp̣ triǹ h Windows printf (_T ("Status from QueryServiceStatus\n")); printf (_T ("Service Status\n")); Print all other status information } if (hSc != NULL) CloseServiceHandle (hSc); return 0; } Bài tập: Bài tập 1: Viết dịch vụ ghi lại nhật ký các lần login và sử dụng hệ thống trên Windows. Bài tập 2: Viết chƣơng trình quản lý các dịch vụ của Windows. 64
- Bài giảng môn học: Lâp̣ triǹ h Windows Chƣơng 6: Lâp̣ trình Socket 1. Khái niệm sockets trên Windows Socket: là một thực thể logic đại diện cho kết nối giữa các máy tính trên một hệ thống mạng. Tất cả các truyền thông giƣ̃a các máy tính trên môṭ hê ̣thống maṇ g đƣơc̣ thƣc̣ hiêṇ thông qua socket . Có hai mô hình lập trình mạng : mô hình client /server và mô hình peer 2 peer (mạng ngang hàng). Theo mô hình client/server: Server: mở môṭ dic̣ h vu ̣bằng cách taọ ra môṭ đối tƣơṇ g ServerSocket vớ i tham số là điạ chỉ cổng để kết nối , sau đó nhâṇ kết nối tƣ̀ phía client bằng cách sƣ̉ duṇ g hàm accept (), lấy các luồng dữ liệu vào /ra của client để thƣc̣ hiêṇ truyền dƣ̃ liêụ , thƣc̣ hiêṇ các xƣ̉ lý cần thiết , trả kết quả cho client và đóng kết nối tớ i client, dịch vụ kết thúc. 2. Các hàm sockets phía server Các hàm socket phía server mà chúng ta cần quan tâm gồm có: int bind ( SOCKET s, const struct sockaddr *saddr, int namelen); int listen (SOCKET s, int nQueueSize); SOCKET accept ( SOCKET s, LPSOCKADDR lpAddr, LPINT lpAddrLen); Ví dụ: struct sockaddr_in SrvSAddr; /* Server address struct. */ struct sockaddr_in ConnectAddr; SOCKET SrvSock, sockio; SrvSock = socket (AF_INET, SOCK_STREAM, 0); SrvSAddr.sin_family = AF_INET; SrvSAddr.sin_addr.s_addr = htonl (INADDR_ANY); SrvSAddr.sin_port = htons (SERVER_PORT); bind (SrvSock, (struct sockaddr *) &SrvSAddr, sizeof SrvSAddr); listen (SrvSock, 5); AddrLen = sizeof (ConnectAddr); 65
- Bài giảng môn học: Lâp̣ triǹ h Windows sockio = accept (SrvSock, (struct sockaddr *) &ConnectAddr, &AddrLen); Receive requests and send responses shutdown (sockio); closesocket (sockio); 3. Các hàm sockets phía client Các hàm socket phía client cần phải quan tâm là: int connect ( SOCKET s, LPSOCKADDR lpName, int nNameLen); int send ( SOCKET s, const char * lpBuffer, int nBufferLen, int nFlags); Ví dụ: SOCKET ClientSock; ClientSock = socket (AF_INET, SOCK_STREAM, 0); memset (&ClientSAddr, 0, sizeof (ClientSAddr)); ClientSAddr.sin_family = AF_INET; ClientSAddr.sin_addr.s_addr = inet_addr (argv [1]); ClientSAddr.sin_port = htons (SERVER_PORT); ConVal = connect (ClientSock, (struct sockaddr *) &ClientSAddr, sizeof (ClientSAddr)); 4. Ứng dụng mang đơn giản 4.1 Phía server #define _NOEXCLUSIONS #include "EvryThng.h" #include "ClntSrvr.h" /* Defines request and response records. */ struct sockaddr_in SrvSAddr; 66
- Bài giảng môn học: Lâp̣ triǹ h Windows /* Server's socket address structure. */ struct sockaddr_in ConnectSAddr; /* Connected socket. */ WSADATA WSStartData; /* Socket library data structure. */ typedef struct SERVER_ARG_TAG { /* Server thread arguments. */ volatile DWORD number; volatile SOCKET sock; volatile DWORD status; /* Explained in main thread comments. */ volatile HANDLE srv_thd; HINSTANCE dlhandle; /* Shared library handle. */ } SERVER_ARG; volatile static ShutFlag = FALSE; static SOCKET SrvSock, ConnectSock; int _tmain (DWORD argc, LPCTSTR argv []) { /* Server listening and connected sockets. */ BOOL Done = FALSE; DWORD ith, tstatus, ThId; SERVER_ARG srv_arg [MAX_CLIENTS]; HANDLE hAcceptTh = NULL; HINSTANCE hDll = NULL; /* Initialize the WSA library, Ver 2.0, although 1.1 will work. */ WSAStartup (MAKEWORD (2, 0), &WSStartData); /* Open command library DLL if specified on command line. */ if (argc > 1) hDll = LoadLibrary (argv [1]); /* Initialize thread arg array. */ for (ith = 0; ith < MAX_CLIENTS; ith++) { srv_arg [ith].number = ith; srv_arg [ith].status = 0; srv_arg [ith].sock = 0; srv_arg [ith].dlhandle = hDll; srv_arg [ith].srv_thd = NULL; 67
- Bài giảng môn học: Lâp̣ triǹ h Windows } /* Follow standard server socket/bind/listen/accept sequence. */ SrvSock = socket (AF_INET, SOCK_STREAM, 0); SrvSAddr.sin_family = AF_INET; SrvSAddr.sin_addr.s_addr = htonl ( INADDR_ANY ); SrvSAddr.sin_port = htons ( SERVER_PORT ); bind (SrvSock, (struct sockaddr *) &SrvSAddr, sizeof SrvSAddr); listen (SrvSock, MAX_CLIENTS); /* Main thread becomes listening/connecting/monitoring thread. */ /* Find an empty slot in the server thread arg array. */ /* status values: 0 slot is free; 1 thread stopped; 2 thread running; 3 stop entire system. */ while (!ShutFlag) { for (ith = 0; ith < MAX_CLIENTS && !ShutFlag; ) { if (srv_arg [ith].status==1 || srv_arg [ith].status==3) { /* Thread stopped, normally or by shutdown request. */ WaitForSingleObject (srv_arg[ith].srv_thd INFINITE); CloseHandle (srv_arg[ith].srv_thd); if (srv_arg [ith].status == 3) ShutFlag = TRUE; else srv_arg [ith].status = 0; /* Free thread slot. */ } if (srv_arg [ith].status == 0 || ShutFlag) break; ith = (ith + 1) % MAX_CLIENTS; if (ith == 0) Sleep (1000); /* Break the polling loop. */ /* Alternative: use an event to signal a free slot. */ } /* Wait for a connection on this socket. */ /* Separate thread so we can poll the ShutFlag flag. */ hAcceptTh = (HANDLE)_beginthreadex (NULL, 0, AcceptTh, &srv_arg [ith], 0, &ThId); 68
- Bài giảng môn học: Lâp̣ triǹ h Windows while (!ShutFlag) { tstatus = WaitForSingleObject (hAcceptTh, CS_TIMEOUT); if (tstatus == WAIT_OBJECT_0) break; /* Connection made. */ } CloseHandle (hAcceptTh); hAcceptTh = NULL; /* Prepare for next connection. */ } _tprintf (_T ("Server shutdown. Wait for all srvr threads\n")); /* Terminate the accept thread if it is still running. * See the Web site for more detail on this shutdown logic. */ if (hDll != NULL) FreeLibrary (hDll); if (hAcceptTh != NULL) TerminateThread (hAcceptTh, 0); /* Wait for any active server threads to terminate. */ for (ith = 0; ith sock = accept (SrvSock, /* This is a blocking call. */ 69
- Bài giảng môn học: Lâp̣ triǹ h Windows (struct sockaddr *) &ConnectSAddr, &AddrLen); /* A new connection. Create a server thread. */ pThArg->status = 2; pThArg->srv_thd = (HANDLE) _beginthreadex (NULL, 0, Server, pThArg, 0, &ThId); return 0; /* Server thread remains running. */ } static DWORD WINAPI Server (SERVER_ARG * pThArg) /* Server thread function. Thread created on demand. */ { /* Each thread keeps its own request, response, and bookkeeping data structures on the stack. */ /* Standard declarations from serverNP omitted */ SOCKET ConnectSock; int Disconnect = 0, i; int (*dl_addr)(char *, char *); char *ws = " \0\t\n"; /* White space. */ GetStartupInfo (&StartInfoCh); ConnectSock = pThArg->sock; /* Create a temp file name. */ sprintf (TempFile, "%s%d%s", "ServerTemp", pThArg->number, ".tmp"); while (!Done && !ShutFlag) { /* Main command loop. */ Disconnect = ReceiveRequestMessage (&Request, ConnectSock); Done = Disconnect || (strcmp (Request.Record, "$Quit") == 0) || (strcmp (Request.Record, "$ShutDownServer") == 0); if (Done) continue; /* Stop this thread on "$Quit" or "$ShutDownServer". */ hTmpFile = CreateFile (TempFile, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, &TempSA, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); 70
- Bài giảng môn học: Lâp̣ triǹ h Windows /* Check for a DLL command. For simplicity, shared */ /* library commands take precedence over process commands. First, extract the command name. */ i = strcspn (Request.Record, ws); /* Length of token. */ memcpy (sys_command, Request.Record, i); sys_command [i] = '\0'; dl_addr = NULL; /* Will be set if GetProcAddress succeeds. */ if (pThArg->dlhandle != NULL) { /* Try server "in process." */ dl_addr = (int (*)(char *, char *)) GetProcAddress (pThArg->dlhandle, sys_command); if (dl_addr != NULL) __try { /* Protect server process from exceptions in DLL. */ (*dl_addr) (Request.Record, TempFile); } __except (EXCEPTION_EXECUTE_HANDLER { ReportError (_T ("Exception in DLL"), 0, FALSE); } } } if (dl_addr == NULL) { /* No in-process support. */ /* Create a process to carry out the command. */ /* Same as in serverNP */ } /* Same as in serverNP */ } /* End of main command loop. Get next command. */ /* End of command loop. Free resources; exit from the thread. */ _tprintf (_T ("Shutting down server# %d\n"), pThArg->number); shutdown (ConnectSock, 2); closesocket (ConnectSock); 71
- Bài giảng môn học: Lâp̣ triǹ h Windows pThArg->status = 1; if (strcmp (Request.Record, "$ShutDownServer") == 0) { pThArg->status = 3; ShutFlag = TRUE; } return pThArg->status; } 4.2 Phía client #define _NOEXCLUSIONS /* Required to include socket definitions. */ #include "EvryThng.h" #include "ClntSrvr.h" /* Defines request and response records. */ /* Message functions for request and response. */ /* ReceiveResponseMessage also prints the received messages. */ static DWORD SendRequestMessage (REQUEST *, SOCKET); static DWORD ReceiveResponseMessage (RESPONSE *, SOCKET); struct sockaddr_in ClientSAddr; /* Clients's socket address. */ int _tmain (DWORD argc, LPTSTR argv []) { SOCKET ClientSock = INVALID_SOCKET; REQUEST Request; /* See ClntSrvr.h. */ RESPONSE Response; /* See ClntSrvr.h. */ WSADATA WSStartData; /* Socket library data structure. */ BOOL Quit = FALSE; DWORD ConVal, j; TCHAR PromptMsg [] = _T ("\nEnter Command> "); TCHAR Req [MAX_RQRS_LEN]; TCHAR QuitMsg [] = _T ("$Quit"); /* Request: shut down client. */ TCHAR ShutMsg [] = _T ("$ShutDownServer"); /* Stop all threads. */ CHAR DefaultIPAddr [] = "127.0.0.1"; /* Local system. */ 72
- Bài giảng môn học: Lâp̣ triǹ h Windows /* Initialize the WSA library, Ver 2.0, although 1.1 will work. */ WSAStartup (MAKEWORD (2, 0), &WSStartData); /* Connect to the server. */ /* Follow the standard client socket/connect sequence. */ ClientSock = socket (AF_INET, SOCK_STREAM, 0); memset (&ClientSAddr, 0, sizeof (ClientSAddr)); ClientSAddr.sin_family = AF_INET; if (argc >= 2) ClientSAddr.sin_addr.s_addr = inet_addr (argv [1]); else ClientSAddr.sin_addr.s_addr = inet_addr (DefaultIPAddr); ClientSAddr.sin_port = htons (SERVER_PORT); /* Defined as 1070. */ connect (ClientSock, (struct sockaddr *) &ClientSAddr, sizeof (ClientSAddr)); /* Main loop to prompt user, send request, receive response. */ while (!Quit) { _tprintf (_T ("%s"), PromptMsg); /* Generic input, but command to server must be ASCII. */ _fgetts (Req, MAX_RQRS_LEN-1, stdin); for (j = 0; j <= _tcslen (Req); j++) Request.Record [j] = Req [j]; /* Get rid of the new line at the end. */ Request.Record [strlen (Request.Record) - 1] = '\0'; if (strcmp (Request.Record, QuitMsg) == 0 || strcmp (Request.Record, ShutMsg) == 0) Quit = TRUE; SendRequestMessage (&Request, ClientSock); ReceiveResponseMessage (&Response, ClientSock); } shutdown (ClientSock, 2); /* Disallow sends and receives. */ closesocket (ClientSock); WSACleanup (); 73
- Bài giảng môn học: Lâp̣ triǹ h Windows _tprintf (_T ("\n Leaving client\n")); return 0; } 5. Windows Sockets 2.0 Là phiên bản mới nhất của Windows cho lập trình mạng theo mô hình socket. Bài tập: Bài tập 1: Viết chƣơng trình client/server cho phép client nhập vào một xâu, gửi sang server, server sẽ trả về số từ có trong xâu. 74
- Bài giảng môn học: Lâp̣ triǹ h Windows Chƣơng 7: Thƣ viêṇ liên kết đôṇ g Các thƣ viện liên kết động Dynamic -link Libararies là môṭ trong các phần tƣ̉ quan troṇ g nhất của hê ̣điều hành Windows . Hầu hết các thao tác truy câp̣ điã cƣ́ ng trên Windows đều đƣơc̣ thƣc̣ hiêṇ bở i các chƣơng trình hoăc̣ các file liên kết đôṇ g . Cho đến thờ i điểm này chúng ta đa ̃ viết rất nhiều c ác chƣơng trình và bây giờ là lúc chúng ta xem xét việc viết các thƣ viện liên kết đôṇ g. Rất nhiều nguyên tắc trong viêc̣ viết các chƣơng trình cũng đƣơc̣ áp duṇ g trong viêc̣ viết các thƣ viêṇ song có môṭ số thay đổi quan trong.̣ 7.1. Khái niệm và ứng dụng của thƣ viện liên kết động Nhƣ các baṇ thấy môṭ chƣơng trình trên Windows là môṭ file chaỵ thƣờ ng taọ ra môṭ hoăc̣ môṭ số cƣ̉ a sổ chƣơng trình và sƣ̉ duṇ g môṭ vòng lăp̣ thông điêp̣ để nhâṇ các thông tin input tƣ̀ ngƣờ i dùng . Các thƣ viện liên kết động thƣờng không phải là các file chạy trực tiếp và chúng thƣờng không nhận các thông điệp . Chúng thƣờng là các file riêng biệt chứa các hàm có thể đƣợc gọi bởi các chƣơng trình và các thƣ viện khác để thực hiện một công việc cụ thể nào đó . Môṭ thƣ viêṇ liên kết đôṇ g thƣờ ng đƣơc̣ nap̣ vào môṭ nhớ để thƣc̣ hiêṇ khi môṭ module chƣơng trình khác gọi tới một hàm trong thƣ viện. Thuâṭ ngƣ̃ liên kết đô ̣ ng “dynamic link” đề câp̣ tớ i quá trình mà Windows sƣ̉ duṇ g để liên kết môṭ lờ i goị hàm trong môṭ module chƣơng trình vớ i các hàm thƣc̣ sƣ ̣ nằm trong môṭ thƣ viêṇ liên kết đôṇ g. Các liên kết tĩnh đƣợc thực hiện khi trình biên dịch tiến hành bƣớc liên kết (link) các file object (.obj), các thƣ viện run-time (*.lib) và sử dụng một chƣơng trình biên dịch tài nguyên để tạo thành một file chạy .exe. Quá trình liên kết động không diễn ra vào lúc biên dic̣ h và liên kết chƣơng trình mà diễn ra vào lúc chƣơng trình chạy. 7.2. Hệ thống thƣ viện liên kết động của Windows Các thƣ viện Kernel 32.dll, user32.dll và gdi 32.dll, rất nhiều các file điều khiển khác chẳng haṇ nhƣ keyboard .drv, system.drv và mouse .drv cũng nhƣ các trình điều khiển card màn hình và máy in đều là các thƣ viện liên kết động . Các thƣ viện này đều alf các thƣ viện mà hầu hết các chƣơng trình trên Windows đều sử dụng. Môṭ vài thƣ viêṇ liên kết động (chẳng haṇ nhƣ các font chƣ̃ ) đƣơc̣ goị là các thƣ viêṇ tài nguyên (resource only ). Các thƣ viện này chỉ chứa dữ liệu (thƣờ ng là dƣớ i daṇ g các tài nguyên) và không chứa mã chƣơng trình . Do đó môṭ trong các muc̣ đích của c ác thƣ viện liên kết đôṇ g là cung cấp các hàm và các tài nguyên có thể đƣơc̣ sƣ̉ duṇ g bở i các chƣơng trình khác. Trong các hê ̣điều hành ngày xƣa (kiểu nhƣ DOS) chỉ có hệ điều hành mới chứa các thủ tục mà các chƣơng trìn h khác có thể goị đến để hoàn thành môṭ công viêc̣ gì đó . Trên Windows quá trình môṭ module chƣơng trình goị tớ i môṭ hàm trong môṭ module chƣơng trình khác là rất thƣờng xuyên . Bằng cách viết các Dll chúng ta có thêm các mở rôṇ g cho hê ̣điều hành. Măc̣ dù môṭ module thƣ viêṇ liên kết đôṇ g có thể có bất cƣ́ phần tên mở rôṇ g nào (chẳng haṇ nhƣ .exe hay .com) nhƣng phần tên mở rôṇ g chuẩn của các thƣ viêṇ liên kết đôṇ g trên Windows là .dll. Chỉ có các thƣ viện liên kết động có phần tên mở rộng là dll mới đƣợc tự đôṇ g nap̣ vào bô ̣nhớ bở i Windows các thƣ viêṇ có phần tên mở rôṇ g khác se ̃ đƣơc̣ nap̣ thông qua viêc̣ goị tớ i các hàm LoadLibrary hoăc̣ LoadLibraryEx . Chúng ta th ƣờng thấy các thƣ viện liên kết động đƣợc sử dụng trong các chƣơng trình lớ n. Chẳng haṇ nhƣ chúng ta viết môṭ gói phần mềm kế toán lớ n chƣ́ a môṭ vài chƣơng trình khác nhau. Các chƣơng trình này sử dụng nhiều thủ tục chung do đó chúng ta có thể kết hơp̣ 75
- Bài giảng môn học: Lâp̣ triǹ h Windows các hàm này vào một thƣ viện liên kết tĩnh (*.lib) sau đó thêm vào mỗi module chƣơng trình trong quá trình biên dic̣ h các chƣơng trình này . Tuy nhiên cách tiếp câṇ đó se ̃ là lañ g phí vì mỗi chƣơng trình se ̃ chƣ́ a các đoaṇ ma ̃ giống nhau và hơn nƣ̃a nếu chúng ta thay đổi môṭ hàm nào đó trong thƣ viện chúng ta sẽ phải liên kết lại tất cả các chƣơng trình có sử dụng thƣ viện đó. Nhƣng nếu chúng ta cho tất cả các hàm đó vào môṭ thƣ viêṇ liên kết đôṇ g chẳng haṇ nhƣ account.dll chẳng haṇ thì se ̃ giải quyết đƣơc̣ cả hai vấn đề trên . Chỉ có module cần thiết chứa tất cả các ma ̃ của các hàm đƣơc̣ sƣ̉ duṇ g bở i tất cả các chƣơng trình điều nà y se ̃ đòi hỏi ít không gian điã cƣ́ ng hơn và đồng thờ i đòi hỏi ít bô ̣nhớ hơn khi nhiều chƣơng trình cùng chaỵ đồng thờ i, hơn nƣ̃a chúng ta có thể thay đổi các cài đăṭ của các hàm trong thƣ viêṇ mà không cần thiết phải liên kết laị thƣ viêṇ vớ i các chƣơng trình. Các thƣ viện liên kết động cũng có thể là cá sản phẩm thƣơng mại , hiêṇ nay có rất nhiều hãng chuyên cung cấp các hàm thƣ viện dành cho phát triển một loại sản phẩm phần mềm nào đó chẳng haṇ nhƣ DirectX là môṭ tâp̣ các thƣ viêṇ liên kết đôṇ g dành cho viêc̣ viết các chƣơng trình đồ hoạ cao cấp. Thƣ viêṇ : môṭ lời ngàn ý. (One word, many meanings) Môṭ phần các nhầm lâñ đối vớ i các thƣ viêṇ liên kết đôṇ g là việc sử dụng của thuật ngữ thƣ viêṇ (library) trong môṭ số các khung cảnh khác nhau . Bên caṇ h các thƣ viêṇ liên kết đôṇ g chúng ta còn nói đến các thƣ viêṇ đối tƣơṇ g và các thƣ viêṇ import. Môṭ thƣ viêṇ đối tƣơṇ g là môṭ file có phần mở rôṇ g là .lib chƣ́ a các đoaṇ ma ̃ chƣơng trình sẽ đƣợc thêm vào file .exe trong quá trình liên kết tiñ h . Chẳng haṇ trong VC++ thƣ viêṇ đối tƣơṇ g run-time thƣờ ng liên kết vớ i các chƣơng trình là libc.lib. Môṭ thƣ viện import là một dạng đặc biệt của các file thƣ viện đối tƣợng. Giống nhƣ các thƣ viêṇ đối tƣơṇ g các thƣ viêṇ import có phần mở rôṇ g là .lib và đƣơc̣ sƣ̉ duṇ g bở i trình biên dịch để giải quyết các lời gọi hàm trong các c hƣơng trình của chúng ta viết ra . Tuy nhiên các thƣ viêṇ import này không chƣ́ a các ma ̃ chƣơng trình . Thay vào đó chúng cung cấp cho trình liên kết các thông tin cần thiết đẻ thiết lâp̣ các bảng chuyển (relocation table) trong file .exe để thƣc̣ hiêṇ các liên kết đôṇ g . Các file kernel 32.lib, user32.lib, gdi32.lib đi kèm vớ i các trình biên dic̣ h của Microsoft đều là các thƣ viêṇ import . Nếu chúng ta goị tớ i môṭ hàm chẳng haṇ nhƣu Rectangle thì file gdi 32.lib sẽ báo cho trình liên kết biết rằng đó là một hàm trong thƣ viêṇ liên kết đôṇ g gdi 32.dll. Thông tin này se ̃ đƣơc̣ chƣ́ a trong file chƣơng trình (*.exe) để Windows có thể thƣc̣ hiêṇ các liên kết đôṇ g khi chƣơng trình của chúng ta đƣơc̣ thƣc̣ hiêṇ . Các thƣ viện đối tƣợng và các thƣ viện import đều đƣợc sử dụng trong quá trình phát triển các chƣơng trình . Các thƣ viện liên kết động đƣợc sử dụng trong quá trình chƣơng trình chạy. Môṭ thƣ viêṇ liên kết đôṇ g phải sẵn có trên đĩa để chƣơng trình có thể sử dụng nó khi chạy. Khi Windows cần nap̣ môṭ module DLL trƣớ c khi chaỵ môṭ chƣơng trình đòi hỏi sƣ̉ dụng hàm trong nó file thƣ viện phải nằm cùng thƣ mục với file .exe, trong thƣ muc̣ hiêṇ thời, trong thƣ muc̣ system của hê ̣điều hành hoăc̣ nằm trong thƣ muc̣ có trong biến môi trƣờ ng PATH của hê ̣điều hành (hê ̣điều hành se ̃ tiến hành tìm kiếm thƣ viêṇ theo thƣ́ tƣ ̣ đó). 7.3. Các bƣớc tạo một thƣ viện DLL Măc̣ dù ý tƣởng của các thƣ viện liên kết động là có thể sử dụng với nhiều ứng dụng nhƣng chúng ta se ̃ viết môṭ ƣ́ ng duṇ g demo sƣ̉ duṇ g môṭ thƣ viêṇ liên kết đôṇ g đơn giản. 76
- Bài giảng môn học: Lâp̣ triǹ h Windows Chúng ta sẽ tạo một một thƣ viện liên kết động là edrlib .dll (Easy drawing routine ). Hàm thƣ viện này sẽ chỉ chứa một hàm đơn giản để thực hiện công việc vẽ ra một xâu trong ứng dụng demo của chúng ta. Để taọ ra môṭ thƣ viêṇ liên kết đôṇ g chúng ta cần có môṭ cách tiếp câṇ khác s o vớ i cách mà chúng ta vẫn dùng để viết các ứng dụng . VC++ phân biêṭ giƣ̃a các khái niêṃ “workspace” và “project” . Môṭ project thƣờ ng là môṭ ƣ́ ng duṇ g hoăc̣ môṭ thƣ viêṇ liên kết đôṇ g . Môṭ workspace có thể gồm nhiều project . Cho đến thờ i điểm này chúng ta mớ i chỉ viết các workspace chỉ có 1 project. Trong phần này chúng ta se ̃ taọ môṭ workspace có hai project , môṭ cho viêc̣ taọ dll và môṭ cho viêc̣ goị tớ i file dll đó. Các bƣớc tạo ứng dụng với Visual Studio .NET 2003 nhƣ sau: 1. Tạo 1 Solution (Dll1 chẳng haṇ ) rỗng (Blank Solution) 2. Thêm môṭ project chƣ́ a file dll cho ƣ́ ng duṇ g (chọn New Project Win32 Project) và gõ tên của Project là SimpleDll. 3. Trong hôp̣ thoaị Application Setting chọn DLL (Application Type) và chọn mục Empty. 4. Thêm file ma ̃ nguồn cho Project và gõ nôị dung của file dll vào: /* simpledll.h header file */ #ifdef __cplusplus #define EXPORT extern "C" __declspec (dllexport) #else #define EXPORT __declspec (dllexport) #endif EXPORT BOOL CALLBACK EdrCenterTextA (HDC, PRECT, PCSTR) ; EXPORT BOOL CALLBACK EdrCenterTextW (HDC, PRECT, PCWSTR) ; #ifdef UNICODE #define EdrCenterText EdrCenterTextW #else #define EdrCenterText EdrCenterTextA #endif 77
- Bài giảng môn học: Lâp̣ triǹ h Windows /* simpledll.c Easy Drawing Routine Library module (c) Charles Petzold, 1998 */ #include #include "simpledll.h" int WINAPI DllMain (HINSTANCE hInstance, DWORD fdwReason, PVOID pvReserved) { return TRUE ; } EXPORT BOOL CALLBACK EdrCenterTextA (HDC hdc, PRECT prc, PCSTR pString) { int iLength ; SIZE size ; iLength = lstrlenA (pString) ; GetTextExtentPoint32A (hdc, pString, iLength, &size) ; return TextOutA (hdc, (prc->right - prc->left - size.cx) / 2, (prc->bottom - prc->top - size.cy) / 2, pString, iLength) ; } EXPORT BOOL CALLBACK EdrCenterTextW (HDC hdc, PRECT prc, PCWSTR pString) { int iLength ; SIZE size ; 78
- Bài giảng môn học: Lâp̣ triǹ h Windows iLength = lstrlenW (pString) ; GetTextExtentPoint32W (hdc, pString, iLength, &size) ; return TextOutW (hdc, (prc->right - prc->left - size.cx) / 2, (prc->bottom - prc->top - size.cy) / 2, pString, iLength) ; } 5. Tạo một Project Win 32 Project, sau đó choṇ kiểu Project là Application, tạo file .c cho ƣ́ ng duṇ g và gõ nôị dung ƣ́ ng duṇ g vào nhƣ sau: #include #include " \\SimpleDll\\simpledll.h" LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static TCHAR szAppName[] = TEXT ("StrProg") ; HWND hwnd ; MSG msg ; WNDCLASS wndclass ; wndclass.style = CS_HREDRAW | CS_VREDRAW ; wndclass.lpfnWndProc = WndProc ; wndclass.cbClsExtra = 0 ; wndclass.cbWndExtra = 0 ; wndclass.hInstance = hInstance ; wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ; wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ; wndclass.lpszMenuName = NULL ; wndclass.lpszClassName = szAppName ; if (!RegisterClass (&wndclass)) { MessageBox (NULL, TEXT ("This program requires Windows NT!"), szAppName, MB_ICONERROR) ; 79
- Bài giảng môn học: Lâp̣ triǹ h Windows return 0 ; } hwnd = CreateWindow (szAppName, TEXT ("DLL Demonstration Program"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL) ; ShowWindow (hwnd, iCmdShow) ; UpdateWindow (hwnd) ; while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } return msg.wParam ; } LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { HDC hdc ; PAINTSTRUCT ps ; RECT rect ; switch (message) { case WM_PAINT: hdc = BeginPaint (hwnd, &ps) ; GetClientRect (hwnd, &rect) ; EdrCenterText (hdc, &rect, TEXT ("This string was displayed by a DLL")) ; 80
- Bài giảng môn học: Lâp̣ triǹ h Windows EndPaint (hwnd, &ps) ; return 0 ; case WM_DESTROY: PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; } 6. Đặt chế độ dịch là Project thứ hai phụ thuôc̣ (dependencies) vào Project thƣ́ nhất và thƣ muc̣ Output cùng taṃ của hai Project là /Release. 7. Dịch và chạy chƣơng trình. Ở đây chúng ta có hai phiên bản của một hàm trong thƣ viện liên kết động đƣợc sử dụng, điều này cho phép dùng các hàm có hỗ trợ Unicode trong trƣờng hợp hệ thống có hỗ trợ và ngƣợc lại sử dụng một hàm không có Unicode , thƣờ ng tên của hàm se ̃ có thêm chƣ̃ W nếu có hỗ trợ Unicode và A nếu không. Đồng thời chúng ta cũng t hấy trong ma ̃ của thƣ viêṇ có môṭ hàm DllMain , hàm này có vai trò tƣơng tƣ ̣ nhƣ hàm WinMain trong môṭ chƣơng trình . Tác dụng của hàm DllMain là khở i taọ và thu hồi bô ̣nhớ và nhƣ̃ng thƣ́ liên quan khác , chúng ta sẽ bàn tới vấn đề này ở cuối chƣơng, hiêṇ taị chỉ cần return TRUE là ổn. Điều bí ẩn còn laị có le ̃ là ở điṇ h danh EXPORT . Các hàm trong một thƣ viện liên kết đôṇ g đƣơc̣ sƣ̉ duṇ g bở i các ƣ́ ng duṇ g khác phải đƣơc̣ xuất khẩu . Điều này không liên quan tới các vấn đề thƣơng mại thông thƣờng mà chỉ là một chỉ thị đảm bảo trình biên dịch sẽ thêm tên hàm vào thƣ viện import simpledll .lib để trình liên kết có thể đƣa các thông tin phù hơp̣ vào chƣơng trình (file *.exe) để có thể nạp các thƣ viện dll khi chƣơng trình chạy . Điṇ h danh EXPORT còn bao gồm chỉ điṇ h lớ p chƣ́ a __declspec(dllexport) và một chỉ thị tiền xử lý if extern “C” để đề phòng trƣờ ng hơp̣ file header đƣơc̣ biên dic̣ h theo kiểu C++. Điều này ngăn chăṇ trình biên dic̣ h khỏi các lỗi trùng tên của các hàm C ++ và cho phép các thƣ viện liên kết đôṇ g có thể đƣơc̣ sƣ̉ duṇ g bở i cả các chƣơng trình C và C++. Điểm vào và điểm thoá t củ a thƣ viêṇ (Entry and Exit Point) Hàm DllMain đƣợc gọi đến khi thƣ viện liên kết động lần đầu tiên đƣợc nạp vào bộ nhớ để thực hiện và khi nó kết thúc nhiệm vụ (bị loại khỏi bộ nhớ ). Tham số đầu tiên của hàm DllMain là handle tớ i instance của th ƣ viêṇ . Nếu nhƣ thƣ viêṇ có sƣ̉ duṇ g các tài nguyên đòi hỏi một handle instance (chẳng haṇ nhƣ các hôp̣ thoaị ), chúng ta nên lƣu lại hInstance vào môṭ biến toàn cuc̣ . Tham số cuối cùng của hàm DllMain đƣơc̣ dƣ̃ trƣ ̣ dành cho hê ̣t hống sƣ̉ dụng. Tham số fdwReason có thể là môṭ trong 4 giá trị chỉ ra tại sao Windows lại gọi tới hàm DllMain. Trong các muc̣ tiếp theo chúng ta nên nhớ rằng môṭ chƣơng trình đơn có thể đƣơc̣ 81
- Bài giảng môn học: Lâp̣ triǹ h Windows nạp nhiều lần và chạy đồng thời t rên Windows. Mỗi lần chƣơng trình đƣơc̣ nap̣ nó đƣơc̣ xem nhƣ là môṭ tiến trình (process) riêng rẽ. Giá trị của tham số fdwReason bằng DLL _PROCESS_ATTACH chỉ ra rằng thƣ viêṇ liên kết đôṇ g đa ̃ đƣơc̣ ánh xa ̣vào vùng điạ chỉ của mô ̣ t tiến trình . Đây là môṭ đầu mối cho phép thƣ viện thực hiện bất cứ khởi tạo nào đòi hỏi đƣợc phục vụ cho các yêu cầu tiếp theo của tiến trình. Các khởi tạo kiểu này có thể là cấp phát bộ nhớ chẳng hạn . Trong thờ i gian tiến trình đang chạy , DllMain đƣơc̣ goị vớ i môṭ tham số DLL _PROCESS_ATTACH chỉ môṭ lần trong cả thờ i gian tồn taị của process đó . Bất cƣ́ môṭ tiến trình nào khác sƣ̉ duṇ g cùng file DLL se ̃ goị đến hàm DllMain vớ i môṭ giá trị tham số DLL_PROCESS_ATTACH. Nếu nhƣ viêc̣ khở i taọ là thành công DllMain se ̃ trả về môṭ giá tri ̣khác 0, giá trị trả về là 0 sẽ làm cho Windows không chạy chƣơng trình. Nếu giá tri ̣của fdwReason bằng DLL _PROCESS_DETACH thì có nghĩa là chƣơng trình không cần file DLL nữa , và đây là một cơ hội để thƣ viện thực hiện các công việc dọn dẹp của nó . Trên các hê ̣điều hành 32 bit của Windows điều này không thƣc̣ sƣ ̣ cần thiết nhƣng là môṭ thói quen lâp̣ trình tốt. Tƣơng tƣ ̣ khi hàm DllMain đƣơc̣ goị vớ i môṭ giá tri ̣tham số là DLL_THREAD_ATTACH thì có nghiã là môṭ tiến trình sƣ̉ duṇ g thƣ viêṇ đa ̃ taọ ra môṭ luồng (thread) mớ i. Khi luồng kết thúc Windows laị goị tớ i hàm DllMain vớ i tham số là DLL_THREAD_DETACH. Cũng có thể xảy ra trƣờng hợp Windows thực hiện lời gọi tới hàm DllMain với giá trị của tham số fdwReason bằng DLL _THREAD_DETACH mà không thƣc̣ hiêṇ lờ i goị vớ i giá tri ̣DLL _THREAD_ATTACH trƣớ c đó nếu nhƣ thƣ viêṇ liên kết đôṇ g đƣơc̣ gắn vớ i môṭ tiến trình sau khi luồng đa ̃ đƣơc̣ taọ ra. Luồng vâñ tồn taị khi hàm DllMain đƣơc̣ goị đến vớ i tham số DLL_THREAD_DETACH. Nó thậm chí có thể gửi các thông điệp trong tiến trì nh. Nhƣng các luồng không nên sử dụng hàm PostMessage () vì luồng có thể kết thúc trƣớc khi thông điêp̣ đến đích. Chƣơng trình để test thƣ viêṇ liên kết đôṇ g là môṭ chƣơng trình đơn giản và là môṭ Project khác (thuôc̣ loaị Win 32 Application). Chúng ta có thể để các file của 2 Project vào cùng một thƣ mục hoặc riêng rẽ trong 2 thƣ muc̣ . Trong quá trình dic̣ h chƣơng trình file simpledll .dll và simpledll .lib se ̃ đƣơc̣ sinh ra trƣớ c, file simpledll .lib se ̃ đƣơc̣ tƣ ̣ đôṇ g liên kết vớ i chƣơng trình thƣ̉ nghiêṃ và file simpledll.dll se ̃ đƣơc̣ nap̣ vào bô ̣nhớ khi chƣơng trình chaỵ . Cần chú ý là chƣơng trình usedll.exe không chƣ́ a ma ̃ của hàm sƣ̉ duṇ g trong thƣ viêṇ , mã của hàm chỉ đƣợc nạp vào b ộ nhớ khi chƣơng trình chaỵ . Viêc̣ include file simpledll .h cũng giống nhƣ chúng ta include file windows .h, liên kết vớ i file simpledll .lib cũng tƣơng tƣ ̣ nhƣ liên kết vớ i file user 32.lib và liên kết vớ i file simpledll.dll cũng giống nhƣ chƣơng trình liên kết vớ i file user32.dll. Măc̣ dù chúng ta xếp môṭ file DLL là môṭ mở rôṇ g của Windows nhƣng nó cũng là môṭ mở rôṇ g của chƣơng trình ƣ́ ng duṇ g của chúng ta . Tất cả nhƣ̃ng gì file DLL thƣc̣ hiêṇ đều là thay măṭ cho ƣ́ ng duṇ g sƣ̉ duṇ g nó . Chẳng haṇ tất cả các thao tác cấp phát bô ̣nhớ đƣơc̣ kiểm soát bởi chƣơng trình. Bất cƣ́ cƣ̉ a sổ nào nó taọ ra đều sở hƣ̃u bở i chƣơng trình và bất cƣ́ file nào đƣợc mở cũng đƣợc kiểm soát bởi ch ƣơng trình. Nhiều chƣơng trình có thể sƣ̉ duṇ g cùng 82
- Bài giảng môn học: Lâp̣ triǹ h Windows môṭ file dll đồng thờ i, nhƣng Windows se ̃ đóng vai trò lá chắn để ngăn chăṇ các can thiêp̣ lâñ nhau giƣ̃a các ƣ́ ng duṇ g này. Nhiều tiến trình có thể chia sẻ cùng môṭ môṭ đoaṇ ma ̃ trong môṭ thƣ viêṇ liên kết đôṇ g . Tuy nhiên dƣ́ liêụ đƣơc̣ sƣ̉ đuṇ g bở i môṭ thƣ viêṇ DLL se ̃ là khác nhau vớ i các tiến trình khác nhau. Mỗi tiến trình có môṭ không gian điạ chỉ dƣ̃ liêụ của riêng nó để chƣ́ a các dƣ̃ li ệu có thể sƣ̉ duṇ g đến bở i file DLL . Chia sẻ bô ̣nhớ giƣ̃a các tiến trình đòi hỏi môṭ số kỹ thuâṭ khác mà chúng ta sẽ bàn tới trong phần tiếp theo. 7.4. Chia sẻ bô ̣nhớ giƣ̃a cá c thƣ viêṇ liên kết đôṇ g Windows cô lâp̣ các ƣ́ ng duṇ g sƣ̉ duṇ g cùng môṭ thƣ viêṇ liên kết đôṇ g đồng thờ i . Tuy nhiên đôi khi đây không phải là môṭ lƣạ choṇ thích hơp̣ . Chúng ta có thể muốn viết một thƣ viêṇ DLL chƣ́ a môṭ vùng nhớ nào đó có thể đƣơc̣ chia sẻ bở i nhiều ƣ́ n g duṇ g, hoăc̣ giƣ̃a các instance của cùng môṭ ƣ́ ng duṇ g. Điều này liên quan tớ i viê ̣sƣ̉ duṇ g bô ̣nhớ chia sẻ , hay chính xác là một file ánh xạ bộ nhớ (memory-mapped file). Chúng ta sẽ khảo sát một chƣơng trình có tên là strprog (String Program) và thƣ viện đƣơc̣ sƣ̉ duṇ g là strlib (string library). Strlib chƣ́ a 3 hàm mà strprog có thể gọi tới . Và một trong số các hàm của strlib se ̃ sƣ̉ duṇ g môṭ hàm call-back đƣơc̣ điṇ h nghiã trong strprog. Strlib là môṭ thƣ viêṇ liên kết đôṇ g chƣ́ a và làm viêc̣ vớ i môṭ xâu tối đa 256 ký tự. Xâu đƣơc̣ chuyển thành daṇ g ký tƣ ̣ hoa và kiểm soát trong vùng bô ̣nhớ chia sẻ của strlib . Strprog có thể sử dụng 3 hàm của thƣ viện Strlib để cộng , xóa và nhâṇ đƣơc̣ tất cả các xâu hiêṇ taị tƣ̀ strlib. Chƣơng trình strprog có hai muc̣ menu để lƣạ choṇ là Enter và Delete cho phép ngƣời dùng nhập các xâu để thực hiện việc cộng và xóa các xâu . Strprog se ̃ in ra giá tri ̣tất cả cá c xâu hiêṇ đang nằm chƣ́ a trong thƣ viêṇ . Các hàm đƣợc định nghĩa trong thƣ viện Strlib gồm có: EXPORT BOOL CALLBACK AddString (pStringIn) EXPORT BOOL CALLBACK DeleteString (pStringIn) EXPORT int CALLBACK GetStrings (pfnGetStrCallBack, pParam) Hàm thứ nhất sẽ chuyển các xâu thành dạng ký tự hoa và thêm vào danh sách các xâu của thƣ viện. Giá trị trả về của hàm là TRUE (khác 0) nếu nhƣ thành công và FALSE (0) nếu nhƣ xảy ra lỗi: hoăc̣ xâu có đô ̣dài bằng 0 hoăc̣ không cấp phát đƣơc̣ bô ̣nhớ hoăc̣ đa ̃ sƣ̉ duṇ g hết 256 xâu của bô ̣nhớ . Hàm thứ hai sẽ thực hiện xóa bỏ một xâu khỏi danh sách các xâu của thƣ viện nếu khớp và nếu có nhiều xâu khớp thì chỉ xâu đầu tiên bị xóa . Kết quả trả về của hàm là TRUE nếu xóa bỏ thành công và FALSE nếu xâu cần xóa có độ dài bằng 0 hoăc̣ không tìm thấy. Hàm thứ ba là hàm sử dụng để liệt kê các xâu đang có trong thƣ viện , hàm này sử dụng môṭ tham số là môṭ hàm cal l-back chƣ́ a trong chƣơng trình sƣ̉ duṇ g hàm . Hàm này phải đƣợc điṇ h nghiã trong chƣơng trình sƣ̉ duṇ g thƣ viêṇ nhƣ sau: EXPORT BOOL CALLBACK GetStrCallBack (PSTR pString, PVOID pParam) Tham số pfnGetStrCallBack của hàm GetString chỉ tớ i m ột hàm call-back. GetString có thể goị tớ i hàm GetStrCallBack mỗi lần cho mỗi giá tri ̣của môṭ xâu cho tớ i khi hàm này trả về FALSE. GetString se ̃ trả về số lƣơṇ g các xâu đƣơc̣ truyền cho hàm call -back. Biến pParam sẽ là một con trỏ far tớ i dƣ̃ liêụ đƣơc̣ ngƣờ i dùng điṇ h nghiã . 83
- Bài giảng môn học: Lâp̣ triǹ h Windows Tất nhiên ở đây chúng ta cũng sƣ̉ duṇ g hai phiên bản cho mỗi hàm , ANSI và Unicode version. 7.5. Các vấn đề khác về thƣ viện liên kết động Tôi đa ̃ tƣ̀ ng đề câp̣ trong phần đầu củ a chƣơng này là các thƣ viêṇ liên kết đôṇ g không nhâṇ các thông điêp̣ . Tuy nhiên môṭ thƣ viêṇ liên kết đôṇ g có thể goị tớ i hàm GetMessage và PeekMessage. Các thông điệp mà thƣ viện lấy về từ hàng đợi thông điệp qua các hàm nà y thƣc̣ sƣ ̣ là các thông điêp̣ của các chƣơng trình goị tớ i các hàm của thƣ viêṇ . Nói chung thƣ viêṇ làm viêc̣ thay măc̣ cho chƣơng trình goị nó – môṭ qui luâṭ chi phối hầu hết các hàm của Windows mà môṭ thƣ viêṇ có thể goị tớ i. Môṭ thƣ viêṇ liên kết đôṇ g có thể nap̣ các tài nguyên (chẳng haṇ nhƣ các biểu tƣơṇ g chƣơng trình, các xâu và các ảnh bitmap) tƣ̀ file thƣ viêṇ hoăc̣ tƣ̀ các file của chƣơng trình goị tớ i thƣ viêṇ đó . Các hàm nạp tài nguyên đòi hỏi môṭ handle tớ i instance . Nếu nhƣ thƣ viêṇ sƣ̉ dụng handle tới instance của riêng nó (đƣơc̣ truyền cho thƣ viêṇ qua viêc̣ goị tớ i hàm DllMain để khởi tạo nó ) thì thƣ viện có thể nhận đƣợc các tài nguyên từ file riêng c ủa nó. Để nap̣ các tài nguyên từ chƣơng trình gọi tới các hàm của thƣ viện , đòi hỏi handle tớ i instance của chƣơng trình goị hàm. Viêc̣ khai báo các lớ p cƣ̉ a sổ chƣơng trình và taọ ra cƣ̉ a sổ chƣơng trình trong môṭ thƣ viêṇ đ òi hỏi một số thủ thuật . Cả cấu trúc lớp cửa sổ và hàm CreateWindow hoặc CreateWindowEx đều đòi hỏi môṭ handle tớ i môṭ instance của chƣơng trình . Măc̣ dù chúng ta có thể sử dụng hande của thƣ viện trong việc tạo ra các lớ p cƣ̉ a sổ và cƣ̉ a sổ chƣơng trình, các thông điêp̣ cƣ̉ a sổ vâñ đi qua hàng đơị thông điêp̣ của chƣơng trình goị tớ i thƣ viêṇ khi thƣ viêṇ taọ ra cƣ̉ a sổ chƣơng trình . Nếu nhƣ baṇ cần phải taọ ra các lớ p cƣ̉ a sổ và các cƣ̉ a sổ chƣơng trình trong môṭ thƣ viêṇ thì tốt nhất là nên sƣ̉ duṇ g handle tớ i instance của chƣơng trình gọi tới hàm thƣ viện. Vì các thông điệp cho các hộp thoại modal đƣợc nhận bên ngoài vòng lặp thông điệp của chƣơng trình nên chún g ta có thể taọ ra môṭ hôp̣ thoaị modal trong môṭ thƣ viêṇ bằng cách gọi tới hàm DialogBox . Handle tớ i instance có thể là của thƣ viêṇ hoăc̣ tham số hwndParent của hàm DialogBox có thể đăṭ bằng NULL. Các thƣ viện không có import Thay vì để Windows thƣc̣ hiêṇ viêc̣ liên kết đôṇ g khi chƣơng trình lần đầu tiên đƣơc̣ nạp vào bộ nhớ chúng ta có thể liên kết một chƣơng trình với một thƣ viện khi chƣơng trình đang chaỵ . Chẳng haṇ chúng ta muốn goị tớ i hàm Rectangle nhƣ sau: Rectangle (hdc, xLeft, yTop, xRight, yBottom) ; Điều này se ̃ làm cho chƣơng trình đƣơc̣ liên kết tớ i thƣ viêṇ gdi 32.lib khi biên dic̣ h để lấy điạ chỉ của hàm Rectangle. Chúng ta có thể gọi tới hàm Rectangle theo một cách khác: typedef BOOL (WINAPI * PFNRECT) (HDC, int, int, int, int) ; khai báo hai biến: HANDLE hLibrary ; PFNRECT pfnRectangle ; Tiếp đến goị tớ i các hàm LoadLibrary và GetProcAddress : 84
- Bài giảng môn học: Lâp̣ triǹ h Windows hLibrary = LoadLibrary (TEXT ("GDI32.DLL")) pfnRectangle = (PFNPRECT) GetProcAddress (hLibrary, TEXT ("Rectangle")) Và bây giờ có thể gọi tới hàm Rectangle: pfnRectangle (hdc, xLeft, yTop, xRight, yBottom) ; FreeLibrary (hLibrary) ; Măc̣ dù kỹ thuâṭ sƣ̉ duṇ g thƣ viêṇ liên kết đôṇ g theo kiểu này kh ông làm tăng hiêụ quả sƣ̉ duṇ g của hàm Rectangle nhƣng nó laị là môṭ cách hiêụ quả trong trƣờ ng hơp̣ mà chúng ta không biết tên của thƣ viêṇ cho tớ i khi chƣơng trình chaỵ (chẳng haṇ đối vớ i hàm AlphaBlend chẳng haṇ ). Đoaṇ ma ̃ trên sƣ̉ duṇ g hai hàm LoadLibrary và FreeLibrary . Windows kiểm soát các biến đếm tham chiếu tớ i tất cả các module thƣ viêṇ . Hàm LoadLibrary sẽ làm cho biến đếm tham chiếu tớ i các thƣ viêṇ đƣơc̣ nap̣ tăng lên 1. Biến đếm tham chiếu cũng đƣợc tăng lên khi Windows nap̣ môṭ chƣơng trình có sƣ̉ duṇ g thƣ viêṇ . FreeLibarary se ̃ làm cho biến đếm này giảm đi 1, trƣờ ng hơp̣ môṭ instance của môṭ chƣơng trình sƣ̉ duṇ g thƣ viêṇ bi ̣loaị khỏi bô ̣nhớ biến đếm tham chiếu cũng giảm đi 1 đơn vi.̣ Khi biến đếm tham chiếu này bằng 0 Windows se ̃ loại bỏ thƣ viện khỏi bộ nhớ vì lúc đó thƣ viện không còn cần thiết nữa. Các thƣ viện chỉ chứa tài nguyên Bất cƣ́ hàm này trong môṭ thƣ viêṇ liên kết đô ̣ng mà môṭ chƣơng trình trên Windows và các thƣ viện khác có thể gọi tới đều phải đƣợc export . Tuy nhiên môṭ thƣ viêṇ liên kết đôṇ g có thể không nhất thiết phải chứa bất cứ một hàm export nào . Vâỵ các thƣ viêṇ đó chƣ́ a gì ? Câu trả lờ i là các tài nguyên. Chẳng haṇ chúng ta làm viêc̣ trên môṭ ƣ́ ng duṇ g Windows cần môṭ số các ảnh bitmap . Thông thƣờ ng chúng ta se ̃ liêṭ kê các ảnh này trong file kic̣ h bản tài nguyên và nap̣ chúng vào bô ̣nhớ vớ i hàm LoadBitmap. Nhƣng có le ̃ chúng ta muốn có môṭ số tâp̣ ảnh , mỗi tâp̣ cho môṭ đô ̣phân giải của màn hình hình thƣờ ng đƣơc̣ sƣ̉ duṇ g vớ i Windows . Giải pháp khả dĩ nhất là chƣ́ a các tâp̣ ảnh khác nhau này vào các file khác nhau vì môṭ ngƣờ i dùng chỉ cần tớ i môṭ tâp̣ các ảnh này trên đĩa cứng. Và các file này đƣợc gọi là các file thƣ viện chỉ chứa tài nguyên . Hình 21-5 cho chúng ta thấy cách thƣ́ c taọ ra môṭ thƣ viêṇ chỉ chƣ́ a tài nguyên đƣơc̣ goị là bitlib.dll chƣ́ a 9 ảnh bitmap. File bitlib.rc chƣ́ a tất cả các ảnh này và gán cho mỗi ảnh môṭ số. Để taọ ra file bitlib .dll chúng ta cần có 9 ảnh có tên lần lƣợt là bitmap 1.bmp, bitmap2.bmp, , bitmap9.bmp. Chúng ta có thể sử d ụng các ảnh đi kèm với đĩa CD của quyển sách để dùng. Bài tập: Bài tập 1: Viết thƣ viện DLL chứa các hàm xử lý xâu. 85
- Bài giảng môn học: Lâp̣ triǹ h Windows Tài liệu tham khảo [1] Lê Hƣ̃u Đaṭ . Lâp̣ trình Windows. NXB Giáo duc̣ . [2] Charles Petzold. Programming Windows, fifth edition. Microsoft Press. 1998. [3] Johnson M. Hart. Windows System Programming Third Edition. Addison Wesley Professional. 2004. 86
- Bài giảng môn học: Lâp̣ triǹ h Windows Đề thi tham khảo Đề số 1: Bài số 1 Cho Dialog sau: Biết rằng các ID của các control của Dialog trên nhƣ sau : các ID của các Edit Text tƣơng ứng vớ i các Static Text Canh a , Canh b , Canh c lần lƣơṭ là : ID_CANHA, ID_CANHB, ID_CANHC, ID của Static 1 là ID_KQKT, ID của Static 2 là ID_DTCV, ID của các Button Kiem tra và Thoat là ID _KIEMTRA, ID_THOAT. Hãy viết hàm xử lý cho Dialog trên sau cho khi nhấn vào nút Kiem tra thì chƣơng trình se ̃ kiểm tra xem ba số nguyên đƣơc̣ nhâp̣ và 3 Edit text tƣơng ƣ́ ng có là 3 cạnh của 1 tam giác hay không , kết quả kiểm tra đƣơc̣ thông báo qua Static 1. Trong trƣờ ng hơp̣ là 3 cạnh của 1 tam giác haỹ tính và hiển thi ̣chu vi , diêṇ tích của tam giác qua Static 2 và khi ngƣời dùng nhấn vào nút Thoat sẽ kết thúc Dialog. Bài số 2 a) Hãy trình bày (đƣa ra ) dạng đơn giản nhất của một hàm xử lý thông điệp cửa sổ chƣơnh trình (hàm Window Proc). b) Giả sử chƣơng trình chỉ có một mục menu Help , trong đó có 2 mục menu con là Contents và About , hãy viết hàm xử lý thôn g điêp̣ cƣ̉ a sổ chƣơng trình sao cho khi ngƣờ i dùng choṇ các muc̣ trong menu Help chƣơng trình se ̃ hiển thi ̣các thông báo (hàm Message Box) tƣơng ƣ́ ng và khi ngƣờ i dùng nhấn chuôṭ trái vào 1 vị trí trên màn hình, hãy in ra một thông báo về toạ đô ̣chuôṭ taị vi ̣trí đó (hàm TextOut). Đề số 2: Bài số 1 Viết hàm xƣ̉ lý hôp̣ thoaị sau: 87
- Bài giảng môn học: Lâp̣ triǹ h Windows Các điều khiển XA , YA, XB, YB, XC, YC là toạ đô ̣ 3 đỉnh trên măṭ phẳng toạ đô ̣ (nguyên). Khi nhấn nút “Tinh chu vi” haỹ tính chu vi của tam giác tạo thành bởi 3 đỉnh A, B, C và hiển thi ̣lên IDC _KQ. Nhấn “Thoat” để thóat khỏi hôp̣ thoaị và nhấn “Tinh dien tich” se ̃ tính diện tích và hiển thị nhƣ trong phần tính chu vi Bài số 2 a) Hãy trình bày (đƣa ra) dạng đơn giản nhất của một hàm xử lý thông điệp cửa sổ ch o môṭ hôp̣ thoaị. b) Hãy viết hàm WndProc cho một chƣơng trình có hệ thống menu gồm 1 mục File , trong đó có các muc̣ con vớ i các chƣ́ c năng sau : Menu1, Menu2, khi ngƣời dùng nhấn vào các mục này chỉ cần đƣa ra thông báo đơn giản , mục Exit để thoát khỏi chƣơng trình. Đề số 3: Bài số 1 Viết hàm xƣ̉ lý hôp̣ thoaị sau: Các điều khiển XA , YA, XB, YB, XC, YC là toạ đô ̣ 3 đỉnh trên măṭ phẳng tọa độ (nguyên). Khi nhấn nút “Tinh chu vi” haỹ tính chu vi của tam giác taọ thành bở i 3 đỉnh A, B, C và hiển thi ̣lên IDC _KQ. Nhấn “Thoat” để thóat khỏi hôp̣ thoaị và nhấn “Tinh dien tich” se ̃ tính diện tích và hiển thị nhƣ trong phần tính chu vi 88
- Bài giảng môn học: Lâp̣ triǹ h Windows Bài số 2 a) Hãy trình bày (đƣa ra) dạng đơn giản nhất của một hàm xử lý thông điệp cửa sổ ch o môṭ hôp̣ thoaị. b) Hãy viết hàm WndProc cho một chƣơng trình có hệ thống menu gồm 1 mục File , trong đó có các muc̣ con vớ i các chƣ́ c năng sau : Menu1, Menu2, khi ngƣờ i dùng nhấn vào các mục này chỉ cần đƣa ra thông báo đơn giản , mục Exit để thoát khỏi chƣơng trình. 89



