DLL là một file nhị phân chứa một hoặc nhiều hàm thực thi cho phép các ứng dụng khác sẽ có thể gọi đến.. Với dạng này, các đoạn mã của thư việc MFC sẽ không chèn trực tiếp vào trong Regu
Trang 1LÀM QUEN VỚI DLL
Văn Chí Nam
vcnam@fit.hcmuns.edu.vn Khoa Công nghệ Thông tin, trường ĐH KHTN TP.HCM
Phiên bản cập nhật ngày 15/11/2005
Giới thiệu
DLL là gì?
DLL là viết tắt của cụm từ Dynamic Link Library DLL là một file nhị phân chứa một hoặc nhiều hàm thực thi cho phép các ứng dụng khác sẽ có thể gọi đến Một DLL có thể được nhiều ứng dụng sử dụng đồng thời
Các hàm trong DLL được phân chia làm 2 loại: export (hàm xuất, những hàm cho phép các ứng dụng bên ngoài sử dụng), internal (hàm nội bộ, những hàm chỉ được sử dụng trong nội bộ DLL)
Ưu điểm của việc sử dụng DLL
Sử dụng DLL sẽ làm cho kích thước của ứng dụng nhỏ
Nhiều ứng dụng có thể dùng chung 1 DLL, do đó, tiết kiệm bộ nhớ (thông thường, các ứng dụng có dữ liệu riêng, nhưng có thể chia sẻ mã lệnh)
Khi không còn sử dụng, có thể giải phóng DLL khỏi bộ nhớ
Khi cần nâng cấp, chỉ cần thay thế file DLL, các file chương trình khác không bị ảnh hưởng
Khuyết điểm của việc sử dụng DLL
Đòi hỏi lập trình viên phải thực hiện nhiều thao tác hơn bình thường
Phân loại DLL
MFC hỗ trợ 2 loại DLL: DLL dạng mở rộng (Extension DLL) và dạng bình thường (Regular DLL)
DLL dạng mở rộng (Extension DLL)
Extension DLL cho phép sử dụng lại các lớp từ MFC cũng như cho phép các ứng dụng khác sử dụng các lớp (dạng “export”) của nó
Trang 2Các ứng dụng MFC có định nghĩa _AFXEXT chỉ sử dụng được với Extension DLL
mà thôi
Extension DLL liên kết với các DLL của MFC theo dạng liên kết động
Regular DLL
Regular DLL có thể sử dụng được với các ứng dụng MFC và các ứng dụng dạng Win32 Application
Regular DLL chỉ cho phép “export” các hàm dạng C/C++ chứ không cho phép
“export” các lớp có trong DLL
Regular DLL có thể liên kết với các DLL của MFC theo 1 trong 2 cách: liên kết tĩnh (statically linking) và liên kết động (dynamic linking)
Liên kết tĩnh có nghĩa là các đoạn mã trong DLL của MFC sẽ được chèn vào trong Regular DLL mỗi khi hàm tương ứng được sử dụng đến Việc liên kết tĩnh với các thư viện MFC sẽ làm cho DLL (Regular DLL) có kích rất lớn
Để giảm kích thước của DLL xuống, chúng ta có thể liên kết dạng liên kết động Với dạng này, các đoạn mã của thư việc MFC sẽ không chèn trực tiếp vào trong Regular DLL và chỉ nạp lên khi cần được sử dụng đến Tuy nhiên, với dạng liên kết này, chúng
ta phải kèm các DLL của MFC cần sử dụng với Regular DLL của chúng ta
Cách thức gọi DLL trong ứng dụng
Gọi lúc ứng dụng được nạp (load-time)
Với cách thức gọi DLL theo kiểu này, ứng dụng sẽ nạp DLL lên bộ nhớ bất kể ứng dụng có sử dụng đến những hàm thực thi của DLL trong quá trình thực thi hay không Chính vì vậy, điều này có thể dẫn đến những bất tiện: tốn bộ nhớ nạp DLL, ứng dụng
có thể không thể hoạt động được trong trường hợp DLL không tồn tại Tuy nhiên, cách này rất dễ thực hiện đối với những lập trình viên mới bắt đầu sử dụng DLL
Gọi lúc thực thi (run-time)
Cách gọi DLL kiểu này vượt qua những khó khăn gặp phải của kiểu gọi DLL lúc ứng dụng được nạp (load-time): lập trình viên sẽ quyết định khi nào cần phải nạp DLL lên
bộ nhớ, ứng dụng có thể thực thi (với những chức năng bổ sung khác) nếu DLL không tồn tại Tuy nhiên, điểm khó khăn của cách gọi này nằm ở chỗ người lập trình viên sẽ tốn nhiều công sức cho việc nạp DLL, gọi hàm trong DLL
Trang 3Minh hoạ xây dựng một regular DLL
Trong minh hoạ dưới đây, chúng tôi minh hoạ cách tạo lập một Regular DLL trong Visual C++ 6.0 (sử dụng dạng liên kết động với các DLL của MFC) DLL này sẽ chứa hai hàm tính toán (cộng và nhân) cho phép các ứng dụng khác gọi đến Minh hoạ dưới đây có 2 hàm xuất (export)
Tạo ứng dụng DLL dạng Regular
- Chọn New\Projects\MFC AppWizard (dll)
- Đặt tên project (Project name) và chọn lựa vị trí thư mục thích hợp (Location)
Chọn Regular DLL using shared MFC DLL tại Step 1 of 1
Trang 4Nhấn Finish để hoàn tất việc tạo lập project dạng Regular DLL
Visual C++ 6.0 sẽ phát sinh một số tập tin và một số đoạn mã cho ứng dụng
Trang 5Khai báo hàm xuất
Dạng hàm tổng quát
extern "C" declspec(dllexport) TenKieuTraVe TenHam(CacThamSo);
Đối với ứng dụng có nhiều hàm cần xuất, chúng ta có thể định nghĩa một giá trị thay thế như sau:
#define extern"C" declspec(dllexport) EXPORT
Khi đó, hàm tổng quát có dạng:
EXPORT TenKieuTraVe TenHam(CacThamSo);
Ví dụ:
Khai báo hai hàm xuất Cong, và Nhan
extern "C" declspec(dllexport) int Cong(int a, int b);
extern "C" declspec(dllexport) int Nhan(int a, int b);
Hoặc
EXPORT int Cong(int a, int b);
EXPORT int Nhan(int a, int b);
Khai báo các hàm xuất có thể được đặt trong tập tin header (.h) hoặc tập tin thi hành (.cpp) Không nhất thiết đặt các khai báo hàm xuất trong tập tin header
Khai báo hàm nội bộ
Hàm nội bộ trong DLL không có khai báo gì đặc biệt hơn so với việc khai báo hàm trong các ứng dụng thông thường
Viết phần thực thi cho hàm xuất
Khi viết phần thực thi cho các hàm xuất cần lưu ý đặt macro
AFX_MANAGE_STATE đầu tiên trong thân hàm Macro này cần thiết đối với
những hàm có gọi (sử dụng) những đối tượng từ các thư viện MFC Trong những hàm xuất không có sử dụng đến các đối tượng của MFC thì có thể không cần sử dụng macro này
Tuy nhiên, lời khuyên là: “nên đạt macro này vào đầu mỗi thân hàm xuất”
Ví dụ:
Phần thực thi cho hàm Cong
Trang 6extern "C" declspec(dllexport) int Cong(int a, int b)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
return a + b
}
Phần thực thi cho hàm Nhan
extern "C" declspec(dllexport) int Nhan (int a, int b)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
return a* ;
}
Biên dịch DLL giống như biên dịch các ứng dụng bình thường khác
Sau khi biên dịch thành công, tập tin dll tương ứng được tạo ra và đi kèm với tập tin này sẽ có tập tin thư viện lib Chúng tôi sẽ minh hoạ cách sử dụng hai tập tin này ở phần sau của bài viết này
Minh hoạ tạo một ứng dụng sử dụng DLL
Phần tiếp theo của bài viết này sẽ minh hoạ việc sử dụng DLL vừa được tạo ở phần
trên Cách thức gọi DLL trong ứng dụng này là gọi DLL lúc nạp ứng dụng (kiểu load-time) Đây là cách thức nạp DLL đơn giản, dễ thực hiện Tuy nhiên, hạn chế của cách
thức nạp DLL này là khi không tìm thấy DLL tương ứng trong lúc nạp ứng dụng thì ứng dụng sẽ thoát và không thực hiện gì tiếp theo
Tạo lập ứng dụng
Ứng dụng được tạo lập theo dạng dialog-based với các control đặt trên dialog như mô
tả dưới đây
Đặt các biến đại diện tương ứng với các control nhập trên màn hình
Trang 7Thêm tập tin lib vào trong project ứng dụng
Như đã đề cập ở phần trên, sau khi biên dịch Regular DLL, có 2 tập tin quan trọng cần lưu giữ: lib và dll File lib chứa các khai báo của các hàm có trong DLL, file này được sử dụng đối với cách gọi DLL kiểu load-time, nó hỗ trợ cho quá trình biên dịch ứng dụng File dll sẽ chứa mã thực thi các hàm của DLL, các hàm này sẽ được nạp vào bộ nhớ khi dll được gọi
Đầu tiên, cần phải chép các file lib và dll vào cùng thư mục chứa project của ứng dụng gọi DLL
Thêm file lib vào trong project bằng cách:
- Chọn Project\Add to Project\ Files
- Chọn Files of type là Library Files (.lib)
- Chọn file cần thêm vào (RegularDLL.lib) và nhấn OK
Trang 8Khai báo các hàm nhập trong ứng dụng
Những hàm xuất (export) trong DLL chính là những hàm nhập (import) trong ứng dụng Chúng ta có thể chỉ cần khai báo những hàm nhập cần thiết, không phải khai báo tất cả những hàm mà DLL “xuất”
Dạng hàm khai báo tổng quát
extern "C" declspec(dllimport) TenKieuTraVe TenHam(CacThamSo);
Ví dụ:
extern "C" declspec(dllimport) int Cong(int a, int b);
Trong trường hợp, khai báo hàm xuất trong DLL được đặt trong file header (.h) thì có
thể include file đó vào trong project Tuy nhiên, nhớ chỉnh dllexport trong khai báo xuất thành dllimport trong khai báo nhập
Viết các mã thực thi cho các chức năng
Hàm xử lý sự kiện nhấn vào nút Cong
void CUsingRegularDLLDlg::OnCong()
{
// TODO: Add your control notification handler code here
UpdateData();
m_iZ = Cong( m_iX , m_iY );
UpdateData( FALSE );
}
Hàm xử lý sự kiện nhấn vào nút Nhan
void CUsingRegularDLLDlg::OnNhan()
{
// TODO: Add your control notification handler code here
Trang 9UpdateData();
m_iZ = Nhan( m_iX , m_iY );
UpdateData( FALSE );
}
Hàm xử lý sự kiện nhấn vào nút Thoat
void CUsingRegularDLLDlg::OnThoat()
{
// TODO: Add your control notification handler code here
CDialog::OnCancel();
}
Lưu ý
Mỗi khi nội dung các hàm của DLL có thay đổi chúng ta phải cập nhật DLL mới cho ứng dụng bằng cách ghi đè các file DLL cũ đang được ứng dụng sử dụng
Đối với kiểu gọi DLL load-time, mỗi khi DLL có sự thay đổi về cách khai báo các hàm xuất, chúng ta phải xoá lib cũ khỏi project ứng dụng và thêm (cập nhật) lib mới cho project ứng dụng
Minh hoạ tạo ứng dụng gọi DLL kiểu run-time
Sử dụng kiểu gọi DLL loại run-time có nhiều đặc điểm thuận lợi so với gọi DLL kiểu load-time:
- Không cần file lib đi kèm lúc tạo project ứng dụng
- DLL được nạp lên khi cần được sử dụng (thay vì nạp lúc ứng dụng được gọi thực thi như kiểu load-time) Việc này dẫn đến khả năng uyển chuyển khi sử dụng DLL: chương trình có thể thực hiện với một số chức năng bị thiếu (do không có DLL), thay đổi DLL khi ứng dụng đang thực thi,…
Tuy nhiên, cách gọi này có bất tiện là tốn nhiều công sức cho việc viết phần thực thi gọi hàm
Tạo ứng dụng
Ứng dụng được tạo ra kiểu dialog-based giống phần minh hoạ phía trên
Trang 10Các hàm nhập cần thiết
Trong ứng dụng này, chúng ta sẽ sử dụng hai hàm xuất của DLL là: Cong, và Nhan Hai hàm này có dạng:
int Cong(int a, int b);
int Nhan(int a, int b);
Chính vì vậy, chúng ta sẽ định nghĩa con trỏ hàm MYPROC cho hai hàm này như
sau:
typedef int (*MYPROC)(int, int);
Cách thức nạp hàm từ DLL:
- Nạp DLL bằng hàm LoadLibrary Kiểm tra xem DLL có tồn tại không bằng cách
xem kết quả trả về có khác NULL hay không
- Nạp hàm cần thiết bằng hàm GetProcAddress Kiểm tra việc gọi hàm thành công
hay không
- Giải phóng DLL khỏi bộ nhớ bằng hàm FreeLibrary
Ví dụ:
hInstance = LoadLibrary ( TENTAPTINDLL );
MYPROC Ham ;
if ( hInstance == NULL )
{
//Tap tin DLL khong ton tai return;
}
Ham = ( MYPROC )GetProcAddress( hInstance , HamCanNap );
if ( Ham == NULL )
{
//Ham khong ton tai hoac khong dung return;
}
FreeLibrary(hInstance);
Viết phần thực thi cho các hàm
Hàm xử lý sự kiện nhấn nút Cong
void CUsingRegularDLLRuntimeDlg::OnCong()
{
// TODO: Add your control notification handler code here
hInstance = LoadLibrary ( "RegularDLL.dll" );
MYPROC Cong ;
Trang 11if ( hInstance == NULL )
{
MessageBox ( "Tap tin RegularDLL.dll khong ton tai" , "Loi" );
return;
}
Cong = ( MYPROC )GetProcAddress( hInstance , "Cong" );
{
MessageBox ( "Khong ton tai ham Cong trong DLL." , "Loi" );
return;
}
UpdateData();
m_iZ = Cong( m_iX , m_iY );
UpdateData( FALSE );
FreeLibrary(hInstance);
}
Hàm xử lý sự kiện nhấn nút Nhan
void CUsingRegularDLLRuntimeDlg::OnNhan()
{
// TODO: Add your control notification handler code here
hInstance = LoadLibrary ( "RegularDLL.dll" );
MYPROC Nhan ;
if ( hInstance == NULL )
{
MessageBox ( "Tap tin RegularDLL.dll khong ton tai" , "Loi" );
return;
}
Nhan = ( MYPROC )GetProcAddress( hInstance , "Nhan" );
{
MessageBox ( "Khong ton tai ham Nhan trong DLL." , "Loi" );
return;
}
UpdateData();
m_iZ = Nhan( m_iX , m_iY );
UpdateData( FALSE );
FreeLibrary(hInstance);
}
Thử nghiệm
Thực hiện thử nghiệm dưới đây để hiểu rõ hơn cách gọi DLL theo kiểu run-time
- Không chép DLL vào cùng vị trí của ứng dụng sử dụng DLL Chạy ứng dụng được không? Có thực thi được các chức năng (Cong, Nhan) của ứng dụng không?
- Không thoát ứng dụng Chép DLL vào cùng vị trí của ứng dụng sử dụng DLL Thực thi được các chức năng của ứng dụng không?
Trang 12Debug với DLL
Khi cần debug hàm trong DLL, chúng ta sẽ thực thi DLL dựa trên một ứng dụng sử dụng DLL Trong ví dụ minh hoạ dưới đây, chúng tôi sử dụng DLL (Regular.dll) và ứng dụng (UsingRegularDLL.exe) được tạo lập sẵn để thực hiện việc debug
- Tạo breakpoint tại vị trí cần debug (hàm Cong)
- Thực thi DLL ở chế độ Debug Khi thực thi, môi trường lập trình sẽ yêu cầu chọn lựa ứng dụng sử dụng DLL để thực hiện
Có thể xuất hiện lỗi sau:
Lỗi đó xuất hiện do ứng dụng không tìm thấy DLL để thực thi Cần phải chép DLL vào cùng thư mục của ứng dụng và chạy debug lại cho DLL lại Khi đó, có thể xuất hiện màn hình debug cho DLL như sau:
Trang 13Tài liệu tham khảo
1 “DLL tutorial”, (MindCracker)
2 MSDN