nguyendang95
Thành viên chính thức


- Tham gia
- 25/5/22
- Bài viết
- 94
- Được thích
- 91
Có lẽ rất nhiều người dùng đã quen với các dạng add-in mà Excel hỗ trợ, những dạng phổ biến hiện nay (không tính những add-in chỉ dùng cho các bản Office cũ hơn bản 2007) bao gồm .xlam và .xll, trong đó .xlam được viết bằng VBA, còn .xll được viết bằng C/C++ và một số ngôn ngữ lập trình khác như C#, Delphi, v.v., bất cứ ngôn ngữ lập trình nào hỗ trợ khả năng xuất ra DLL tiêu chuẩn (Standard DLL). Với .xlam, người dùng có thể tương tác sâu rộng với Excel cũng như có thể viết hàm tùy chỉnh (hoặc hàm do người dùng tự định nghĩa, user-defined function) để sử dụng trong bảng tính, trong khi .xll chỉ hỗ trợ khả năng tương tác hạn chế với Excel thông qua Excel C API , ngoài ra loại add-in này còn cho phép người dùng tạo ra sheet macro và dialog (những tính năng này đều đã lỗi thời và đã bị macro VBA thay thế từ rất lâu), bên cạnh đó .xll cũng cho phép người dùng tạo ra các hàm tùy chỉnh để dùng trong bảng tính và hiệu năng mà nó mang lại vượt trội so với .xlam của VBA.
Để lập trình một .xll add-in bằng C/C++, người dùng cần đáp ứng những tiêu chí sau đây:
Đầu tiên, người dùng mở Visual Studio, chọn Create New Project, sau đó chọn tiếp Dynamic-Link Library (DLL) và lưu lại dự án mới.

Tiếp theo, tiến hành cài đặt Excel Software Development Kit, đường dẫn mặc định là C:\2013 Office System Developer Resources, sau đó sao chép những tệp sau đây vào thư mục của dự án vừa mới tạo ở trên:
1. Excel2013XLLSDK\INCLUDE:

Tiến hành viết code, ở đây viết một hàm tên là PrintHello với một tham số duy nhất kiểu chuỗi, nó chỉ có tác dụng in ra dòng chữ "Hello, <tham_số>". Lưu ý, do từ phiên bản Excel 2007 trở đi đều có tính năng Multithreaded Recalculation và Multithreaded Calculation cho phép Excel tận dụng số nhân xử lý của CPU để thực hiện việc tính toán, cho nên tiện lợi nhất là người dùng nên sử dụng Thread-local Storage API nhằm tránh những rắc rối như race condition (điều kiện tranh giành) do tính toán đa luồng gây ra khiến cho kết quả tính toán bị sai lệch.
Tiến hành biên dịch ra DLL, sau đó đổi phần mở rộng thành .xll và đưa vào Excel.

Kiểm tra add-in đã được Excel nạp vào.

Viết thử hàm.

Để biết thêm thông tin chi tiết về những tính năng mà .xll add-in hỗ trợ, tham khảo bài viết Programming with the C API in Excel
Để lập trình một .xll add-in bằng C/C++, người dùng cần đáp ứng những tiêu chí sau đây:
- Hiểu biết cơ bản về C/C++.
- Đã cài đặt Excel Software Development Kit (tải xuống ở đây).
Đầu tiên, người dùng mở Visual Studio, chọn Create New Project, sau đó chọn tiếp Dynamic-Link Library (DLL) và lưu lại dự án mới.

Tiếp theo, tiến hành cài đặt Excel Software Development Kit, đường dẫn mặc định là C:\2013 Office System Developer Resources, sau đó sao chép những tệp sau đây vào thư mục của dự án vừa mới tạo ở trên:
1. Excel2013XLLSDK\INCLUDE:
- XLCALL.H
- XLCALL32.LIB
- FRAMEWRK.C
- FRAMEWRK.H
- MemoryManager.cpp
- MemoryManager.h
- MemoryPool.cpp
- MemoryPool.h
- XLCALL.CPP

Tiến hành viết code, ở đây viết một hàm tên là PrintHello với một tham số duy nhất kiểu chuỗi, nó chỉ có tác dụng in ra dòng chữ "Hello, <tham_số>". Lưu ý, do từ phiên bản Excel 2007 trở đi đều có tính năng Multithreaded Recalculation và Multithreaded Calculation cho phép Excel tận dụng số nhân xử lý của CPU để thực hiện việc tính toán, cho nên tiện lợi nhất là người dùng nên sử dụng Thread-local Storage API nhằm tránh những rắc rối như race condition (điều kiện tranh giành) do tính toán đa luồng gây ra khiến cho kết quả tính toán bị sai lệch.
C++:
#include "XLCALL.H"
#include "FRAMEWRK.H"
#include "stdlib.h"
#include "string"
DWORD tlsIndex; //Khai báo một biến DWORD để lưu trữ chỉ số của Thread-local Storage mà các thread có thể sử dụng để lưu trữ dữ liệu cục bộ cho mỗi thread
//Khai báo struct để lưu trữ dữ liệu cục bộ cho mỗi thread
struct TlsData {
XLOPER12 data;
};
//Hàm này trả về struct từ Thread-local Storage mà thread hiện tại đang sử dụng
TlsData* GetTlsData() {
if (!tlsIndex) return NULL;
TlsData* tlsData = reinterpret_cast<TlsData*>(TlsGetValue(tlsIndex));
return tlsData;
}
//Khởi tạo Thread-local Storage
BOOL InitializeTls(DWORD DllMainCallReason) {
switch (DllMainCallReason) {
case DLL_PROCESS_ATTACH:
if ((tlsIndex = TlsAlloc()) == TLS_OUT_OF_INDEXES)
return FALSE;
case DLL_PROCESS_DETACH:
if (tlsIndex) {
TlsFree(tlsIndex);
break;
}
}
return TRUE;
}
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
//Khởi tạo Thread-local Storage
return InitializeTls(ul_reason_for_call);
}
//Hàm này chuyển chuỗi ký tự wide char sang định dạng mà Excel yêu cầu
wchar_t *CreateCountedUnicodeWcharStringFromNullTerminatedUnicodeWCharString(const wchar_t* text) {
size_t len; //Khai báo biến chứa độ dài của chuỗi cần chuyển đổi
if (!text || !(len = wcslen(text))) return NULL; //Nếu chuỗi rỗng thì trả về NULL
if (len > 32767u) len = 32767u; //Nếu độ dài chuỗi lớn hơn 32767u ký tự thì cắt gọt chuỗi
wchar_t *wstr = new wchar_t[(len + 2) * sizeof(wchar_t)]; //Cấp phát bộ nhớ động cho chuỗi kết quả, số lượng byte cần cấp phát là độ dài chuỗi cần chuyển đổi cộng thêm 2 ký tự (để dành ký tự cuối cùng làm ký tự kết thúc chuỗi) * 2 byte
if (!wstr) return NULL; //Nếu không cấp phát được bộ nhớ thì trả về NULL
memcpy(wstr + 1, text, (len + 1) * sizeof(wchar_t)); //Sao chép chuỗi cần chuyển đổi, bắt đầu từ ký tự thứ hai, vào chuỗi kết quả
wstr[0] = len; //Gán độ dài chuỗi vào vị trí đầu tiên của chuỗi kết quả
return wstr; //Trả về chuỗi kết quả
}
extern "C" __declspec(dllexport) LPXLOPER12 WINAPI PrintHello(wchar_t* name) {
//static LPXLOPER12 result; Không an toàn thread vì biến tĩnh này có thể bị ghi đè bởi các thread khác
TlsData* tlsData = GetTlsData(); //Thay vào đó, sử dụng Thread-local Storage API để lưu trữ dữ liệu cục bộ cho mỗi thread
if (!tlsData) return NULL;
LPXLOPER12 result = &(tlsData->data); //Khai báo biến con trỏ của kiểu XLOPER12 để lưu trữ kết quả trả về
result->xltype = xltypeStr | xlbitDLLFree; //Thiết lập kiểu trả về của hàm là chuỗi, đồng thời thông báo cho Excel biết rằng Excel sẽ giải phóng bộ nhớ cho chuỗi này do nó được cấp phát động
wchar_t wstrInput[100] = L"Hello, "; //Khai báo một mảng ký tự wide char để lưu trữ chuỗi kết quả
wcscat_s(wstrInput, name); //Nối chuỗi name vào chuỗi wstrInput
wchar_t* pWstrOutput = CreateCountedUnicodeWcharStringFromNullTerminatedUnicodeWCharString(wstrInput); //Chuyển chuỗi wstrInput sang định dạng mà Excel yêu cầu
result->val.str = pWstrOutput; //Gán địa chỉ của chuỗi kết quả vào biến con trỏ result
return result; //Trả về con trỏ result để Excel có thể sử dụng kết quả
}
//Excel gọi hàm này khi nạp add-in
extern "C" __declspec(dllexport) int WINAPI xlAutoOpen(void)
{
XLOPER12 xDLL;
//Xác định tên của DLL
if (Excel12f(xlGetName, &xDLL, 0) == xlretSuccess) {
Excel12f(xlfRegister, 0, 11, (LPXLOPER12)&xDLL, //Đăng ký tên của DLL
(LPXLOPER12)TempStr12(L"PrintHello"), //Tên hàm
(LPXLOPER12)TempStr12(L"UC%"), //Kiểu tham số, ký tự đầu tiên đại diện cho kiểu dữ liệu trả về của hàm, các ký tự tiếp theo là kiểu dữ liệu của các tham số
(LPXLOPER12)TempStr12(L"PrintHello"), //Tên hàm trong trình Function Wizard
(LPXLOPER12)TempStr12(L"name"), //Mô tả tham số trong trình Function Wizard
(LPXLOPER12)TempStr12(L"1"), //Kiểu macro
(LPXLOPER12)TempStr12(L"myOwnCppFunctions"), //Hạng mục
(LPXLOPER12)TempStr12(L""), //Tổ hợp phím tắt
(LPXLOPER12)TempStr12(L""), //Tên tập tin Help
(LPXLOPER12)TempStr12(L"Print 'Hello <user>'"), //Mô tả hàm khi được chọn trong trình Function Wizard
(LPXLOPER12)TempStr12(L"")); //Mô tả tham số khi được chọn trong trình Function Wizard (tối đa lên đến 255 tham số)
//Giải phóng biến xDLL
Excel12f(xlFree, 0, 1, (LPXLOPER12)&xDLL);
}
return 1;
}
//Excel gọi hàm này khi người dùng tắt Add-in hoặc thoát khỏi Excel
extern "C" __declspec(dllexport) int WINAPI xlAutoClose(void) {
return 1;
}
//Excel gọi hàm này khi kiểu XLOPER12 trả về có chứa cờ bit xlbitDLLFree hoặc xlbitXLFree để dọn dẹp bộ nhớ
extern "C" __declspec(dllexport) void WINAPI xlAutoFree12(LPXLOPER12 p_oper) {
//Nếu kiểu dữ liệu trả về là chuỗi
if (p_oper->xltype == xltypeStr) {
delete p_oper->val.str; //Giải phóng bộ nhớ đã cấp phát động cho chuỗi
}
}
Tiến hành biên dịch ra DLL, sau đó đổi phần mở rộng thành .xll và đưa vào Excel.

Kiểm tra add-in đã được Excel nạp vào.

Viết thử hàm.

Để biết thêm thông tin chi tiết về những tính năng mà .xll add-in hỗ trợ, tham khảo bài viết Programming with the C API in Excel