Nạp dll là quá trình tải dll vào bộ nhớ Ram, khi các chương trình cần sử dụng sẽ gọi các hàm bên trong dll đã nạp. Khi chương trình cần dll, và dll không có sẵn trên bộ nhớ, thì hệ thống sẽ tải (nạp) dll thông qua 2 bước:
Tìm vị trí dll trên đĩa cứng
Nạp lên bộ nhớ Ram
Hệ thống sẽ tìm kiếm dll được yêu cầu theo tên, lần lượt tìm trong các thư mục sau theo thứ tự:
Thư mục hiện tại (cùng chung thư mục với chương trình)
Thư mục System32 (hoặc SysWOW64 nếu là Windows 64 bit)
Các đường dẫn được định nghĩa trong biến môi trường (environment variable)
Như vậy, khi sử dụng dll, các bạn nên đặt nó ở vị trí mà hệ thống có thể tìm thấy (thường là chung thư mục file .exe).
Nếu như không tìm thấy, một lỗi sẽ được thông báo và chương trình dừng lại.
Một trong các dạng dll được sử dụng thường xuyên là các api của hệ điều hành. Hệ điều hành Windows cung cấp Windows API trong các tệp shell32.dll, user32.dll, kernel32.dll, ...
Tiếp theo, chúng ta sẽ lấy ví dụ bằng hàm SetCurrentDirectory (https://docs.microsoft.com/en-us/windows/desktop/api/winbase/nf-winbase-setcurrentdirectory). Các Windows API thường được tìm thấy trên MSDN. Tuy nhiên, bạn không cần thiết quá lo lắng vì delphi hỗ trợ rất tốt windows api, nên thường bạn sẽ tìm thấy các hàm cần thiết đã được import, không cần import thủ công.
Tuy nhiên, bạn cần xem qua ví dụ import hàm từ dll thủ công cho biết :D
Cú pháp C++ của dll (được ghi trên trang MSDN)
BOOL SetCurrentDirectory(
LPCTSTR lpPathName
);
Và được import vào delphi như sau
function SetCurrentDirectory(lpPathName: PWideChar): Boolean; stdcall;
external 'Kernel32.dll' name 'SetCurrentDirectoryW';
Có 4 vấn đề cần quan tâm ở đây:
Chuyển đổi kiểu: Các kiểu dữ liệu trong C++ khác với delphi. Như trên, kiểu LPCTSTR trong C++ thực chất tương đương với kiểu PWideChar (giông giống string, nhưng là con trỏ). Các bạn cần biết rõ chuyển đổi như thế nào để cho phù hợp.
Phần từ external trở về sau: phần này chỉ định dll chứa hàm cùng với tên hàm được lưu trong dll (tên hàm trong dll có thể khác tên hàm được import, ví dụ như hàm ShellExecute được import từ hàm ShellExecuteA trong dll)
Chỉ thị stdcall là cần thiết để gọi dll viết bằng các ngôn ngữ khác (xem lại quy ước gọi dll trong bài trước)
Dll được yêu cầu là Kernel32.dll. Bạn không cần quan tâm nhiều quá vì hầu hết máy tính Windows đều có sẵn dll này rồi.
Chú ý: Phần tên hàm trong dll không phải lúc nào cũng giống tên hàm được định nghĩa. Ở ví dụ trên hàm SetCurrentDirectory có tên trong dll là SetCurrentDirectoryW. W ở đây là Wide, ám chỉ hàm có hỗ trợ unicode. Ngược lại, một số hàm có hậu tố là A, mang nghĩa là chỉ hỗ trợ Ansi (không cho unicode). Rất nhiều hàm Windows API có dạng W và A như thế này.
Sau khi đã liên kết hàm trong dll với hàm trong chương trình, thì gọi hàm đó như bình thường, nhưng sẽ do các lệnh trong dll thực thi.
begin
SetCurrentDirectory('F:\ABC');
end;
Trước tiên, hãy xem xét sự khác nhau giữa tải tĩnh và động. Về cơ bản có 3 điểm khác biệt:
Tải tĩnh thì dll được nạp vào bộ nhớ khi chương trình được chạy. Tải động thì bạn có thể tải dll thủ công, lúc nào muốn cũng được
Tải tĩnh bạn không thể cung cấp đường dẫn tuyệt đối đến dll, phải đặt dll trong thư mục có thể tìm thấy. Ngược lại, với tải động, bạn có thể chỉ định đường dẫn đến dll.
Nếu dll không tìm thấy, tải tĩnh sẽ thông báo ngay khi chương trình được chạy, và chương trình không hoạt động. Tải động thì chương trình vẫn hoạt động bình thường cho đến khi nạp dll.
Nói chung, mình thích sử dụng tải tĩnh dll hơn vì mã viết ra đẹp và dễ nhìn.
Tải động dll dựa trên 3 hàm: LoadLibrary, FreeLibrary và GetProcAddress. Bạn cũng cần quan tâm đến function pointer và con trỏ để có thể sử dụng.
Các khái niệm:
LoadLibrary: Hàm này tải dll từ một tệp dll trên đĩa cứng. Kết quả của hàm trả về một THandle (Cardinal) là handle của dll trên bộ nhớ. Nếu tải dll không thành công, handle trả về là 0.
FreeLibrary: Hàm dùng để unload dll, giải phóng bộ nhớ khi dll sử dụng xong.
Một function pointer: Có cấu trúc giống với hàm trong dll. Được gán địa chỉ của hàm dll trong bộ nhớ bằng hàm GetProcAddress
GetProcAddress: Nhận vào hai tham số: handle của dll và tên hàm trong dll. Nếu không tìm thấy hàm trong dll, GetProcAddress trả về nil.
Ví dụ mã cho tải hàm SetCurrentDirectory như ở trên (lưu ý vị trí file Kernel32.dll có thể là trong thư mục C:\Windows\System32, hoặc cũng có thể là C:\Windows\SysWOW64)
type
TFunc = function (lpPathName: PWideChar): Boolean; stdcall;
var
aFunc: TFunc;
DLLHandle: THandle; // Hoặc Cardinal cũng được
begin
DLLHandle := LoadLibrary('C:\Windows\SysWOW64\Kernel32.dll');
if DLLHandle = 0 then
writeln('Khong the tai DLL')
else
begin
@aFunc := GetProcAddress(DLLHandle, 'SetCurrentDirectoryW');
if @aFunc = nil then
writeln('Khong tim thay ham trong DLL')
else
aFunc('D:\ABC');
// aFunc đại diện cho tên hàm là SetCurrentDirectory
end;
end;
Như đã nói, các dll có thể được viết bằng ngôn ngữ lập trình khác như C++, nên việc tương thích kiểu dữ liệu cũng là vấn đề nên được quan tâm. Khi viết dll, bạn cần chú ý hạn chế sử dụng các kiểu dữ liệu của delphi như Boolean, Interger, Byte, hay string.
Thay vào đó, sử dụng các kiểu chuẩn của winapi như sau:
BOOL: thay cho Boolean
NativeInt: thay cho Integer
NativeUInt: thay cho Cardinal
PWideChar (hoặc PChar): thay cho string
Chú ý: Luôn dùng stdcall cho các hàm, thủ tục của dll (cả viết dll và sử dụng dll) để đảm bảo tương thích.