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ṇ mã 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 sẽ 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 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.
96 trang |
Chia sẻ: huyhoang44 | Lượt xem: 804 | Lượt tải: 0
Bạn đang xem trước 20 trang tài liệu Bài giảng lập trình windows, để xem tài liệu hoàn chỉnh bạn click vào nút DOWNLOAD ở trên
atus (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. */
Bài giảng môn học: Lâp̣ triǹh Windows
60
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 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;
Bài giảng môn học: Lâp̣ triǹh Windows
61
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];
Bài giảng môn học: Lâp̣ triǹh Windows
62
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);
Bài giảng môn học: Lâp̣ triǹh Windows
63
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);
Bài giảng môn học: Lâp̣ triǹh Windows
64
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.
Bài giảng môn học: Lâp̣ triǹh Windows
65
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);
Bài giảng môn học: Lâp̣ triǹh Windows
66
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;
Bài giảng môn học: Lâp̣ triǹh Windows
67
/* 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;
Bài giảng môn học: Lâp̣ triǹh Windows
68
}
/* 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);
Bài giảng môn học: Lâp̣ triǹh Windows
69
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. */
Bài giảng môn học: Lâp̣ triǹh Windows
70
(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);
Bài giảng môn học: Lâp̣ triǹh Windows
71
/* 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);
Bài giảng môn học: Lâp̣ triǹh Windows
72
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. */
Bài giảng môn học: Lâp̣ triǹh Windows
73
/* 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 ();
Bài giảng môn học: Lâp̣ triǹh Windows
74
_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.
Bài giảng môn học: Lâp̣ triǹh Windows
75
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 troṇg.
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̣
Bài giảng môn học: Lâp̣ triǹh Windows
76
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 .
Bài giảng môn học: Lâp̣ triǹh Windows
77
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
Bài giảng môn học: Lâp̣ triǹh Windows
78
/*-------------------------------------------------
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 ;
Bài giảng môn học: Lâp̣ triǹh Windows
79
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) ;
Bài giảng môn học: Lâp̣ triǹh Windows
80
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")) ;
Bài giảng môn học: Lâp̣ triǹh Windows
81
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̣
Bài giảng môn học: Lâp̣ triǹh Windows
82
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
Bài giảng môn học: Lâp̣ triǹh Windows
83
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ã.
Bài giảng môn học: Lâp̣ triǹh Windows
84
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 :
Bài giảng môn học: Lâp̣ triǹh Windows
85
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.
Bài giảng môn học: Lâp̣ triǹh Windows
86
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.
Bài giảng môn học: Lâp̣ triǹh Windows
87
Đề 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:
Bài giảng môn học: Lâp̣ triǹh Windows
88
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
Bài giảng môn học: Lâp̣ triǹh Windows
89
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 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.
Các file đính kèm theo tài liệu này:
- win32api_1254.pdf