Bài giảng môn học: Lâ ̣p trình 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 trình 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 trình Windows
Phƣơng pháp Thiết lập
Control Panel Phần lớn thiết lập hệ thống SYSTEM. Ví dụ bạn sử dụng Display 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
hằng hợp của các giá trị
Để 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 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 trình Windows
2.3 Liê ̣t kê cá c khó a
Hàm liệt kê các khóa trong CSDL Registry là hàm LONG RegEnumKeyEx(HKEY lpcbName, LPDWORD lpName, LPDWORD
hKey, DWORD dwIndex, LPTSTR 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ê ̣n
Để 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 sửa các khóa ta có thể
file Wallpaper của Windows “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 ̃a
Để thay đổi lập với các ổ đĩa thiết ta
thực hiện với các khó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 của một ngƣời dùng trong
nằm thống nằm phần trong phần
trƣờng “HKEY_CURRENT_USER\Environment”, còn cho cả hệ “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 trình Windows
Chƣơng 4: Quản lý các tiến trình 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 cho mỗ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 trình 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 trình Windows
LPCTSTR lpCurDir,
LPSTARTUPINFO lpStartupInfo,
LPPROCESS_INFORMATION lpProcInfo)
2.2. Kết thú c và thoá t khỏ i mô ̣t tiến trình
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â ̣t ký thờ i gian thƣ̣c hiê ̣n củ a cá c tiến trình
#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 trình 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 trình 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ô hình Boss/Worker và cá c mô hình 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 trình 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ô ̣t số ví du ̣ về tiến trình 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 trình 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 < argc - 2; iProc += MAXIMUM_WAIT_OBJECTS)
WaitForMultipleObjects ( /* Allows a large # of processes */
min (MAXIMUM_WAIT_OBJECTS, argc - 2 - iProc),
&hProc [iProc], TRUE, INFINITE);
/* Result files sent to std output using "cat." */
for (iProc = 0; iProc < argc - 2; iProc++) {
if (GetExitCodeProcess(hProc [iProc], &ExCode) && ExCode==0) {
/* Pattern was detected -- List results. */
if (argc > 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 trình Windows
CloseHandle (ProcessInfo.hThread);
}
CloseHandle (hProc [iProc]);
DeleteFile (ProcFile [iProc].TempFile);
}
free (ProcFile);
free (hProc);
return 0;
}
4.2. Thuâ ̣t toá n sắ p xếp trô ̣n 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 trình 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 trình Windows
for (iTh = 0; iTh < NPr; iTh++) /* Run all sort threads. */
ResumeThread (ThreadHandle [iTh]);
WaitForSingleObject (ThreadHandle [0], INFINITE);
for (iTh = 0; iTh < NPr; iTh++) CloseHandle (ThreadHandle [iTh]);
StringEnd = (LPTSTR) pRecords + FsLow;
*StringEnd = '\0';
if (!NoPrint) printf ("\n%s", (LPCTSTR) pRecords);
free (pRecords);
free (ThArg);
free (ThreadHandle);
return 0;
} /* End of _tmain. */
static VOID MergeArrays (LPRECORD, LPRECORD);
DWORD WINAPI ThSort (PTHREADARG pThArg)
{
DWORD GrpSize = 2, RecsInGrp, MyNumber, TwoToI = 1;
LPRECORD First;
MyNumber = pThArg->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 trình 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 && i2 < nRecs) {
if (KeyCompare ((LPCTSTR) p1, (LPCTSTR) p2) <= 0) {
memcpy (pDest, p1, RECSIZE);
i1++; p1++; pDest++;
}
else {
memcpy (pDest, p2, RECSIZE);
i2++; p2++; pDest++;
}
}
if (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 trình Windows
Chƣơng 5: Các dịch vụ của Windows
1. Tổ ng quan về di ̣ch 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 di ̣ch 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 trình 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 trình 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 trình Windows
void UpdateStatus (int NewStatus, int Check)
/* Set a new service status and checkpoint --
either specific value or increment. */
{
if (Check < 0) hServStatus.dwCheckPoint++;
else hServStatus.dwCheckPoint = Check;
if (NewStatus >= 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 trình 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, hai là
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 di ̣ch 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 trình 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 trình 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 trình 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 trình 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 trình 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ô ̣t hê ̣ thống ma ̣ng đƣơ ̣c thƣ̣c hiê ̣n 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ô ̣t di ̣ch vu ̣ bằng cách ta ̣o ra mô ̣t đối tƣơ ̣ng ServerSocket vớ i tham số là đi ̣a chỉ cổng để kết nối , sau đó nhâ ̣n kết nối tƣ̀ phía client bằng cách sƣ̉ du ̣ng hàm accept (), lấy các luồng dữ liệu vào /ra củ a client để thƣ̣c hiê ̣n truyền dƣ̃ liê ̣u , thƣ̣c hiê ̣n 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 trình 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 trình 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 trình 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 trình 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 < MAX_CLIENTS; ith++)
if (srv_arg [ith].status != 0) {
WaitForSingleObject (srv_arg[ith].srv_thd, INFINITE);
CloseHandle (srv_arg[ith].srv_thd);
}
shutdown (SrvSock, 2);
closesocket (SrvSock);
WSACleanup ();
return 0;
}
static DWORD WINAPI AcceptTh (SERVER_ARG * pThArg)
{
/* Accepting thread that allows the main thread to poll the */
/* shutdown flag. This thread also creates the server thread. */
LONG AddrLen, ThId;
AddrLen = sizeof (ConnectSAddr);
pThArg->sock = accept (SrvSock, /* This is a blocking call. */
69
Bài giảng môn học: Lâ ̣p trình 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 trình 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 trình 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 trình 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 trình 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 trình Windows
Chƣơng 7: Thƣ viê ̣n liên kết đô ̣ng
Các thƣ viện liên kết động Dynamic -link Libararies là mô ̣t trong các phần tƣ̉ quan tro ̣ng
nhất củ a hê ̣ điều hành Windows . Hầu hết các thao tác truy câ ̣p đĩa cƣ́ ng trên Windows đều đƣơ ̣c thƣ̣c hiê ̣n bở i các chƣơng trình hoă ̣c các file liên kết đô ̣ng . Cho đến thờ i điểm này chú ng ta đã 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 đô ̣ng. Rất nhiều nguyên tắc trong viê ̣c viết các chƣơng trình cũng đƣơ ̣c áp du ̣ng trong viê ̣c viết các thƣ viê ̣n song có mô ̣t số thay đổi quan tro ̣ng.
7.1. Khái niệm và ứng dụng của thƣ viện liên kết động
Nhƣ các ba ̣n thấy mô ̣t chƣơng trình trên Windows là mô ̣t file cha ̣y thƣờ ng ta ̣o ra mô ̣t hoă ̣c mô ̣t số cƣ̉ a sổ chƣơng trình và sƣ̉ du ̣ng mô ̣t vòng lă ̣p thông điê ̣p để nhâ ̣n 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ô ̣t thƣ viê ̣n liên kết đô ̣ng thƣờ ng đƣơ ̣c na ̣p vào mô ̣t nhớ để thƣ̣c hiê ̣n khi mô ̣t module chƣơng trình khác gọi tới một hàm trong thƣ viện.
Thuâ ̣t ngƣ̃ liên kết đô ̣ ng “dynamic link” đề câ ̣p tớ i quá trình mà Windows sƣ̉ du ̣ng để liên kết mô ̣t lờ i go ̣i hàm trong mô ̣t module chƣơng trình vớ i các hàm thƣ̣c sƣ̣ nằm trong mô ̣t thƣ viê ̣n liên kết đô ̣ng. 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 di ̣ch 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 ̣n 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ô ̣t vài thƣ viê ̣n liên kết động (chẳng ha ̣n nhƣ các font chƣ̃ ) đƣơ ̣c go ̣i là các thƣ viê ̣n tài
(thƣờ ng là dƣớ i da ̣ng các tài
nguyên (resource only ). Các thƣ viện này chỉ chứa dữ liệu nguyên) và không chứa mã chƣơng trình . Do đó mô ̣t trong các mu ̣c đích củ a c ác thƣ viện liên kết đô ̣ng là cung cấp các hàm và các tài nguyên có thể đƣơ ̣c sƣ̉ du ̣ng 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 ̣i đến để hoàn thành mô ̣t công viê ̣c gì đó . Trên Windows quá trình mô ̣t module chƣơng trình go ̣i tớ i mô ̣t hàm trong mô ̣t 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ô ̣ng cho hê ̣ điều hành.
Mă ̣c dù mô ̣t module thƣ viê ̣n liên kết đô ̣ng có thể có bất cƣ́ phần tên mở rô ̣ng nào (chẳng ha ̣n nhƣ .exe hay .com) nhƣng phần tên mở rô ̣ng chuẩn củ a các thƣ viê ̣n liên kết đô ̣ng 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ự đô ̣ng na ̣p vào bô ̣ nhớ bở i Windows các thƣ viê ̣n có phần tên mở rô ̣ng khác sẽ đƣơ ̣c na ̣p thông qua viê ̣c go ̣i 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 ̣n nhƣ chú ng ta viết mô ̣t gói phần mềm kế toán lớ n chƣ́ a mô ̣t 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 trình 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 di ̣ch các chƣơng trình này . Tuy nhiên cách tiếp câ ̣n đó sẽ là lãng phí vì mỗi chƣơng trình sẽ chƣ́ a các đoa ̣n mã giống nhau và hơn nƣ̃a nếu chú ng ta thay đổi mô ̣t 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ô ̣t thƣ viê ̣n liên kết đô ̣ng chẳng ha ̣n nhƣ account.dll chẳng ha ̣n thì sẽ 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 mã củ a các hàm đƣơ ̣c sƣ̉ du ̣ng bở i tất cả các chƣơng trình điều nà y sẽ đòi hỏi ít không gian đĩa 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 ̣y đồng thờ i, hơn nƣ̃a chú ng ta có thể thay đổi các cài đă ̣t củ a các hàm trong thƣ viê ̣n mà không cần thiết phải liên kết la ̣i thƣ viê ̣n 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ê ̣n 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 ̣n nhƣ DirectX là mô ̣t tâ ̣p các thƣ viê ̣n liên kết đô ̣ng dành cho viê ̣c viết các chƣơng trình đồ ho ̣a cao cấp.
Thƣ viê ̣n: mô ̣t lờ i ngàn ý . (One word, many meanings)
Mô ̣t phần các nhầm lẫn đối vớ i các thƣ viê ̣n liên kết đô ̣ng là việc sử dụng của thuật ngữ
thƣ viê ̣n (library) trong mô ̣t số các khung cảnh khác nhau . Bên ca ̣nh các thƣ viê ̣n liên kết đô ̣ng chú ng ta còn nói đến các thƣ viê ̣n đối tƣơ ̣ng và các thƣ viê ̣n import.
Mô ̣t thƣ viê ̣n đối tƣơ ̣ng là mô ̣t file có phần mở rô ̣ng là
.lib chƣ́ a các đoa ̣n mã chƣơng trình sẽ đƣợc thêm vào file .exe trong quá trình liên kết tĩnh . Chẳng ha ̣n trong VC ++ thƣ viê ̣n đối tƣơ ̣ng run-time thƣờ ng liên kết vớ i các chƣơng trình là libc.lib.
(*.exe) để
Mô ̣t 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ê ̣n đối tƣơ ̣ng các thƣ viê ̣n import có phần mở rô ̣ng là .lib và đƣơ ̣c sƣ̉ du ̣ng 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ê ̣n import này không chƣ́ a các mã 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ê ̣n các liên kết đô ̣ng . Các file kernel 32.lib, user32.lib, gdi32.lib đi kèm vớ i các trình biên di ̣ch củ a Microsoft đều là các thƣ viê ̣n import . Nếu chú ng ta go ̣i tớ i mô ̣t hàm chẳng ha ̣n 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ê ̣n liên kết đô ̣ng gdi 32.dll. Thông tin này sẽ đƣơ ̣c chƣ́ a trong file chƣơng trình Windows có thể thƣ̣c hiê ̣n các liên kết đô ̣ng khi chƣơng trình củ a chú ng ta đƣơ ̣c thƣ̣c hiê ̣n. 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ô ̣t thƣ viê ̣n liên kết đô ̣ng 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 na ̣p mô ̣t module DLL trƣớ c khi cha ̣y mô ̣t 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ƣ mu ̣c hiê ̣n thờ i, trong thƣ mu ̣c system củ a hê ̣ điều hành hoă ̣c nằm trong thƣ mu ̣c có trong biến môi trƣờ ng PATH củ a hê ̣ điều hành (hê ̣ điều hành sẽ tiến hành tìm kiếm thƣ viê ̣n 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 sẽ viết mô ̣t ƣ́ ng du ̣ng demo sƣ̉ du ̣ng mô ̣t thƣ viê ̣n liên kết đô ̣ng đơn giản.
76
Bài giảng môn học: Lâ ̣p trình 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 ̣o ra mô ̣t thƣ viê ̣n liên kết đô ̣ng chú ng ta cần có mô ̣t cách tiếp câ ̣n 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ê ̣t giƣ̃a các khái niê ̣m “workspace” và “project” . Mô ̣t project thƣờ ng là mô ̣t ƣ́ ng du ̣ng hoă ̣c mô ̣t thƣ viê ̣n liên kết đô ̣ng . Mô ̣t . Cho đến thờ i điểm này chú ng ta mớ i chỉ viết các workspace có thể gồm nhiều project workspace chỉ có 1 project. Trong phần này chú ng ta sẽ ta ̣o mô ̣t workspace có hai project mô ̣t cho viê ̣c ta ̣o dll và mô ̣t cho viê ̣c go ̣i 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 ̣n) rỗng (Blank Solution)
2. Thêm mô ̣t project chƣ́ a file dll cho ƣ́ ng du ̣ng (chọn New Project Win32 Project) và gõ tên của Project là SimpleDll.
3. Trong hô ̣p thoa ̣i Application Setting chọn DLL (Application Type) và chọn mục Empty.
4. Thêm file mã nguồn cho Project và gõ nô ̣i 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 trình 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 trình 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 ̣n kiểu Project là
Application, tạo file .c cho ƣ́ ng du ̣ng và gõ nô ̣i dung ƣ́ ng du ̣ng 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 trình 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 trình 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ƣ mu ̣c Output cù ng ta ̣m 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 sẽ 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 mã củ a thƣ viê ̣n có mô ̣t hàm DllMain , hàm này có . Tác dụng của hàm DllMain là vai trò tƣơng tƣ̣ nhƣ hàm WinMain trong mô ̣t chƣơng trình khở i ta ̣o 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ê ̣n ta ̣i chỉ cần return TRUE là ổn.
. Đi ̣nh danh
Điều bí ẩn còn la ̣i có lẽ là ở đi ̣nh danh EXPORT . Các hàm trong một thƣ viện liên kết đô ̣ng đƣơ ̣c sƣ̉ du ̣ng bở i các ƣ́ ng du ̣ng 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 EXPORT còn bao gồm chỉ đi ̣nh 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 di ̣ch theo kiểu C++. Điều này ngăn chă ̣n trình biên di ̣ch 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 đô ̣ng có thể đƣơ ̣c sƣ̉ du ̣ng bở i cả các chƣơng trình C và C++.
hống sƣ̉ Điểm vào và điểm thoá t củ a thƣ viê ̣n (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. Nếu nhƣ thƣ viê ̣n có sƣ̉ du ̣ng các tài nguyên đòi hỏi một handle instance (chẳng ha ̣n nhƣ các hô ̣p thoa ̣i ), chúng ta nên lƣu lại hInstance vào mô ̣t biến toàn cu ̣c . Tham số cuối cù ng củ a hàm DllMain đƣơ ̣c dƣ̃ trƣ̣ dành cho hê ̣ t dụng.
Tham số fdwReason có thể là mô ̣t trong 4 giá trị chỉ ra tại sao Windows lại gọi tới hàm
DllMain. Trong các mu ̣c tiếp theo chú ng ta nên nhớ rằng mô ̣t chƣơng trình đơn có thể đƣơ ̣c
81
Bài giảng môn học: Lâ ̣p trình 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 na ̣p nó đƣơ ̣c xem nhƣ là mô ̣t 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ê ̣n t tiến trình . Đây là mô ̣t đầu mối cho
. Bất cƣ́ mô ̣t tiến trình nào khác sƣ̉ du ̣ng cù ng file liên kết đô ̣ng đã đƣơ ̣c ánh xa ̣ vào vù ng đi ̣a chỉ củ a mô ̣ 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 ̣i vớ i mô ̣t tham số DLL _PROCESS_ATTACH chỉ mô ̣t lần trong cả thờ i gian tồn ta ̣i củ a process đó DLL sẽ go ̣i đến hàm DllMain vớ i mô ̣t giá trị tham số DLL_PROCESS_ATTACH.
Nếu nhƣ viê ̣c khở i ta ̣o là thành công DllMain sẽ trả về mô ̣t 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ô ̣t thói quen lâ ̣p trình tốt.
Tƣơng tƣ̣ khi hàm DllMain đƣơ ̣c go ̣i vớ i mô ̣t giá tri ̣ tham số là
vớ i tham số là
DLL_THREAD_ATTACH thì có nghĩa là mô ̣t tiến trình sƣ̉ du ̣ng thƣ viê ̣n đã ta ̣o ra mô ̣t luồng (thread) mớ i. Khi luồng kết thú c Windows la ̣i go ̣i tớ i hàm DllMain 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ê ̣n lờ i go ̣i vớ i giá tri ̣ DLL _THREAD_ATTACH trƣớ c đó nếu nhƣ thƣ viê ̣n liên kết đô ̣ng đƣơ ̣c gắn vớ i mô ̣t tiến trình sau khi luồng đã đƣơ ̣c ta ̣o ra.
Luồng vẫn tồn ta ̣i khi hàm DllMain đƣơ ̣c go ̣i đến vớ i tham số nh. Nhƣng
DLL_THREAD_DETACH. Nó thậm chí có thể gửi các thông điệp trong tiến trì 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ê ̣n liên kết đô ̣ng là mô ̣t chƣơng trình đơn giản và là mô ̣t Project khác (thuô ̣c loa ̣i 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ƣ mu ̣c.
.dll và simpledll .lib sẽ đƣơ ̣c sinh ra Trong quá trình di ̣ch chƣơng trình file simpledll
đô ̣ng liên kết vớ i chƣơng trình thƣ̉ nghiê ̣m và file
. Cần chú ý là chƣơng trình
trƣớ c, file simpledll .lib sẽ đƣơ ̣c tƣ̣ simpledll.dll sẽ đƣơ ̣c na ̣p vào bô ̣ nhớ khi chƣơng trình cha ̣y usedll.exe không chƣ́ a mã củ a hàm sƣ̉ du ̣ng trong thƣ viê ̣n , mã của hàm chỉ đƣợc nạp vào b ộ nhớ khi chƣơng trình cha ̣y.
Viê ̣c include file simpledll .h cũng giống nhƣ chú ng ta include file windows .h, liên kết
32.lib và liên kết vớ i file
vớ i file simpledll .lib cũng tƣơng tƣ̣ nhƣ liên kết vớ i file user 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ô ̣t file DLL là mô ̣t mở rô ̣ng củ a Windows nhƣng nó cũng là mô ̣t mở rô ̣ng củ a chƣơng trình ƣ́ ng du ̣ng củ a chú ng ta . Tất cả nhƣ̃ng gì file DLL thƣ̣c hiê ̣n đều là thay mă ̣t cho ƣ́ ng du ̣ng sƣ̉ du ̣ng nó . Chẳng ha ̣n 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 ̣o 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 ̣ng cù ng
82
Bài giảng môn học: Lâ ̣p trình Windows
mô ̣t file dll đồng thờ i, nhƣng Windows sẽ đóng vai trò lá chắn để ngăn chă ̣n các can thiê ̣p lẫn nhau giƣ̃a các ƣ́ ng du ̣ng này.
Nhiều tiến trình có thể chia sẻ cù ng mô ̣t mô ̣t đoa ̣n mã trong mô ̣t thƣ viê ̣n liên kết đô ̣ng . Tuy nhiên dƣ́ liê ̣u đƣơ ̣c sƣ̉ đu ̣ng bở i mô ̣t thƣ viê ̣n DLL sẽ là khác nhau vớ i các tiến trình khác nhau. Mỗi tiến trình có mô ̣t không gian đi ̣a chỉ dƣ̃ liê ̣u củ a riêng nó để chƣ́ a các dƣ̃ li ệu có thể sƣ̉ du ̣ng đến bở i file DLL . Chia sẻ bô ̣ nhớ giƣ̃a các tiến trình đòi hỏi mô ̣t số kỹ thuâ ̣t 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ê ̣n liên kết đô ̣ng
. Tuy Windows cô lâ ̣p các ƣ́ ng du ̣ng sƣ̉ du ̣ng cù ng mô ̣t thƣ viê ̣n liên kết đô ̣ng đồng thờ i nhiên đôi khi đây không phải là mô ̣t lƣ̣a cho ̣n thích hơ ̣p . Chúng ta có thể muốn viết một thƣ viê ̣n DLL chƣ́ a mô ̣t vù ng nhớ nào đó có thể đƣơ ̣c chia sẻ bở i nhiều ƣ́ n g du ̣ng, hoă ̣c giƣ̃a các instance củ a cù ng mô ̣t ƣ́ ng du ̣ng. Điều này liên quan tớ i viê ̣ sƣ̉ du ̣ng 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 . Và một
đƣơ ̣c sƣ̉ du ̣ng là strlib (string library). Strlib chƣ́ a 3 hàm mà strprog có thể gọi tới trong số các hàm củ a strlib sẽ sƣ̉ du ̣ng mô ̣t hàm call-back đƣơ ̣c đi ̣nh nghĩa trong strprog.
. Strprog sẽ in ra giá tri ̣ tất cả cá c
Strlib là mô ̣t thƣ viê ̣n liên kết đô ̣ng chƣ́ a và làm viê ̣c vớ i mô ̣t xâu tối đa 256 ký tự. Xâu đƣơ ̣c chuyển thành da ̣ng 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â ̣n đƣơ ̣c tất cả các xâu hiê ̣n ta ̣i tƣ̀ strlib. Chƣơng trình strprog có hai mu ̣c menu để lƣ̣a cho ̣n 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 xâu hiê ̣n đang nằm chƣ́ a trong thƣ viê ̣n.
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 đã sƣ̉ du ̣ng 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 . Kết quả trả về củ a hàm là TRUE nếu
và nếu có nhiều xâu khớp thì chỉ xâu đầu tiên bị xóa 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ô ̣t tham số là mô ̣t hàm cal l-back chƣ́ a trong chƣơng trình sƣ̉ du ̣ng hàm . Hàm này phải đƣợc đi ̣nh nghĩa trong chƣơng trình sƣ̉ du ̣ng thƣ viê ̣n 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 ̣i tớ i hàm GetStrCallBack mỗi lần cho mỗi giá tri ̣ củ a mô ̣t xâu cho tớ i khi hàm này trả về FALSE. GetString sẽ trả về số lƣơ ̣ng 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ê ̣u đƣơ ̣c ngƣờ i dù ng đi ̣nh nghĩa.
83
Bài giảng môn học: Lâ ̣p trình Windows
Tất nhiên ở đây chú ng ta cũng sƣ̉ du ̣ng 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 đã tƣ̀ ng đề câ ̣p trong phần đầu củ a chƣơng này là các thƣ viê ̣n liên kết đô ̣ng không nhâ ̣n các thông điê ̣p . Tuy nhiên mô ̣t thƣ viê ̣n liên kết đô ̣ng có thể go ̣i 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 . Nói chung thƣ thƣ̣c sƣ̣ là các thông điê ̣p củ a các chƣơng trình go ̣i tớ i các hàm củ a thƣ viê ̣n viê ̣n làm viê ̣c thay mă ̣c cho chƣơng trình go ̣i nó – mô ̣t qui luâ ̣t chi phối hầu hết các hàm củ a Windows mà mô ̣t thƣ viê ̣n có thể go ̣i tớ i.
Mô ̣t thƣ viê ̣n liên kết đô ̣ng có thể na ̣p các tài nguyên
, đòi hỏi handle tớ i instance củ a
(chẳng ha ̣n nhƣ các biểu tƣơ ̣ng chƣơng trình, các xâu và các ảnh bitmap) tƣ̀ file thƣ viê ̣n hoă ̣c tƣ̀ các file củ a chƣơng trình go ̣i tớ i thƣ viê ̣n đó . Các hàm nạp tài nguy ên đòi hỏi mô ̣t handle tớ i instance . Nếu nhƣ thƣ viê ̣n sƣ̉ dụng handle tới instance của riêng nó (đƣơ ̣c truyền cho thƣ viê ̣n qua viê ̣c go ̣i 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ó. Để na ̣p các tài nguyên từ chƣơng trình gọi tới các hàm của thƣ viện chƣơng trình go ̣i hàm.
Viê ̣c khai báo các lớ p cƣ̉ a sổ chƣơng trình và ta ̣o ra cƣ̉ a sổ chƣơng trình trong mô ̣t thƣ . Cả cấu trúc lớp cửa sổ và hàm CreateWindow hoặc
viê ̣n đ òi hỏi một số thủ thuật CreateWindowEx đều đòi hỏi mô ̣t handle tớ i mô ̣t 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ẫn đi qua hàng đơ ̣i thông điê ̣p củ a chƣơng trình go ̣i tớ i thƣ viê ̣n khi thƣ viê ̣n ta ̣o ra cƣ̉ a sổ chƣơng trình . Nếu nhƣ ba ̣n cần phải ta ̣o ra các lớ p cƣ̉ a sổ và các cƣ̉ a sổ chƣơng trình trong mô ̣t thƣ viê ̣n thì tốt nhất là nên sƣ̉ du ̣ng handle tớ i instance củ a chƣơng trình gọi tới hàm thƣ viện.
. Handle tớ i instance có thể là củ a thƣ viê ̣n hoă ̣c tham số
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 ̣o ra mô ̣t hô ̣p thoa ̣i modal trong mô ̣t thƣ viê ̣n bằng cách gọi tới hàm DialogBox hwndParent củ a hàm DialogBox có thể đă ̣t bằng NULL.
Các thƣ viện không có import
Thay vì để Windows thƣ̣c hiê ̣n viê ̣c liên kết đô ̣ng 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 ̣y.
Chẳng ha ̣n chú ng ta muốn go ̣i tớ i hàm Rectangle nhƣ sau:
Rectangle (hdc, xLeft, yTop, xRight, yBottom) ;
Điều này sẽ làm cho chƣơng trình đƣơ ̣c liên kết tớ i thƣ viê ̣n gdi 32.lib khi biên di ̣ch để lấy đi ̣a 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 ̣i tớ i các hàm LoadLibrary và GetProcAddress :
84
Bài giảng môn học: Lâ ̣p trình 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â ̣t sƣ̉ du ̣ng thƣ viê ̣n liên kết đô ̣ng theo kiểu này kh ông làm tăng hiê ̣u quả
sƣ̉ du ̣ng củ a hàm Rectangle nhƣng nó la ̣i là mô ̣t cách hiê ̣u quả trong trƣờ ng hơ ̣p mà chú ng ta không biết tên củ a thƣ viê ̣n cho tớ i khi chƣơng trình cha ̣y (chẳng ha ̣n đối vớ i hàm AlphaBlend chẳng ha ̣n).
Đoa ̣n mã trên sƣ̉ du ̣ng 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ê ̣n . Hàm LoadLibrary sẽ làm cho biến đếm tham chiếu tớ i các thƣ viê ̣n đƣơ ̣c na ̣p tăng lên 1. Biến đếm tham chiếu cũng đƣợc tăng lên khi Windows na ̣p mô ̣t chƣơng trình có sƣ̉ du ̣ng thƣ viê ̣n . FreeLibarary sẽ làm cho biến đếm này giảm đi 1, trƣờ ng hơ ̣p mô ̣t instance củ a mô ̣t chƣơng trình sƣ̉ du ̣ng thƣ viê ̣n bi ̣ loa ̣i 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 sẽ 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ô ̣t thƣ viê ̣n liên kết đô ̣ng mà mô ̣t 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ô ̣t thƣ viê ̣n liên kết đô ̣ng có thể không nhất thiết phải chứa bất cứ một hàm export nào . Vâ ̣y các thƣ viê ̣n đó chƣ́ a gì ? Câu trả lờ i là các tài nguyên.
. Chẳng ha ̣n chú ng ta làm viê ̣c trên mô ̣t ƣ́ ng du ̣ng Windows cần mô ̣t số các ảnh bitmap
Thông thƣờ ng chú ng ta sẽ liê ̣t kê các ảnh này trong file ki ̣ch bản tài nguyên và na ̣p chú ng vào bô ̣ nhớ vớ i hàm LoadBitmap. Nhƣng có lẽ chú ng ta muốn có mô ̣t số tâ ̣p ảnh , mỗi tâ ̣p cho mô ̣t đô ̣ phân giải củ a màn hình hình thƣờ ng đƣơ ̣c sƣ̉ du ̣ng 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ô ̣t ngƣờ i dù ng chỉ cần tớ i mô ̣t 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 .
9 ảnh có tên lần lƣợt là bitmap 1.bmp, .dll chú ng ta cần có
Hình 21-5 cho chú ng ta thấy cách thƣ́ c ta ̣o ra mô ̣t thƣ viê ̣n chỉ chƣ́ a tài nguyên đƣơ ̣c go ̣i 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ô ̣t số . Để ta ̣o ra file bitlib 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 trình Windows
Tài liệu tham khảo
[1] Lê Hƣ̃u Đa ̣t. Lập trình Windows. NXB Giáo du ̣c. [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 trình Windows
Đề thi tham khảo
Đề số 1: Bài số 1 Cho Dialog sau:
, Canh b , Canh c lần lƣơ ̣t là
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 : ID_CANHA, ID_CANHB, vớ i các Static Text Canh a 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 sẽ 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 hãy tính và hiển thi ̣ chu vi , diê ̣n 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 ̣n các mu ̣c trong menu Help chƣơng trình sẽ 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ô ̣t 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 ̣a đô ̣ chuô ̣t ta ̣i vi ̣ trí đó (hàm TextOut).
Đề số 2: Bài số 1 Viết hàm xƣ̉ lý hô ̣p thoa ̣i sau:
87
Bài giảng môn học: Lâ ̣p trình Windows
Các điều khiển XA , YA, XB, YB, XC, YC là to ̣a đô ̣ 3 đỉnh trên mă ̣t phẳng to ̣a đô ̣
(nguyên). Khi nhấn nú t “Tinh chu vi” hãy 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 ̣i và nhấn “Tinh dien tich” sẽ 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ô ̣t hô ̣p thoa ̣i. 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 mu ̣c 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 ̣i sau:
Các điều khiển XA , YA, XB, YB, XC, YC là to ̣a đô ̣ 3 đỉnh trên mă ̣t phẳng tọa độ (nguyên). Khi nhấn nú t “Tinh chu vi” hãy tính chu vi củ a tam giác ta ̣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 ̣i và nhấn “Tinh dien tich” sẽ 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 trình 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ô ̣t hô ̣p thoa ̣i. 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 mu ̣c 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.

