Thử lập trình XLL add-in bằng C/C++ (1 người xem)

Liên hệ QC

Người dùng đang xem chủ đề này

Tôi tuân thủ nội quy khi đăng bài

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:
  • Hiểu biết cơ bản về C/C++.
  • Đã cài đặt Excel Software Development Kit (tải xuống ở đây).
Để viết mã C/C++, người dùng có thể chọn môi trường lập trình tích hợp (IDE) bất kỳ, ở đây tôi chọn Visual Studio bởi vì đã quen với giao diện của nó cũng như làm việc nhiều với Visual C++.
Đầ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.
1740587660860.png
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
2. Excel2013XLLSDK\LIB\x64 (với Excel bản 64bit), hoặc Excel2013XLLSDK\LIB (với Excel bản 32bit):
  • XLCALL32.LIB
3. Excel2013XLLSDK\SAMPLES\FRAMEWRK:
  • FRAMEWRK.C
  • FRAMEWRK.H
  • MemoryManager.cpp
  • MemoryManager.h
  • MemoryPool.cpp
  • MemoryPool.h
4. Excel2013XLLSDK\SRC:
  • XLCALL.CPP
1740588008767.png
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.
1740589448063.png
Kiểm tra add-in đã được Excel nạp vào.
1740589490521.png
Viết thử hàm.
1740589526459.png
Để 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
 
Web KT

Bài viết mới nhất

Back
Top Bottom