Trình điều khiển cl (MSVC)
cl.exe là trình điều khiển C/C++ chính thức của Microsoft, đi kèm với Visual Studio (hoặc Build Tools for Visual Studio). Trình điều khiển này tuân theo hệ sinh thái MSVC và sử dụng cú pháp Windows-style (với dấu /) thay vì UNIX-style (- như gcc/clang).
Nó tích hợp chặt chẽ với linker link.exe, các trình tối ưu như /O2, /GL, và hệ thống debug PDB.
Cú pháp tổng quát
cl [file.cpp] [cờ tiền xử lý] [cờ biên dịch] [cờ cảnh báo] [cờ tối ưu] [cờ include] [/link [cờ linker]]
- Các cờ compiler nhóm theo chức năng: preprocess, biên dịch, cảnh báo, tối ưu, include, ...
/linknên đặt cuối lệnh, vì mọi thông số sau đó sẽ được chuyển sang linkerlink.exe
Các nhóm cờ và giải thích
Dưới đây sẽ liệt kê các cờ cơ bản và phổ biến đại diện cho mỗi nhóm cờ trong cú pháp tổng quát.
- Cờ tiền xử lý
/D<macro>: Định nghĩa macro/U<macro>: Xóa macro/FI<file>: Yêu cầu trình biên dịch tự động include một file header (.h/.hpp) vào đầu mỗi file nguồn trong quá trình tiền xử lý/E: Chỉ tiền xử lý, in ra stdout/showIncludes: In danh sách file header
- Cờ biên dịch / cấu hình
/c: Chỉ biên dịch, không link/Fo:<file.obj>: Tên file đối tượng đầu ra/Fe:<file.exe>: Tên file thực thi đầu ra/LD: Tạo DLL (tương đương/link /DLL)/MD,/MT: Liên kết runtime C++ dạng DLL hoặc static/EHsc: Bật exception C++/GR-: Tắt RTTI/m32//m64: Chỉ định kiến trúc 32-bit hoặc 64-bit khi biên dịch/Zc:__cplusplus: Đảm bảo macro__cplusplusđúng chuẩn
- Cờ cảnh báo
/W3,/W4,/Wall: Mức độ cảnh báo/WX: Chuyển cảnh báo thành lỗi/permissive-: Tuân thủ nghiêm C++ standard/std:c++20: Chọn chuẩn C++
- Cờ tối ưu hóa
/Od: Tắt tối ưu (debug)/O1,/O2,/Ox: Tối ưu hiệu năng/GL: Bật Link-Time Code Generation (LTO)/Gw: Tối ưu global/GS-: Tắt stack check (không khuyến nghị)
- Cờ include
/I<dir>: Thêm thư mục include/external:I<dir>: External header, ý nghĩa tương tự cờ-isystem <dir>củag++, cảnh báo trong các file header ở đây sẽ bị bỏ qua./external:W0: Tắt cảnh báo external
- Cờ linker (sau /link)
/link /OUT:<file.exe>: Tên file exe/IMPLIB:<file.lib>: Tạo import library khi build DLL/DLL: Xác nhận build DLL/LIBPATH:<dir>: Thư mục chứa.lib/SUBSYSTEM:<arg>: Chỉ định loại chương trình:console,windows,native,... — ảnh hưởng đến entry point và hiển thị CMD.mylib.lib,user32.lib,...: Các file lib liên kết
Lựa chọn cờ đầu ra: /Fe: hay /link /OUT:?
- Nên ưu tiên dùng
/Fo:<file.obj>và/Fe:<file.exe>để đặt tên file đầu ra ngay trong trình biên dịchcl - Tránh dùng
/link /OUT:trừ khi bạn cần điều khiển linker rất chi tiết (export symbol, PDB, DEF file...) - Các cờ như
/Fe:,/Fo:tích hợp tốt với các cờ debug hoặc chia nhỏ build theo file - Sử dụng
/Fe:rõ ràng hơn, dễ dùng hơn và là best practice khi biên dịch theo phong cách MSVC
Trình liên kết của cl
Trình liên kết mặc định trong hệ sinh thái MSVC là link.exe, được tích hợp sẵn khi sử dụng trình biên dịch cl.exe. Đây là linker của Microsoft, hoạt động tối ưu cho định dạng PE/COFF trên Windows.
- Tương thích chặt chẽ với cl.exe: Mọi lệnh biên dịch không có
/csẽ tự động gọilink.exeđể thực hiện liên kết. - Hỗ trợ sinh nhiều loại file đầu ra:
- File thực thi
.exe - Thư viện động
.dll+ import library.lib - Thư viện tĩnh
.lib - File debug
.pdbkèm symbol
- File thực thi
- Tối ưu cho hệ sinh thái Windows:
- Tích hợp tốt với Visual Studio và MSBuild
- Hỗ trợ chuẩn gọi hàm Windows (
__stdcall,__cdecl,__fastcall) - Hỗ trợ file
.defđể xuất symbol cụ thể
- Hiệu suất ổn định và tối ưu hóa runtime:
- Tương thích với Link-Time Code Generation (
/GL,/LTCG) - Có thể sinh file
.mapđể phân tích bố cục bộ nhớ chương trình - Tối ưu hóa kích thước và tốc độ khi kết hợp với
/O2,/OPT:REF,/OPT:ICF
- Tương thích với Link-Time Code Generation (
- Không tương thích với linker GNU hoặc LLVM (như
ld,lld):link.exesử dụng cú pháp và flag riêng biệt (Windows-style)- Không dùng được với file
.o,.akiểu ELF
- Mọi tùy chọn dành cho
link.exe(như/OUT:,/DLL,/LIBPATH:,/DEF:,...) phải được đặt sau/linkkhi gọi từ dòng lệnhcl.exe. - Khi dùng với
clang-cl,link.exevẫn là linker mặc định nếu không cấu hìnhlld-linkhoặc linker khác.
Ví dụ
Biên dịch file thực thi .exe:
cl main.cpp utils.cpp /std:c++20 /Fe:build\myapp.exe /W4 /O2 /Iinclude /link /LIBPATH:lib mylib.lib
Giải thích:
- File nguồn:
main.cpp,utils.cpp - Cờ biên dịch:
/std:c++20,/Fe:build\myapp.exe(Chọn chuẩn C++ và chỉ định file đầu ra) - Cờ cảnh báo:
/W4(Cấu hình mức cảnh báo) - Cờ tối ưu:
/O2(Tối ưu hóa tốc độ) - Cờ include:
/Iinclude(Thêm thư mục chứa header) - Từ khóa bắt buộc để chuyển phần còn lại cho linker:
/link - Cờ linker:
/LIBPATH:lib mylib.lib(Chỉ định thư mục và thư viện để liên kết)
Biên dịch và sử dụng thư viện tĩnh (.lib)
1. Biên dịch mã nguồn thành file đối tượng (.obj)
- Mục đích: Biên dịch các file
.cppthành file đối tượng.objđể chuẩn bị đóng gói thành thư viện.lib - Cú pháp tổng quát:
cl /c <file.cpp> /Fo:<file.obj> /MT [cờ biên dịch khác]- Nếu không chỉ định
/MT,/MD,/MTd, hoặc/MDd, thìclsẽ mặc định dùng/MD(liên kết động với runtime DLLMSVCRT.dll), trừ khi bạn override bằng tham số khác trong hệ thống build.
- Nếu không chỉ định
- Ví dụ áp dụng:
cl /c mylib.cpp /Fo:build\mylib.obj /MT /std:c++20 /W4 /EHsc/c: Chỉ biên dịch, không liên kết./Fo: Đặt tên file đối tượng đầu ra.obj./MT: Chỉ định runtime là liên kết tĩnh./std:c++20: Chỉ định biên dịch theo tiêu chuẩn C++20.
2. Đóng gói file đối tượng thành thư viện tĩnh (.lib)
- Mục đích: Tạo thư viện tĩnh
.libtừ một hoặc nhiều file.obj - Cú pháp tổng quát:
lib <file.obj> [/OUT:<file.lib>] - Ví dụ áp dụng:
lib build\mylib.obj /OUT:lib\mylib.lib - Kiểm tra thư viện sau khi đóng gói:
Sau khi tạo
.dllvà.lib, bạn có thể dùng các công cụ dòng lệnh của MSVC để kiểm tra thông tin trong các file thư viện.-
Liệt kê symbol trong
.libhoặc.dlldumpbin /EXPORTS lib\mylib.dll- Hiển thị danh sách các hàm được export.
- Dùng để xác nhận các hàm đã được export đúng (hữu ích nếu bạn không dùng
.defhoặc gặp lỗi unresolved symbol).
dumpbin /SYMBOLS lib\mylib.lib- Hiển thị symbol trong import library
.lib, xác định các tên hàm mà linker sẽ dùng khi bạn biên dịch chương trình chính.
-
Hiển thị thông tin toàn bộ file
dumpbin /ALL lib\mylib.dll > dllinfo.txt- Ghi toàn bộ thông tin chi tiết về cấu trúc file .dll vào file dllinfo.txt, bao gồm:
- Header PE
- Import table, export table
- Base relocation, debug info (nếu có)
- Ghi toàn bộ thông tin chi tiết về cấu trúc file .dll vào file dllinfo.txt, bao gồm:
-
Liệt kê các file
.objtrong thư viện.liblib /LIST lib\mylib.lib- Tương tự
llvm-ar -t, dùng để xác nhận thư viện.libchứa những file.objnào.
- Tương tự
-
Trích xuất .obj từ
.lib(nếu cần)lib /EXTRACT:mylib.obj lib\mylib.lib- Trích xuất một file
.objtừ thư viện.lib. Có thể dùng để kiểm tra hoặc debug riêng file.obj.
- Trích xuất một file
lưu ý- Các công cụ này như
dumpbin.exevàlib.exeđều đi kèm với Developer Command Prompt for Visual Studio. - Nếu bạn không tìm thấy lệnh
dumpbin, hãy mở terminal đúng môi trường hoặc thêm đường dẫn đếnVC\Tools\...vàoPATH.
-
3. Liên kết thư viện .lib với chương trình chính
- Mục đích: Biên dịch chương trình chính (
.cpp) và liên kết với thư viện tĩnh.libđể tạo file thực thi - Cú pháp tổng quát:
cl <main.cpp> [cờ biên dịch khác] /I<dir_include> /Fe:<output.exe> /link /LIBPATH:<dir_lib> <libname.lib> - Ví dụ áp dụng:
cl main.cpp /std:c++20 /W4 /O2 /Iinclude /Fe:bin\myapp.exe /link /LIBPATH:lib mylib.libmain.cpp→[file.cpp]/std:c++20,/Fe:...→[cờ biên dịch]/W4→[cờ cảnh báo]/O2→[cờ tối ưu]/Iinclude→[cờ include]/link→ tách nhóm linker/LIBPATH:lib,mylib.lib→[cờ linker]
Biên dịch và sử dụng thư viện động (.dll)
1. Biên dịch mã nguồn thành file đối tượng (.obj)
-
Mục đích: Biên dịch file nguồn (
.cpp) thành file đối tượng (.obj) để sử dụng trong quá trình tạo DLL. -
Cú pháp tổng quát:
cl /c <file.cpp> /Fo:<file.obj> /MD [cờ biên dịch khác]- Nếu không chỉ định rõ
/MT,/MD,/MTdhoặc/MDd, thìclsẽ mặc định sử dụng/MD(liên kết động với runtime DLLMSVCRT.dll). Tuy nhiên, để đảm bảo nhất quán và tránh lỗi liên kết, nên khai báo rõ ràng/MDtrong lệnh biên dịch khi mục tiêu là tạo thư viện.dll. - Điều này đặc biệt quan trọng khi bạn xây dựng hệ thống có nhiều module
.dllvà.execần chia sẻ cùng runtime.
- Nếu không chỉ định rõ
-
Ví dụ áp dụng:
cl /c src\mylib.cpp /Fo:build\mylib.obj /MD /std:c++20 /W4 /EHsc /DBUILD_MYLIB/c: Chỉ biên dịch, không liên kết (tạo.obj)src\mylib.cpp: File nguồn cần biên dịch/Fo:build\mylib.obj: Chỉ định tên file đối tượng đầu ra/MD: Chỉ định sử dụng runtime động. Khi liên kết và chạy, chương trình sẽ phụ thuộc vào các DLL nhưMSVCRT.dll,vcruntime140.dll, vàucrtbase.dll(tùy theo phiên bản công cụ). Đây là cách dùng mặc định khi chia sẻ runtime giữa nhiều module và giảm kích thước file.exe./std:c++20: Biên dịch theo tiêu chuẩn C++20/W4: Bật mức cảnh báo cao (Level 4)/EHsc: Bật xử lý ngoại lệ C++ (try-catch), không hỗ trợ SEH/DBUILD_MYLIB: Định nghĩa macroBUILD_MYLIB(tương đương#define BUILD_MYLIB)
-
Header file cần thiết:
- Sử dụng
__declspec(dllexport)khi build DLL, và__declspec(dllimport)khi sử dụng DLL trong chương trình khác. - Dùng macro để gom cả 2 tình huống:
#ifdef BUILD_MYLIB
#define MYLIB_API __declspec(dllexport)
#else
#define MYLIB_API __declspec(dllimport)
#endif
extern "C" MYLIB_API int add(int a, int b);
- Sử dụng
Calling convention là gì?
-
Calling convention là gì?
Calling convention (quy ước gọi hàm) là quy tắc quy định:- Thứ tự truyền tham số
- Ai chịu trách nhiệm thu dọn stack (caller hay callee)
- Cách đặt tên hàm (name mangling) (nếu có)
-
Các loại phổ biến trong Windows (x86)
Tên gọi Từ khóa Truyền tham số Thu dọn stack Dùng phổ biến ở đâu Cdecl __cdeclTừ phải sang trái Caller Mặc định trong C/C++ Stdcall __stdcallTừ phải sang trái Callee Windows API, AutoIt, VB6 Fastcall __fastcallMột số dùng thanh ghi Callee Hiệu năng cao (ít dùng) Thiscall __thiscallthistrongecx, còn lại trên stackCallee Mặc định với hàm thành viên C++ a.
__cdecl- Caller (người gọi) phải clean stack sau khi gọi hàm.
- Cho phép overload số lượng tham số (
printf,scanf) - Thường dùng mặc định trong C/C++ compile
__cdecl int add(int a, int b);
b.
__stdcall- Callee (hàm được gọi) sẽ tự động dọn stack.
- Thường dùng cho Windows API, DLL để gọi từ ngôn ngữ khác
- Gọi từ AutoIt, VB6, Pascal, C# (P/Invoke) → nên dùng
__stdcall__stdcall int add(int a, int b); // gọn hơn cho người gọi
c.
__fastcall- Truyền 2 tham số đầu tiên qua thanh ghi (
ecx,edx) → nhanh hơn. - Stack dọn bởi callee.
- Thường không cần thiết trừ khi tối ưu hóa chuyên sâu.
__fastcall int add(int a, int b);
d.
__thiscall(mặc định cho C++ member function)thisnằm trong thanh ghiecx- Các tham số còn lại trên stack
- Không dùng được trong
extern "C"(vì C không cóthis)
Nếu không khai báo rõ __stdcall, thì hàm export từ DLL sẽ dùng calling convention mặc định, thường là:
__cdecltrên Windows x86- Trên Windows x64: calling convention mặc định là Microsoft x64 ABI, không cần khai báo (vì tất cả dùng chung)
TÌNH HUỐNG:
- Nếu đang dùng Windows 64-bit:
- Không có
__stdcallhay__cdeclphân biệt nữa - Tất cả hàm gọi giữa module đều theo x64 calling convention (được thống nhất)
- Chương trình khác (64-bit) vẫn gọi được hàm dù không khai báo
__stdcall
- Không có
- Nhưng nếu viết DLL 32-bit và chương trình khác (32-bit) sử dụng DLL này:
- Thì phải chỉ định rõ
__stdcall; Nếu không, sẽ xảy ra:- Lỗi stack khi trả về từ hàm
- Treo chương trình hoặc kết quả sai
- Thì phải chỉ định rõ
2. Tạo thư viện động (.dll) và import library (.lib)
- Mục đích: Tạo
.dlltừ file.obj, đồng thời sinh file import.libđể dùng cho liên kết tĩnh (implicit linking). - Cú pháp tổng quát:
cl /LD <file.obj> [/I<dir>] [/D<macro>] /Fe:<file.dll> [cờ biên dịch khác] /link /IMPLIB:<file.lib> [/DEF:<file.def>] [/LIBPATH:<dir>] [lib1.lib ...] [/DEBUG] [cờ linker khác] - Ví dụ áp dụng:
cl /LD build\mylib.obj /Fe:lib\mylib.dll /link /IMPLIB:lib\mylib.lib/LD: Yêu cầu tạo thư viện động.dll(tự động bật/DLLkhi chuyển cho linker)build\mylib.obj: File đối tượng được liên kết thành.dll/Fe:lib\mylib.dll: Đặt tên file đầu ra làmylib.dlltrong thư mụclib\/link: Bắt đầu nhóm cờ dành cho linker (link.exe)/IMPLIB:lib\mylib.lib: Tạo file import library.libđể hỗ trợ liên kết tĩnh (implicit linking)
3. Sử dụng thư viện động (.dll)
-
Liên kết tĩnh (Implicit Linking)
- Cú pháp tổng quát:
cl <main.cpp> [cờ biên dịch khác] /I<dir_include> /Fe:<output.exe> /link /LIBPATH:<dir_lib> <import.lib> - Yêu cầu:
- File
.liblà import library (do/IMPLIB:sinh ra) - File
.hphải dùng__declspec(dllimport)khi include - File
.dllphải tồn tại khi chạy chương trình
- File
- Lưu ý quan trọng:
mylib.dllphải nằm cùng thư mục với.exe, hoặc phải khai báo trong%PATH%- Có thể dùng API như
SetDllDirectory()để bổ sung thư mục DLL tại runtime
- Cú pháp tổng quát:
-
Tải động (Explicit Linking)
- Không cần
mylib.libhaymylib.h, nhưng cần:- Biết tên hàm và prototype
- Dùng
LoadLibrary,GetProcAddress
- Cú pháp biên dịch:
cl main.cpp /Fe:myapp.exe - Mã ví dụ:
#include <windows.h>
#include <iostream>
typedef int (*AddFunc)(int, int);
int main() {
HMODULE h = LoadLibrary("lib\\mylib.dll");
if (!h) return 1;
AddFunc add = (AddFunc)GetProcAddress(h, "add");
if (!add) return 2;
std::cout << "2 + 3 = " << add(2, 3) << "\n";
FreeLibrary(h);
}
- Không cần