Tạo và sử dụng thư viện liên kết động trong C++ trên Windows

Chào các bạn đang theo dõi khóa học lập trình trực tuyến ngôn ngữ C++.

Thư viện (library) là một tập mã nguồn đã được đóng gói, có thể được tái sử dụng trong nhiều chương trình khác nhau.

Thư viện trong ngôn ngữ C++ gồm 2 thành phần chính:

  • Những header files khai báo các hàm có thể được sử dụng trong chương trình.
  • Tập hợp mã nguồn đã được biên dịch thành mã máy tương ứng với phần định nghĩa của các hàm đã được khai báo trong các header files.

Nhiều thư viện khi cung cấp đã được biên dịch sẵn vì nhiều lý do. Thông thường, hiếm khi có sử chỉnh sửa trong phần thư viện, nên không cần phải biên dịch nhiều lần. Thư viện cũng được biên dịch sẵn thành mã máy để tránh người dùng truy cập và chỉnh sửa.

Có 2 kiểu thư viện: thư viện liên kết tĩnh (static library) và thư viện liên kết động (dynamic library) .

Trong bài học này, chúng ta sẽ tìm hiểu xem thư viện liên kết động là gì? Cách sử dụng như thế nào? Và cùng thử tạo ra một thư viện liên kết động cho riêng mình.

Thư viện liên kết động (dynamic library)

Thư viện liên kết động là tập hợp những hàm, chức năng được tải lên chương trình của bạn trong thời điểm chương trình đang thực thi. Khi biên dịch chương trình có sử dụng thư viện liên kết động, bản thân thư viện đó sẽ không trở thành một phần trong chương trình thực thi, nhưng nó trở thành một đơn vị riêng biệt.

Trên hệ điều hành Windows, chúng ta có thể thấy những file có phần mở rộng .dll viết tắt của “dynamic-link library”. Trên hệ điều hành Linux, thư viện liên kết động có phần mở rộng .so viết tắt của “shared object”.

Ưu điểm của thư viện liên kết động:

  • Tiết kiệm bộ nhớ lưu trữ thư viện trên ổ cứng: nhiều tiến trình có thể sử dụng chung một thư viện liên kết động.
  • Khi chức năng trong thư viện liên kết động thay đổi, chương trình sử dụng thư viện đó không cần phải biên dịch lại.
  • Chương trình viết trên nhiều ngôn ngữ lập trình khác nhau có thể gọi đến cùng một thư viện liên kết động.

Tuy nhiên, thư viện liên kết động cũng có nhược điểm:

  • Chương trình có yêu cầu sử dụng thư viện liên kết động sẽ không thể thực thi khi không tìm thấy thư viện tương ứng.
  • Giảm hiệu suất trong quá trình chương trình đang chạy, vì khi chức năng trong thư viện được gọi, chương trình cần thời gian để tìm kiếm và tải lên RAM.

Bây giờ, chúng ta cùng nhau làm từng bước để tạo ra một thư viện liên kết động đơn giản.

Tạo một dự án để xây dựng thư viện liên kết động

Chúng ta cùng tạo một Project mới bằng cách chọn File -> New -> Project :

Tại đây, với Visual Studio 2015/2017, mình chọn kiểu Project là Dynamic-Link Library , đặt tên là MathDLL. Thư viện này sẽ chứa một số hàm toán học đơn giản.

Sau khi nhấn OK, Visual Studio IDE sẽ tạo ra một dự án mới với cấu trúc như bên dưới. Lúc này, các bạn chưa cần quan tâm đến chúng làm gì.

Sau khi build thử dự án này, kết quả biên dịch sẽ được xuất ra trong 1 file có tên là MathDLL.dll như bên dưới:

Vì mình đang để ở chế độ build Debug, nên file thư viện này sẽ nằm trong thư mục Debug, và tương thích với các dự án khác ở trong chế độ build Debug.

Sau này, các bạn cũng nên build thêm một bản Release, bản này sẽ dùng để phân phối đến những lập trình viên khác sử dụng để Release sản phẩm của họ.

Tiếp theo, các bạn click chuột phải vào project hiện tại, chọn Properties . Các bạn vào Configuration Properties -> C/C++ -> Preprocessor và xem trong phần Preprocessor Definations :

Nếu các bạn nhìn thấy MATHDLL_EXPORTS trong danh sách Preprocessor Definations, các bạn không cần chỉnh sửa gì thêm. Nếu bạn thấy MathDLL_EXPORTS hoặc tương tự, các bạn vào sửa lại thành MATHDLL_EXPORTS.

Đến đây, coi như chúng ta đã thành công trong bước đầu tạo một thư viện liên kết động. Nếu có trục trặc gì xảy ra, các bạn nên xem lại trước khi đến với phần tiếp theo.

Tạo phần header file cho thư viện

Bây giờ, chúng ta cùng tạo một header file chứa phần khai báo cho các chức năng trong thư viện:

Mình đặt tên cho header file này là MathLib.h nhằm khai báo một số hàm tính toán cơ bản. Và dưới đây là code cho header file:

#pragma once
​
#ifdef MATHDLL_EXPORTS
#define MATHDLL_API __declspec(dllexport)
#else
#define MATHDLL_API __declspec(dllimport)
#endif
​
extern "C" MATHDLL_API double Add(double a, double b);
​
extern "C" MATHDLL_API double Sub(double a, double b);
​
extern "C" MATHDLL_API double Mul(double a, double b);
​
extern "C" MATHDLL_API double Div(double a, double b);

Đến đây, có lẽ một số thứ khá mới đối với các bạn mới học lập trình.

Ví dụ, extern “C” có nghĩa là gì? Đây là cách để những hàm viết trong ngôn ngữ C++ có thể sử dụng được trong những chương trình viết trên ngôn ngữ C. Các bạn tham khảo thêm ở đây.

Và định nghĩa MATHDLL_API với __declspec(dllexport)__declspec(dllimport) nhằm mục đích gì? Các bạn còn nhớ ở phần trên, khi chúng ta tạo Project để xây dựng thư viện liên kết động, chúng ta có vào phần Preprocessor Definations để kiểm tra xem có tồn tại cờ (flag) có tên là MATHDLL_EXPORTS hay chưa phải không?

Cờ MATHDLL_EXPORTS này sẽ chỉ tồn tại trong Project MathDLL (tên mình đặt) mình vừa tạo, và nó sẽ hiếm khi bị trùng với những cờ trong các project khác.

Vậy, trong dự án tạo thư viện liên kết động này, chúng ta sẽ kiểm tra xem nếu có tồn tại cờ MATHDLL_EXPORTS , thì sẽ sử dụng khai báo __declspec(dllexport) để báo cho compiler biết rằng chúng ta muốn xuất những chức năng này ra file .dll .

Và trong những project khác, sẽ không tồn tại cờ MATHDLL_EXPORTS , thì khai báo __declspec(dllimport) để báo cho compiler biết rằng những hàm đó sẽ được tải lên từ thư viện liên kết động trong quá trình chương trình thực thi.

Xây dựng phần định nghĩa cho các chức năng trong thư viện

Sau khi tạo Project MathDLL , chúng ta sẽ thấy file MathDLL.cpp đã được thêm vào sẵn. Chúng ta không cần phải tạo thêm file .cpp nào khác. Hoặc nếu các bạn muốn tạo thêm, và đặt tên theo cách các bạn dễ hiểu cũng được cả.

Trong file MathDLL.cpp mình định nghĩa các hàm như sau:

// MathDLL.cpp : Defines the exported functions for the DLL application.
//
​
#include "stdafx.h"
#include "MathLib.h"
​
double Add(double a, double b)
{
    return a + b;
}
​
double Sub(double a, double b)
{
    return a - b;
}
​
double Mul(double a, double b)
{
    return a * b;
}
​
double Div(double a, double b)
{
    return a / b;
}

Sử dụng thư viện liên kết động trong chương trình của bạn

Lúc này, chúng ta cùng thử biên dịch lại thư viện một lần nữa.

Kết quả biên dịch thành công nghĩa là bạn đã có một thư viện liên kết động cho riêng mình.

Điều chúng ta cần quan tâm bây giờ là làm sao để sử dụng thư viện này trong dự án hoặc chương trình khác.

Mình quay trở lại với project mẫu mình đã tạo trước đó:

Mình đã clean sạch sẽ, và chỉ để lại một file main.cpp để dùng thử thư viện mình vừa tạo ra.

Để thuận tiện hơn trong quá trình liên kết thư viện, chúng ta cùng tạo một thư mục include và thư mục lib trong thư mục chứa project hiện hành. Một vài bước tiếp theo khá giống với việc liên kết thư viện liên kết tĩnh.

Thư mục include sẽ chứa các header files của thư viện. Trong thư viện MathDLL , chúng ta chỉ có tạo ra một header file duy nhất, nên chỉ cần copy file đó vào trong thư mục include vừa tạo ra của chương trình của các bạn:

Và thư mục lib sẽ chứa file thư viện đã biên dịch:

Bây giờ, chúng ta sẽ vào phần Properties của dự án để cấu hình liên kết thư viện liên kết động.

Các bạn click chuột phải vào dự án, chọn Properties -> C/C++ -> General , trong phần Additional Include Directories , các bạn trỏ đến thư mục include chứa header file của thư viện mà các bạn đã copy vào đó:

Tiếp đến, chọn Properties -> Linker -> General , trong phần Additional Library Directories , các bạn trỏ đến thư mục lib chứa file thư viện đã biên dịch mà các bạn đã copy vào đó:

Cũng trong cửa sổ Properties, các bạn chọn thẻ Linker -> Input , tìm đến dòng Additional Dependencies :

Trong phần này, các bạn điền vào tên các thư viện cần dùng trong chương trình kèm theo phần đuôi mở rộng nha.

Chạy chương trình

Bây giờ thì sử dụng các chức năng trong thư viện thôi các bạn. Trong file main.cpp mình sử dụng đơn giản như sau:

#include <iostream>
#include "MathLib.h"
​
int main()
{
std::cout << Add(3, 5) << std::endl;
std::cout << Sub(3, 5) << std::endl;
std::cout << Mul(3, 5) << std::endl;
std::cout << Div(3, 5) << std::endl;

system("pause");
return 0;
}

Lúc này, các bạn biên dịch, và chạy chương trình sẽ gặp lỗi như sau:

Còn một việc các bạn cần phải làm là copy file .dll (theo dự án mình sử dụng là MathDLL.dll) của thư viện vào trong thư mục chứa file thực thi (.exe) của dự án:

Như các bạn thấy, thư viện liên kết động là một phần tách biệt so với chương trình thực thi.

Hẹn gặp lại các bạn trong bài học tiếp theo trong khóa học lập trình C++ hướng thực hành.

Mọi ý kiến đóng góp hoặc thắc mắc có thể đặt câu hỏi trực tiếp tại diễn đàn.

www.daynhauhoc.com

4 Likes

Hi nguyenchiemminhvu.
Mình có xem lại phần tách file mã nguồn nhưng chưa rõ lắm phần #gì gì đó trong phần đầu file .h bạn có thể viết một bài chi tiết hơn về phần đó được không.
Mình cảm ơn.

2 Likes

Ý bạn là cái #pragma once hay là cái cụm #ifndef… , Cái #pragma once để file header bị include nhiều lần gây ra lỗi, cái đoạn dưới thì nó như if else thôi, chỗ dllimport, export trong đoạn đó bác ấy cũng trả lời rồi

1 Like

Cho mình hỏi ý nghĩa của MATH_API là gì, mình thấy trong chương trình nó ko cần thiết sử dụng *_API?

83% thành viên diễn đàn không hỏi bài tập, còn bạn thì sao?