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

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 thường trực
    Tham gia
    25/5/22
    Bài viết
    243
    Được thích
    196
    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"
    #include "Windows.h"
    
    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)]; //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
     
    Lần chỉnh sửa cuối:
    Cảm ơn bạn về bài viết này, bạn có thể giúp mình làm 1 video về việc tạo add-in vào excel từ C++ không, cảm ơn bạn.
     
    Cảm ơn bạn về bài viết này, bạn có thể giúp mình làm 1 video về việc tạo add-in vào excel từ C++ không, cảm ơn bạn.
    XLL add-in thực chất vẫn là Standard DLL, chỉ là DLL đó cần hỗ trợ một số hàm bắt buộc để Excel gọi và nhận diện add-in.
    Nếu bạn muốn lập trình XLL add-in bằng C/C++ thì có thể tham khảo cuốn sách này xem sao.
    1754736528882.png
     
    Bạn có thể gửi giúp mình file visual studio C++ theo bài post bên trên được không ah, mình có làm theo nhưng không được chạy được code. Cảm ơn bạn
    nguyentruongtho1711@gmail.com
     
    Lần chỉnh sửa cuối:
    Hàm bất đồng bộ (Asynchronous User-Defined Functions)
    Trong một số trường hợp, hàm có thể mất nhiều thời gian hơn để xử lý và trả kết quả (vd: Đợi thông tin từ internet hoặc khối lượng công việc tính toán lớn mất nhiều thời gian), cho nên khi hàm chạy đồng bộ, Excel phải chờ hàm trả về kết quả xong thì mới cho phép người dùng tương tác với giao diện người dùng (UI) của nó.
    Để giải quyết vấn đề này, Excel giới thiệu mô hình hàm bất đồng bộ, trong đó trình tự các bước thực thi như sau:
    • Đăng ký hàm là hàm bất đồng bộ, hàm này bắt buộc phải trả về kiểu void và phải có tham số chứa kiểu XLOPER hoặc XLOPER12 để Excel sử dụng làm điều khiển (handle) quản lý hàm bất đồng bộ. Tham khảo thêm: xlfRegister (Form 1)
    • Trong hàm, tiến hành khởi tạo thread để xử lý các công việc chính của hàm (mục đích chính của hàm bất đồng bộ), chuẩn bị các dữ liệu cần thiết cho việc tính toán (lưu ý: Các giá trị khi truyền vào thread bắt buộc phải dưới dạng sao chép sâu (deep copy) và được lưu trữ trên vùng nhớ heap bởi vì nếu chỉ lưu trữ trên vùng nhớ stack, khi hàm chính kết thúc thì dữ liệu trên vùng nhớ stack sẽ bị xóa và thu hồi, dẫn đến thread của hàm tham chiếu đến vùng nhớ không hợp lệ và khiến cho Excel bị sập (crash)).
    • Từ thread của hàm, tiến hành tính toán như bình thường, sau khi có kết quả thì dùng hàm gọi lại (callback) xlAsyncReturn để trả kết quả cho Excel.
    C++:
    void AsyncFuncCallback(LPXLOPER12 lpHandle) {
        //Giả lập công việc tính toán mất nhiều thời gian
        Sleep(3000);
        //Chuẩn bị kết quả để trả về cho Excel
        XLOPER12 result = {};
        result.xltype = xltypeNum;
        result.val.num = 10;
        //Trả kết quả về cho Excel
        Excel12f(xlAsyncReturn, NULL, 2, lpHandle, &result);
    }
    
    DWORD WINAPI AsyncFuncCallbackProc(LPVOID lpParam) {
        //Kiểm tra điều kiện đầu vào
        if (!lpParam) return 1;
        LPXLOPER12 pHandle = (LPXLOPER12)lpParam;
        //Gọi hàm tính toán
        AsyncFuncCallback(pHandle);
        return 0;
    }
    
    extern "C" __declspec(dllexport) void WINAPI AsyncFunc(LPXLOPER12 lpHandle) {
        //Kiểm tra điều kiện đầu vào
        if (!lpHandle) return;
        //Tạo thread để tính toán
        HANDLE hThread = CreateThread(NULL, 0, AsyncFuncCallbackProc, lpHandle, 0, NULL);
        if (!hThread) return;
    }
    
    extern "C" __declspec(dllexport) int WINAPI xlAutoOpen()
    {
        XLOPER12 xlDllName;
        //Xác định tên của DLL
        if (Excel12f(xlGetName, &xlDllName, 0) == xlretSuccess) {
            //Đăng ký hàm với Excel
            Excel12f(xlfRegister, NULL, 10, (LPXLOPER12)&xlDllName, //Đăng ký tên của DLL
                (LPXLOPER12)TempStr12(L"AsyncFunc"), //Tên hàm
                (LPXLOPER12)TempStr12(L">X"), //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"AsyncFunc"), //Tên hàm trong trình Function Wizard
                (LPXLOPER12)TempStr12(L""), //Mô tả tham số trong trình Function Wizard
                (LPXLOPER12)TempStr12(L"1"), //Kiểu macro
                (LPXLOPER12)TempStr12(L"Asynchronous Functions"), //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""));
            //Giải phóng bộ nhớ
            Excel12f(xlFree, NULL, 1, (LPXLOPER12)&xlDllName);
        }
        return 1;
    }

    Khi hàm chạy trên bảng tính, điều khiển được trả về ngay cho Excel giúp cho giao diện người dùng không bị đơ, trên ô bảng tính sẽ hiện lỗi #CALC! tức là hàm đang bận tính toán.

    1758287064865.png

    Hàm sau khi chạy xong sẽ trả kết quả về cho Excel.

    1758287137042.png
     
    Mới viết xong XLL tham gia chơi với cho Vui bản 64 bít

    1782809934579.png
     

    File đính kèm

    Bà tám với ChatGPT kết luận như sau

    1782870793970.png

    Thực tế Mạnh tự định nghĩa như sau

    XLL menu Ribbon.png

    Ms không hổ trợ là lý thuyết còn thực tế thì tư duy logis tí là xong
    1782872617839.png
     
    Lần chỉnh sửa cuối:
    Bài viết này chỉ quăng mỗi mã lên đây thì hơi nhàm chán, nếu có thêm người quan tâm phân tích những khía cạnh kỹ thuật liên quan thì sẽ hay hơn.
    P/s: Đang nói bài #1.
     

    Xin chào thế giới​

    Xem xét chữ ký của một hàm sau đây, với mục đích là trả về chuỗi “Hello, World” khi người dùng gõ hàm này vào bảng tính trên Excel:

    C++:
    extern "C" __declspec(dllexport) wchar_t* WINAPI PrintHelloWorld();

    Để Excel có thể nhận diện được hàm này, trước tiên cần phải đăng ký hàm với Excel đã. Để đăng ký hàm với Excel, người dùng cần gọi hàm xlfRegister, thông tin chi tiết về hàm này người dùng có thể tìm thấy ở đây: xlfRegister (Form 1) | Microsoft Learn

    1784205096282.png
    1784205104139.png

    Chú ý đến đối số pxTypeText, tham số cho Excel biết hàm được đăng ký có bao nhiêu tham số, kiểu giá trị trả về và kiểu giá trị của các tham số là gì, ở đây Excel yêu cầu hai loại chuỗi:
    • Null-terminated Unicode wide-character string, tức là chuỗi wchar_t thông thường kết thúc bằng ký tự rỗng (null).
    • Counted Unicode wide-character string, tức là chuỗi wchar_t giống như trên, nhưng khác biệt ở chỗ chuỗi có chứa thông tin về độ dài chuỗi, thông tin này được lưu trữ ở phần tử đầu tiên trong mảng chuỗi.
    Ở đây hàm PrintHelloWorld chọn trả về chuỗi wchar_t thông thường, cho nên ký tự đại diện sẽ là C%. Với việc hàm này không có đối số nên đối số pxTypeText cuối cùng sẽ là “C%”.

    Như vậy nội dung của hàm PrintHelloWorld có thể được viết như sau:

    C++:
    extern "C" __declspec(dllexport) wchar_t* WINAPI PrintHelloWorld() {
        const wchar_t szInput[] = L"Hello World";
        size_t len = wcslen(szInput);
        wchar_t* pwszResult = new wchar_t[len + 1];
        if (!pwszResult) return NULL;
        errno_t err = wcscpy_s(pwszResult, len + 1, szInput);
        if (err) {
            delete[] pwszResult;
            return NULL;
        }
        return pwszResult;
    }

    Thường thì người ta sẽ muốn đăng ký hàm ngay khi Excel nạp XLL, cho nên hàm xlAutoOpen sẽ là nơi lý tưởng nhất để làm việc này bởi vì Excel sẽ gọi hàm này khi vừa mới nạp XLL.

    Sau đây là mã đăng ký hàm:

    C++:
    extern "C" __declspec(dllexport) int WINAPI xlAutoOpen() {
        XLOPER12 xlDllName;
        int iResult = Excel12(xlGetName, &xlDllName, 0);
        if (iResult == xlretFailed) return 0;
        iResult = Excel12(xlfRegister, NULL, 11,
            &xlDllName,
            TempStr12(L"PrintHelloWorld"),
            TempStr12(L"C%"),
            TempStr12(L"PrintHelloWorld"),
            TempStr12(L""),
            TempStr12(L"1"),
            TempStr12(L"UDF"),
            TempStr12(L""),
            TempStr12(L""),
            TempStr12(L"PrintHelloWorld"),
            TempStr12(L"")
        );
        Excel12(xlFree, NULL, 1, &xlDllName);
        return iResult == xlretSuccess ? 1 : 0;
    }

    Hàm khi được sử dụng trên bảng tính:

    1784205174863.png

    Lưu ý: Do hàm trả trực tiếp con trỏ wchar_t* về cho Excel, cho nên sẽ xảy ra tình trạng rò rỉ bộ nhớ sau mỗi lần gọi. Excel sau khi sử dụng xong thì sẽ không giải phóng bộ nhớ mà con trỏ này trỏ đến là vì nó không thể nào biết được vùng nhớ được cấp phát bằng phương thức nào (malloc, new, GlobalAlloc, LocalAlloc, HeapAlloc, v.v...) để giải phóng bằng phương thức tương ứng. Cho nên thường thì người ta sẽ trả về giá trị cho Excel thông qua cấu trúc (struct) XLOPER12 và sử dụng hàm gọi lại xlAutoFree12 để giải phóng vùng nhớ mà cấu trúc này chứa con trỏ trỏ đến.
     

    File đính kèm

    Lần chỉnh sửa cuối:

    Khắc phục vấn đề còn tồn đọng: Rò rỉ bộ nhớ​

    Như đã đề cập ở bài trước, hàm PrintHelloWorld trả con trỏ wchar_t* về cho Excel, cho nên tình trạng rò rỉ bộ nhớ sẽ xảy ra mỗi khi Excel gọi đến hàm này. Để khắc phục tình trạng này, người dùng nên sử dụng cấu trúc (struct) XLOPER12 để gửi gắm giá trị cần trả về cho Excel, cấu trúc này gồm nhiều hợp (union) của các cấu trúc khác nhau.

    C++:
    /*
    ** XLOPER12 structure
    **
    ** Excel 12's fundamental data type: can hold data
    ** of any type. Use "U" as the argument type in the
    ** REGISTER function.
    **/
    
    typedef struct xloper12
    {
        union
        {
            double num;                           /* xltypeNum */
            XCHAR *str;                           /* xltypeStr */
            BOOL xbool;                           /* xltypeBool */
            int err;                           /* xltypeErr */
            int w;
            struct
            {
                WORD count;                       /* always = 1 */
                XLREF12 ref;
            } sref;                        /* xltypeSRef */
            struct
            {
                XLMREF12 *lpmref;
                IDSHEET idSheet;
            } mref;                        /* xltypeRef */
            struct
            {
                struct xloper12 *lparray;
                RW rows;
                COL columns;
            } array;                    /* xltypeMulti */
            struct
            {
                union
                {
                    int level;            /* xlflowRestart */
                    int tbctrl;            /* xlflowPause */
                    IDSHEET idSheet;        /* xlflowGoto */
                } valflow;
                RW rw;                           /* xlflowGoto */
                COL col;                       /* xlflowGoto */
                BYTE xlflow;
            } flow;                        /* xltypeFlow */
            struct
            {
                union
                {
                    BYTE *lpbData;            /* data passed to XL */
                    HANDLE hdata;            /* data returned from XL */
                } h;
                long cbData;
            } bigdata;                    /* xltypeBigData */
        } val;
        DWORD xltype;
    } XLOPER12, *LPXLOPER12;

    Như vậy để truyền con trỏ wchar_t* cho Excel thông qua XLOPER12, người dùng cần bật cờ bit xltypeStr cho trường xltype để thông báo cho Excel biết hàm muốn trả về giá trị kiểu chuỗi, rồi sau đó chỉ định con trỏ wchar_t* trong cấu trúc val.str, ví dụ:

    C++:
    static XLOPER12 xlRet;
    xlRet.xltype = xltypeStr;
    xlRet.val.str = pwszVal;

    Có một vấn đề thế này, bởi vì Excel đòi hỏi chuỗi được truyền thông qua XLOPER12 phải là chuỗi có chứa thông tin về độ dài chuỗi, cho nên người dùng không thể nào sử dụng chuỗi wchar_t thông thường được. Thay vào đó, người dùng sẽ phải bố trí cấu trúc của chuỗi đúng như những gì mà Excel yêu cầu: Phần tử đầu tiên trong mảng chứa thông tin về độ dài chuỗi, mà phần tử này có kích thước của kiểu wchar_t là 2 byte, cho nên giá trị dương tối đa mà nó có thể biểu diễn là 32768, phần còn lại của mảng là chuỗi như thường dùng. Do phải tốn một phần tử trong mảng để chứa thông tin về độ dài chuỗi, cho nên 32768 – 1 = 32767 độ dài chuỗi tối đa mà Excel có thể tiếp nhận.

    Như vậy có thể viết hàm chuyển đổi chuỗi theo yêu cầu Excel như sau:

    C++:
    #define MAX_XL12_STR_LEN 32767u
    
    wchar_t* WideCharToXl12Str(const wchar_t* lpwszInput) {
        size_t len;
        if (!lpwszInput || !(len = wcslen(lpwszInput))) return NULL;
        if (len > MAX_XL12_STR_LEN) len = MAX_XL12_STR_LEN;
        wchar_t* pwszResult = new wchar_t[len + 2];
        if (!pwszResult) return NULL;
        pwszResult[0] = (wchar_t)len;
        errno_t err = wcscpy_s(pwszResult + 1, len + 1, lpwszInput);
        if (err) {
            delete[] pwszResult;
            return NULL;
        }
        return pwszResult;
    }

    Sửa lại hàm PrintHelloWorld như sau:

    C++:
    extern "C" __declspec(dllexport) LPXLOPER12 WINAPI PrintHelloWorld() {
        const wchar_t szInput[] = L"Hello World";
        static XLOPER12 xlRet;
        xlRet.xltype = xltypeStr | xlbitDLLFree;
        xlRet.val.str = WideCharToXl12Str(szInput);
        if (!xlRet.val.str) return NULL;
        return &xlRet;
    }

    Sửa lại phần đăng ký hàm với Excel:

    C++:
    extern "C" __declspec(dllexport) int WINAPI xlAutoOpen() {
        XLOPER12 xlDllName;
        int iResult = Excel12(xlGetName, &xlDllName, 0);
        if (iResult == xlretFailed) return 0;
        iResult = Excel12(xlfRegister, NULL, 11,
            &xlDllName,
            TempStr12(L"PrintHelloWorld"),
            TempStr12(L"Q"),
            TempStr12(L"PrintHelloWorld"),
            TempStr12(L""),
            TempStr12(L"1"),
            TempStr12(L"UDF"),
            TempStr12(L""),
            TempStr12(L""),
            TempStr12(L"PrintHelloWorld"),
            TempStr12(L"")
        );
        Excel12(xlFree, NULL, 1, &xlDllName);
        return iResult == xlretSuccess ? 1 : 0;
    }

    Quan trọng: Để giải quyết vấn đề rò rỉ bộ nhớ, Excel cung cấp một hàm gọi lại tên là xlAutoFree12, hàm này có một tham số là con trỏ kiểu LPXLOPER12, đây chính là con trỏ mà người dùng đã trả về từ hàm (vd: PrintHelloWorld chẳng hạn). Hàm xlAutoFree12 được gọi khi và chỉ khi người dùng bật cờ bit xlbitDLLFree hoặc xlbitXLFree. Cờ bit xlbitDLLFree được sử dụng khi người dùng cấp phát và sử dụng vùng nhớ theo cách thông thường (như trong hàm PrintHelloWorld) chẳng hạn, còn cờ bit xlbitXLFree được sử dụng khi vùng nhớ đó được tạo ra nhờ kết quả của hàm Excel12 (vd: Trả về từ hàm xlGetName chẳng hạn).

    Nhờ hàm gọi lại xlAutoFree12 mà người dùng sẽ có cơ hội giải phóng bộ nhớ sau khi hàm được thực hiện xong, loại bỏ hoàn toàn vấn đề rò rỉ bộ nhớ.

    C++:
    extern "C" __declspec(dllexport) int WINAPI xlAutoFree12(LPXLOPER12 pOper) {
        if (!pOper) return 0;
        if (pOper->xltype & (xltypeStr | xlbitDLLFree) || pOper->val.str) {
            delete[] pOper->val.str;
        }
        return 0;
    }

    Kiểm tra xem Excel có gọi lại hàm xlAutoFree12 không sau khi thực hiện xong hàm PrintHelloWorld. Kết quả đúng như ý muốn.

    1784257739328.png

    Tạm thời cứ đơn giản như trên đã, sau này muốn tận dụng tính năng Multithreaded Recalculation của Excel, tức là Excel sẽ sử dụng nhiều thread khác nhau để tính toán lại hàm khi có yêu cầu cần tính toán lại trên bảng tính, thì hàm PrintHelloWorld ở trên bắt buộc phải đảm bảo an toàn thread, tuy nhiên khai báo static XLOPER12 thì không được rồi, cho nên sẽ cần phải làm thêm một số việc khác.
     
    Lần chỉnh sửa cuối:

    Khắc phục vấn đề còn tồn đọng: Rò rỉ bộ nhớ​

    Như đã đề cập ở bài trước, hàm PrintHelloWorld trả con trỏ wchar_t* về cho Excel, cho nên tình trạng rò rỉ bộ nhớ sẽ xảy ra mỗi khi Excel gọi đến hàm này. Để khắc phục tình trạng này, người dùng nên sử dụng cấu trúc (struct) XLOPER12 để gửi gắm giá trị cần trả về cho Excel, cấu trúc này gồm nhiều hợp (union) của các cấu trúc khác nhau.

    C++:
    /*
    ** XLOPER12 structure
    **
    ** Excel 12's fundamental data type: can hold data
    ** of any type. Use "U" as the argument type in the
    ** REGISTER function.
    **/
    
    typedef struct xloper12
    {
        union
        {
            double num;                           /* xltypeNum */
            XCHAR *str;                           /* xltypeStr */
            BOOL xbool;                           /* xltypeBool */
            int err;                           /* xltypeErr */
            int w;
            struct
            {
                WORD count;                       /* always = 1 */
                XLREF12 ref;
            } sref;                        /* xltypeSRef */
            struct
            {
                XLMREF12 *lpmref;
                IDSHEET idSheet;
            } mref;                        /* xltypeRef */
            struct
            {
                struct xloper12 *lparray;
                RW rows;
                COL columns;
            } array;                    /* xltypeMulti */
            struct
            {
                union
                {
                    int level;            /* xlflowRestart */
                    int tbctrl;            /* xlflowPause */
                    IDSHEET idSheet;        /* xlflowGoto */
                } valflow;
                RW rw;                           /* xlflowGoto */
                COL col;                       /* xlflowGoto */
                BYTE xlflow;
            } flow;                        /* xltypeFlow */
            struct
            {
                union
                {
                    BYTE *lpbData;            /* data passed to XL */
                    HANDLE hdata;            /* data returned from XL */
                } h;
                long cbData;
            } bigdata;                    /* xltypeBigData */
        } val;
        DWORD xltype;
    } XLOPER12, *LPXLOPER12;

    Như vậy để truyền con trỏ wchar_t* cho Excel thông qua XLOPER12, người dùng cần bật cờ bit xltypeStr cho trường xltype để thông báo cho Excel biết hàm muốn trả về giá trị kiểu chuỗi, rồi sau đó chỉ định con trỏ wchar_t* trong cấu trúc val.str, ví dụ:

    C++:
    static XLOPER12 xlRet;
    xlRet.xltype = xltypeStr;
    xlRet.val.str = pwszVal;

    Có một vấn đề thế này, bởi vì Excel đòi hỏi chuỗi được truyền thông qua XLOPER12 phải là chuỗi có chứa thông tin về độ dài chuỗi, cho nên người dùng không thể nào sử dụng chuỗi wchar_t thông thường được. Thay vào đó, người dùng sẽ phải bố trí cấu trúc của chuỗi đúng như những gì mà Excel yêu cầu: Phần tử đầu tiên trong mảng chứa thông tin về độ dài chuỗi, mà phần tử này có kích thước của kiểu wchar_t là 2 byte, cho nên giá trị dương tối đa mà nó có thể biểu diễn là 32768, phần còn lại của mảng là chuỗi như thường dùng. Do phải tốn một phần tử trong mảng để chứa thông tin về độ dài chuỗi, cho nên 32768 – 1 = 32767 độ dài chuỗi tối đa mà Excel có thể tiếp nhận.

    Như vậy có thể viết hàm chuyển đổi chuỗi theo yêu cầu Excel như sau:

    C++:
    #define MAX_XL12_STR_LEN 32767u
    
    wchar_t* WideCharToXl12Str(const wchar_t* lpwszInput) {
        size_t len;
        if (!lpwszInput || !(len = wcslen(lpwszInput))) return NULL;
        if (len > MAX_XL12_STR_LEN) len = MAX_XL12_STR_LEN;
        wchar_t* pwszResult = new wchar_t[len + 2];
        if (!pwszResult) return NULL;
        pwszResult[0] = (wchar_t)len;
        errno_t err = wcscpy_s(pwszResult + 1, len + 1, lpwszInput);
        if (err) {
            delete[] pwszResult;
            return NULL;
        }
        return pwszResult;
    }

    Sửa lại hàm PrintHelloWorld như sau:

    C++:
    extern "C" __declspec(dllexport) LPXLOPER12 WINAPI PrintHelloWorld() {
        const wchar_t szInput[] = L"Hello World";
        static XLOPER12 xlRet;
        xlRet.xltype = xltypeStr | xlbitDLLFree;
        xlRet.val.str = WideCharToXl12Str(szInput);
        if (!xlRet.val.str) return NULL;
        return &xlRet;
    }

    Sửa lại phần đăng ký hàm với Excel:

    C++:
    extern "C" __declspec(dllexport) int WINAPI xlAutoOpen() {
        XLOPER12 xlDllName;
        int iResult = Excel12(xlGetName, &xlDllName, 0);
        if (iResult == xlretFailed) return 0;
        iResult = Excel12(xlfRegister, NULL, 11,
            &xlDllName,
            TempStr12(L"PrintHelloWorld"),
            TempStr12(L"Q"),
            TempStr12(L"PrintHelloWorld"),
            TempStr12(L""),
            TempStr12(L"1"),
            TempStr12(L"UDF"),
            TempStr12(L""),
            TempStr12(L""),
            TempStr12(L"PrintHelloWorld"),
            TempStr12(L"")
        );
        Excel12(xlFree, NULL, 1, &xlDllName);
        return iResult == xlretSuccess ? 1 : 0;
    }

    Quan trọng: Để giải quyết vấn đề rò rỉ bộ nhớ, Excel cung cấp một hàm gọi lại tên là xlAutoFree12, hàm này có một tham số là con trỏ kiểu LPXLOPER12, đây chính là con trỏ mà người dùng đã trả về từ hàm (vd: PrintHelloWorld chẳng hạn). Hàm xlAutoFree12 được gọi khi và chỉ khi người dùng bật cờ bit xlbitDLLFree hoặc xlbitXLFree. Cờ bit xlbitDLLFree được sử dụng khi người dùng cấp phát và sử dụng vùng nhớ theo cách thông thường (như trong hàm PrintHelloWorld) chẳng hạn, còn cờ bit xlbitXLFree được sử dụng khi vùng nhớ đó được tạo ra nhờ kết quả của hàm Excel12 (vd: Trả về từ hàm xlGetName chẳng hạn).

    Nhờ hàm gọi lại xlAutoFree12 mà người dùng sẽ có cơ hội giải phóng bộ nhớ sau khi hàm được thực hiện xong, loại bỏ hoàn toàn vấn đề rò rỉ bộ nhớ.

    C++:
    extern "C" __declspec(dllexport) int WINAPI xlAutoFree12(LPXLOPER12 pOper) {
        if (!pOper) return 0;
        if (pOper->xltype & (xltypeStr | xlbitDLLFree) || pOper->val.str) {
            delete[] pOper->val.str;
        }
        return 0;
    }

    Kiểm tra xem Excel có gọi lại hàm xlAutoFree12 không sau khi thực hiện xong hàm PrintHelloWorld. Kết quả đúng như ý muốn.

    View attachment 312075

    Tạm thời cứ đơn giản như trên đã, sau này muốn tận dụng tính năng Multithreaded Recalculation của Excel, tức là Excel sẽ sử dụng nhiều thread khác nhau để tính toán lại hàm khi có yêu cầu cần tính toán lại trên bảng tính, thì hàm PrintHelloWorld ở trên bắt buộc phải đảm bảo an toàn thread, tuy nhiên khai báo static XLOPER12 thì không được rồi, cho nên sẽ cần phải làm thêm một số việc khác.
    mới viết có một hàm PrintHelloWorld thì dòm cái hàm xlAutoOpen đã dài cả mét rồi vậy nếu có 50 hàm với các tham số động thì điều gì xẩy ra

    nó sẽ lên cấp số nhân hay số cộng vậy tạm tính dài lên 50 mét quá ... xong quản lý kiểu gì ???!!!

    đừng buồn từ đó tư duy chút có phương pháp viết thôi ... không lẽ cứ mãi PrintHelloWorld hay Chào thế giới
     
    mới viết có một hàm PrintHelloWorld thì dòm cái hàm xlAutoOpen đã dài cả mét rồi vậy nếu có 50 hàm với các tham số động thì điều gì xẩy ra

    nó sẽ lên cấp số nhân hay số cộng vậy tạm tính dài lên 50 mét quá ... xong quản lý kiểu gì ???!!!

    đừng buồn từ đó tư duy chút có phương pháp viết thôi ... không lẽ cứ mãi PrintHelloWorld hay Chào thế giới
    Buồn cái gì, demo ngắn gọn thì viết thế cho người mới tìm hiểu mới dễ lĩnh ngộ được, chứ nếu muốn ngắn gọn thì viết thêm vài hàm gói gọn tham số là xong, có gì khó.
    Chứ như bên ông dùng Delphi có sẵn thư viện người ta viễn sẵn cho thì nói làm gì.
    Bắt đầu tìm hiểu cái mới thì phải biết viết Hello World, chứ tay mơ đòi nhảy vào phải viết được cái này phải viết được cái kia thì có mà tới mùa quít à.
     
    Buồn cái gì, demo ngắn gọn thì viết thế cho người mới tìm hiểu mới dễ lĩnh ngộ được, chứ nếu muốn ngắn gọn thì viết thêm vài hàm gói gọn tham số là xong, có gì khó.
    Chứ như bên ông dùng Delphi có sẵn thư viện người ta viễn sẵn cho thì nói làm gì.
    Bắt đầu tìm hiểu cái mới thì phải biết viết Hello World, chứ tay mơ đòi nhảy vào phải viết được cái này phải viết được cái kia thì có mà tới mùa quít à.
    Delphi viết cho Excel nói chung XLL hay TaskPane làm gì có hàm nào "Delphi có sẵn thư viện người ta viễn sẵn" mà mọi cái định định nghĩa lấy mà dùng

    nếu nói CSDL như FireDAC họ viết cho SQLite với các hàm bao bọc wrapper thì còn nghe được ... quên chuyện ăn sẵn đi
     
    Delphi viết cho Excel nói chung XLL hay TaskPane làm gì có hàm nào "Delphi có sẵn thư viện người ta viễn sẵn" mà mọi cái định định nghĩa lấy mà dùng

    nếu nói CSDL như FireDAC họ viết cho SQLite với các hàm bao bọc wrapper thì còn nghe được ... quên chuyện ăn sẵn đi
    Rồi ông có dám khẳng định cái XLL ông viết có thông qua một lớp thư viện gói gọn nào không. Excel vốn dĩ ban đầu viết bằng C, nếu không nhờ viết lại mã dựa trên những tệp header .h mà Microsoft cung cấp, nếu không nhờ Delphi ngầm thực hiện việc chuyển đổi kiểu dữ liệu qua lại giữa Delphi với C thì có mà chạy được đấy.
    Hình như tôi thấy ông hơi lạc đề rồi, bài viết này tập trung vào những khía cạnh viết XLL bằng C++ chứ không phải để khoe xem viết bằng Delphi hay C++ thì ngắn hơn, đẳng cấp hơn.
     
    Rồi ông có dám khẳng định cái XLL ông viết có thông qua một lớp thư viện gói gọn nào không. Excel vốn dĩ ban đầu viết bằng C, nếu không nhờ viết lại mã dựa trên những tệp header .h mà Microsoft cung cấp, nếu không nhờ Delphi ngầm thực hiện việc chuyển đổi kiểu dữ liệu qua lại giữa Delphi với C thì có mà chạy được đấy.
    Hình như tôi thấy ông hơi lạc đề rồi, bài viết này tập trung vào những khía cạnh viết XLL bằng C++ chứ không phải để khoe xem viết bằng Delphi hay C++ thì ngắn hơn, đẳng cấp hơn.
    Không lạc đâu còn ai viết trên bất cứ ngôn ngữ nào cũng dựa vào header.h là c++ của Ms xong XLL ... còn chuyện ăn sãn chỉ mấy chú tái sử dụng ví dụ

    như : Excel-DNA hay ADD-IN EXPRESS™ hay bên thứ ba nào đó xong viết XLL thì mới gọi là ăn sẵn

    Rảnh tôi sẽ viết c++ builder cũng thế thôi ... Tôi dừng ở đây không bàn luận nữa vì không cần thiết
     
    Không lạc đâu còn ai viết trên bất cứ ngôn ngữ nào cũng dựa vào header.h là c++ của Ms xong XLL ... còn chuyện ăn sãn chỉ mấy chú tái sử dụng ví dụ

    như : Excel-DNA hay ADD-IN EXPRESS™ hay bên thứ ba nào đó xong viết XLL thì mới gọi là ăn sẵn

    Rảnh tôi sẽ viết c++ builder cũng thế thôi ... Tôi dừng ở đây không bàn luận nữa vì không cần thiết
    Gửi ông mã viết bằng CodeBlocks sử dụng trình biên dịch GCC để tham khảo.

    1784298002836.png

    Nói chung là cú pháp thì vẫn thế thôi, chỉ là khi làm việc bằng IDE khác nhau và trình biên dịch khác nhau thì sẽ có những cách làm việc khác nhau.
     

    File đính kèm

    Gửi ông mã viết bằng CodeBlocks sử dụng trình biên dịch GCC để tham khảo.

    View attachment 312089

    Nói chung là cú pháp thì vẫn thế thôi, chỉ là khi làm việc bằng IDE khác nhau và trình biên dịch khác nhau thì sẽ có những cách làm việc khác nhau.
    Gửi lại ông mã C++ builder tham khảo nè ... cơ bản vậy còn tuỳ biến và triển khai tuỳ ai đó sử dụng và viết

    Tôi loại bỏ sạch Excel4 vì đồ cổ cho Excel 2003 không cần thiết mà sử dụng Excel12 cho sạch và tốt cho viết sau này

    Tôi tải lại File của Ông xong cùng AI viết lại trong 15 phút xong .. chỉ dùng các file cần dùng còn lại xoá hết cho sạch
     

    File đính kèm

    trước khi chưa có AI sợ nhất dịch từ XLCALL.H sang XLCall.pas ... giờ thì cũng quá đơn giản rồi Nhờ AI dịch là ra thôi không còn gì là mật với mỡ nữa

    Mà cái quan trọng là có nó rồi thì ai đó ứng dụng viết được cái gì mới quan trọng ...

    xong tự viết lấy mà dùng chứ không có chuyện ăn sẵn như như ai đó ăn sẵn Excel-DNA , ADD-IN EXPRESS™ hay bên thứ ba nào đó xong viết XLL

    Mã sau do AI dịch ... quá dài nên tôi chỉ cung cấp một phần

    Mã:
    ///***************************************************************************
    // File:        XLCall.pas
    //
    // Muc dich:    Ban dich Pascal (Delphi) day du cua XLCALL.H - header C
    //              cua Microsoft Excel Developer's Toolkit (dinh nghia kieu
    //              du lieu va hang so dung trong Excel C API / XLL).
    //
    // Ghi chu quan trong:
    //              - Day la ban dich 1-1 toan bo hang so (~1094 gia tri) va
    //                cau truc du lieu tu XLCALL.H goc, de dung lau dai cho
    //                nhieu du an XLL viet bang Delphi.
    //              - Khac voi C (header .h rieng, implementation .cpp rieng),
    //                mot unit Delphi gop ca hai phan (interface = header,
    //                implementation = file .cpp) trong CUNG MOT FILE. Do la
    //                quy uoc chuan cua Delphi, khong phai thieu sot.
    //              - Cac ham Excel4()/Excel4v()/XLCallVer()/LPenHelper() la
    //                API Excel4 kieu cu (truoc Excel 2007), KHONG duoc cai
    //                dat trong unit nay vi ban XLCALL.CPP goc cua du an cung
    //                khong co cai dat cho chung (da bi loai bo khi vi ban
    //                C++ Builder chuyen sang chi dung Excel12/XLOPER12). Neu
    //                thuc su can dung API Excel4 cu, ban se can bo sung rieng.
    //              - Excel12() va Excel12v() duoc cai dat day du, dich chinh
    //                xac tu XLCALL.CPP, dung co che GetProcAddress giong het
    //                ban C++.
    //
    // Nen tang:    Microsoft Windows (Win32/Win64)
    //***************************************************************************
    
    unit XLCall;
    
    interface
    
    uses
      Windows;
    
    // -------------------------------------------------------------------------
    // Kieu du lieu co ban XL12 (XL 12 Basic Datatypes)
    // -------------------------------------------------------------------------
    // Ghi chu: khong dinh nghia lai BOOL o day - don vi Windows cua Delphi da
    // co san BOOL = LongBool (Longint 4 byte, 0 = False, khac 0 = True), hoan
    // toan tuong thich voi "typedef INT32 BOOL" trong C.
    type
      XCHAR  = WideChar;              // Wide Character (WCHAR trong C)
      PXCHAR = PWideChar;
      RW     = Integer;               // XL 12 Row  (INT32)
      COL    = Integer;               // XL 12 Column (INT32)
      IDSHEET = NativeUInt;           // XL12 Sheet ID (DWORD_PTR)
    
      // -----------------------------------------------------------------------
      // XLREF - Mo ta MOT tham chieu hinh chu nhat (kieu cu, truoc Excel 2007)
      // -----------------------------------------------------------------------
      PXLREF = ^TXLREF;
      LPXLREF = PXLREF;
      TXLREF = record
        rwFirst:  Word;
        rwLast:   Word;
        colFirst: Byte;
        colLast:  Byte;
      end;
    
      // -----------------------------------------------------------------------
      // XLMREF - Mo ta NHIEU tham chieu hinh chu nhat (kieu cu)
      // Day la cau truc kich thuoc thay doi: reftbl thuc te co "count" phan
      // tu, khong phai 1. Khi cap phat dong, cap phat them (count-1) phan tu
      // XLREF sau cau truc nay.
      // -----------------------------------------------------------------------
      PXLMREF = ^TXLMREF;
      LPXLMREF = PXLMREF;
      TXLMREF = record
        count:  Word;
        reftbl: array[0..0] of TXLREF; // thuc te la reftbl[count]
      end;
    
      // -----------------------------------------------------------------------
      // XLREF12 - Mo ta MOT tham chieu hinh chu nhat (Excel 2007 tro len)
      // -----------------------------------------------------------------------
      PXLREF12 = ^TXLREF12;
      LPXLREF12 = PXLREF12;
      TXLREF12 = record
        rwFirst:  RW;
        rwLast:   RW;
        colFirst: COL;
        colLast:  COL;
      end;
    
      // -----------------------------------------------------------------------
      // XLMREF12 - Mo ta NHIEU tham chieu hinh chu nhat (Excel 2007 tro len)
      // Cung la cau truc kich thuoc thay doi, xem ghi chu cua XLMREF o tren.
      // -----------------------------------------------------------------------
      PXLMREF12 = ^TXLMREF12;
      LPXLMREF12 = PXLMREF12;
      TXLMREF12 = record
        count:  Word;
        reftbl: array[0..0] of TXLREF12; // thuc te la reftbl[count]
      end;
    
      // -----------------------------------------------------------------------
      // FP / FP12 - Mang so thuc 2 chieu (kieu du lieu "P" trong REGISTER)
      // Truong "array" trong C doi ten thanh "arr" vi "array" la tu khoa cua
      // Pascal. Kich thuoc thuc te la rows * columns phan tu.
      // -----------------------------------------------------------------------
      PFP = ^TFP;
      TFP = record
        rows:    Word;
        columns: Word;
        arr:     array[0..0] of Double; // thuc te la array[rows][columns]
      end;
    
      PFP12 = ^TFP12;
      TFP12 = record
        rows:    Integer;
        columns: Integer;
        arr:     array[0..0] of Double; // thuc te la array[rows][columns]
      end;
    
      // =========================================================================
      // XLOPER (kieu cu, truoc Excel 2007) - "R" trong REGISTER
      // =========================================================================
      // Cau truc C goc:
      //   struct xloper { union { ... } val; WORD xltype; };
      //
      // Trong Pascal khong the dat mot truong binh thuong SAU phan variant
      // (case...of), nen ta tach union "val" thanh mot kieu record rieng
      // (TXLOperVal), roi dat no truoc truong xltype trong TXLOPER - dung
      // thu tu bo nho nhu ban C.
      PXLOPER = ^TXLOPER;
      LPXLOPER = PXLOPER;
    
      TXLOperSRef = record
        count: Word;      // luon = 1
        ref:   TXLREF;
      end;
    
      TXLOperMRef = record
        lpmref:  LPXLMREF;
        idSheet: IDSHEET;
      end;
    
      TXLOperArray = record
        lparray: LPXLOPER;
        rows:    Word;
        columns: Word;
      end;
    
      TXLOperFlowValflow = record
        case Integer of
          0: (level:   SmallInt);  // xlflowRestart
          1: (tbctrl:  SmallInt);  // xlflowPause
          2: (idSheet: IDSHEET);   // xlflowGoto
      end;
    
      TXLOperFlow = record
        valflow: TXLOperFlowValflow;
        rw:      Word;   // xlflowGoto
        col:     Byte;    // xlflowGoto
        xlflow:  Byte;
      end;
    
      TXLOperBigDataH = record
        case Integer of
          0: (lpbData: PByte);   // du lieu truyen vao XL
          1: (hdata:   THandle); // du lieu XL tra ve
      end;
    
      TXLOperBigData = record
        h:      TXLOperBigDataH;
        cbData: LongInt;
      end;
    
      TXLOperVal = record
        case Integer of
          0: (num:   Double);          // xltypeNum
          1: (str:   PAnsiChar);       // xltypeStr (chuoi ANSI kieu Pascal-string, byte dau la do dai)
          2: (xbool: Word);            // xltypeBool
          3: (err:   Word);            // xltypeErr
          4: (w:     SmallInt);        // xltypeInt
          5: (sref:  TXLOperSRef);     // xltypeSRef
          6: (mref:  TXLOperMRef);     // xltypeRef
          7: (arr:   TXLOperArray);    // xltypeMulti
          8: (flow:  TXLOperFlow);     // xltypeFlow
          9: (bigdata: TXLOperBigData); // xltypeBigData
      end;
    
      TXLOPER = record
        val:    TXLOperVal;
        xltype: Word;
      end;
    
      // =========================================================================
      // XLOPER12 (Excel 2007 tro len) - "U" trong REGISTER
      // =========================================================================
      // Day la kieu du lieu chinh dung trong toan bo cac ham Excel12 cua du an.
      PXLOPER12 = ^TXLOPER12;
      LPXLOPER12 = PXLOPER12;
    
      TXLOper12SRef = record
        count: Word;       // luon = 1
        ref:   TXLREF12;
      end;
    
      TXLOper12MRef = record
        lpmref:  LPXLMREF12;
        idSheet: IDSHEET;
      end;
    
      TXLOper12Array = record
        lparray: LPXLOPER12;
        rows:    RW;
        columns: COL;
      end;
    
      TXLOper12FlowValflow = record
        case Integer of
          0: (level:   Integer);  // xlflowRestart
          1: (tbctrl:  Integer);  // xlflowPause
          2: (idSheet: IDSHEET);  // xlflowGoto
      end;
    
      TXLOper12Flow = record
        valflow: TXLOper12FlowValflow;
        rw:      RW;    // xlflowGoto
        col:     COL;   // xlflowGoto
        xlflow:  Byte;
      end;
    
      TXLOper12BigDataH = record
        case Integer of
          0: (lpbData: PByte);   // du lieu truyen vao XL
          1: (hdata:   THandle); // du lieu XL tra ve
      end;
    
      TXLOper12BigData = record
        h:      TXLOper12BigDataH;
        cbData: LongInt;
      end;
    
      TXLOper12Val = record
        case Integer of
          0: (num:     Double);         // xltypeNum
          1: (str:     PWideChar);      // xltypeStr (chuoi Unicode kieu Pascal-string, ky tu dau la do dai)
          2: (xbool:   BOOL);           // xltypeBool
          3: (err:     Integer);        // xltypeErr
          4: (w:       Integer);        // xltypeInt
          5: (sref:    TXLOper12SRef);  // xltypeSRef
          6: (mref:    TXLOper12MRef);  // xltypeRef
          7: (arr:     TXLOper12Array); // xltypeMulti
          8: (flow:    TXLOper12Flow);  // xltypeFlow
          9: (bigdata: TXLOper12BigData); // xltypeBigData
      end;
    
      TXLOPER12 = record
        val:    TXLOper12Val;
        xltype: DWORD;
      end;
    
      // -----------------------------------------------------------------------
      // FMLAINFO / MOUSEINFO - dung voi LPenHelper (API cu, xem ghi chu cuoi
      // file). Dich day du de tuong thich kieu du lieu, du hien khong co ham
      // nao trong unit nay su dung truc tiep.
      // -----------------------------------------------------------------------
      PFmlaInfo = ^TFmlaInfo;
      TFmlaInfo = record
        wPointMode: Integer;  // che do soan thao hien tai. 0 => phan con lai chua xac dinh
        cch:        Integer;  // so ky tu trong cong thuc
        lpch:       PAnsiChar; // con tro toi ky tu cong thuc. CHI DOC!!!
        ichFirst:   Integer;  // vi tri bat dau vung chon
        ichLast:    Integer;  // vi tri ket thuc vung chon (co the > cch)
        ichCaret:   Integer;  // vi tri con tro nhap nhay
      end;
    
      PMouseInfo = ^TMouseInfo;
      TMouseInfo = record
        // phan nhap
        hwnd: HWND;   // cua so can lay thong tin
        pt:   TPoint; // vi tri chuot can lay thong tin
        // phan xuat
        dt:  Integer; // loai tai lieu (document/page)
        ht:  Integer; // ma hit-test
        rw:  Integer; // dong tai vi tri chuot (-1 neu khong xac dinh)
        col: Integer; // cot tai vi tri chuot (-1 neu khong xac dinh)
      end;
    
      // -----------------------------------------------------------------------
      // Con tro ham callback bat dong bo cho Cluster Connector (HPC)
      // -----------------------------------------------------------------------
      TXlHpcAsyncCallback = function(operAsyncHandle: LPXLOPER12;
                                      operReturn: LPXLOPER12): Integer; stdcall;
    
    const
     
    Gửi lại ông mã C++ builder tham khảo nè ... cơ bản vậy còn tuỳ biến và triển khai tuỳ ai đó sử dụng và viết

    Tôi loại bỏ sạch Excel4 vì đồ cổ cho Excel 2003 không cần thiết mà sử dụng Excel12 cho sạch và tốt cho viết sau này

    Tôi tải lại File của Ông xong cùng AI viết lại trong 15 phút xong .. chỉ dùng các file cần dùng còn lại xoá hết cho sạch
    Như vầy là ổn rồi, là demo nên tạm thời cứ như thế đã, sau này khi bài toán phức tạp hơn thì gánh nặng công việc cũng sẽ tăng lên, về quản lý bộ nhớ, về việc đảm bảo an toàn thread cho hàm để Excel có thể sử dụng cho Multithreaded Recalculation, v.v...
     
    Như vầy là ổn rồi, là demo nên tạm thời cứ như thế đã, sau này khi bài toán phức tạp hơn thì gánh nặng công việc cũng sẽ tăng lên, về quản lý bộ nhớ, về việc đảm bảo an toàn thread cho hàm để Excel có thể sử dụng cho Multithreaded Recalculation, v.v...
    Viết C++ còn dễ hơn viết trên Delphi ... ông biết tại sao không riêng file XLCALL.H chuẩn của Ms chỉ copy vào là sử dụng với các thư viện c++ xử lý chuỗi tốt hơn trên Delphi cho nên tôi hay nói đâu đó viết c++ còn dễ hơn cả viết mã trên VBA là vậy vì nếu cũng như vậy VBA mãi mãi không bao giờ viết được còn C++ viết quá đơn giản

    tới hôm nay tôi biết nhiều ngôn ngữ thì cái nào Delphi tốt thì viết còn cái nào C++ Builder tốt thì viết và Go nữa nên quá OK xử lý mọi trường hợp

    Như hôm tôi viết ABI trên Delphi mãi không xong toàn lỗi và Crash khi chuyển qua thừ C++ thì tốt hơn hay hơn và xử lý nhiều vấn đề khác nữa
     

    Tận dụng tính năng Multithreaded Recalculation của Excel để cải thiện hiệu năng khi tính toán lại bảng tính​

    Tiếp nối bài #11, hàm PrintHelloWorld đã sẵn sàng và có thể sử dụng trên bảng tính của Excel. Nhưng mà có một nhược điểm thế này, giả sử người dùng sử dụng hàm từ A1 đến A9, sau đó vì một nhu cầu nào đó mà muốn tính toán lại (ví dụ như mở lại sổ làm việc hoặc khi người dùng nhấn phím F9 hoặc tổ hợp phím liên quan đến phím F9, v.v…) thì Excel sẽ thực hiện lần lượt như sau:
    • Excel tính toán công thức ở ô A1.
    • Tính toán xong ô A1 thì chuyển sang ô A2.
    • Xong ô A2 thì chuyển sang ô A3.
    • Thứ tự thực hiện cứ thế lập đi lập lại cho đến khi ô A9 được tính toán xong.
    Cũng may là hàm PrintHelloWorld khá đơn giản (chỉ trả về “Hello, World”) là xong nên mọi thứ diễn ra rất nhanh. Nhưng nếu thay hàm PrintHelloWorld bằng một hàm tính toán nặng và mất nhiều thời gian nào đó thì thời gian để Excel hoàn thành việc tính toán trên toàn bộ bảng tính sẽ diễn ra rất lâu mới xong. Cũng may là từ phiên bản Excel 2007, Microsoft bổ sung một tính năng tên là Multithreaded Recalculation (tạm dịch là tính toán lại công thức nhờ sử dụng đa luồng), tính năng này cho phép mỗi công việc tính toán lại các công thức trên bảng tính được thực hiện trên từng thread riêng biệt (lên đến 1024 thread), nhờ đó mà mỗi công thức trên bảng tính đều được thực hiện song song (như ở trên thì các công thức trong ô từ A1 đến A9 sẽ được thực hiện một cách song song) và giúp cải thiện đáng kể thời gian chờ để tính toán xong.

    Dĩ nhiên là động đến thread thì sẽ phát sinh nhiều vấn đề phức tạp, mà cái đau đầu nhất vẫn là làm thế nào để điều khiển các thread một cách hài hòa khi các thread đó sử dụng chung một vùng nhớ. Excel cũng không ngoại lệ, để một hàm được xem là an toàn thread để tính năng Multithreard Recalculation có thể phát huy được sức mạnh của mình, hàm đó phải đáp ứng những tiêu chí dưới đây (chi tiết xem bài viết này: Multithreaded recalculation in Excel | Microsoft Learn):

    1784347520102.png

    Quay trở lại hàm PrintHelloWorld, khi xét những quy tắc trên thì có thể thấy hàm đã vi phạm quy tắc số 4, cụ thể là hàm đã sử dụng từ khóa static khi khai báo biến sử dụng XLOPER12. Để tìm hiểu nguyên nhân vì sao việc sử dụng biến static khiến cho hàm không an toàn thread, chúng ta hãy xem một ví dụ dưới đây.

    Giả sử chúng ta có một hàm Foo đã đăng ký an toàn thread với Excel như sau:

    C++:
    extern "C" __declspec(dllexport) LPXLOPER12 WINAPI Foo(double a, double b) {
        static XLOPER12 xlRet;
        double dblRet = 0;
        //Tính toán cái gì đấy rồi trả về giá trị cho biến dblRet
    
        xlRet.xltype = xltypeNum;
        xlRet.val.num = dblRet;
        //Làm một số công việc nào đó, vd: dọn dẹp chẳng hạn
        return &xlRet;
    }

    Giả sử khi tính toán lại bảng tính, Excel sinh ra các thread tương ứng để tính toán những công thức nào có chứa hàm Foo. Vấn đề bắt đầu phát sinh từ việc biến xlRet được khai báo bằng từ khóa xlRet, nghĩa là vùng nhớ mà xlRet trỏ đến sẽ là vùng nhớ dùng chung của DLL cho bất cứ thread nào gọi đến các hàm trong DLL và vùng nhớ này sẽ luôn khả dụng kể cả khi đã thoát khỏi hàm Foo. Sự nghiêm trọng ở đây là do là vùng nhớ dùng chung nên các thread có thể tranh nhau ghi vào theo một thứ tự không thể nào đoán được. Lấy ví dụ:

    Giả sử Excel sinh ra hai thread để tính toán lại những công thức có sử dụng hàm Foo, tùy thuộc vào tham số truyền vào mà khiến cho khối lượng tính toán hàm có thể nặng hoặc nhẹ, từ đó thời gian hoàn thành công việc của các thread có thể khác nhau sau mỗi lần chạy:
    • Thread 1 và 2 bắt đầu tính toán hàm Foo.
    • Thread 1 tính toán xong và truyền kết quả 3 vào biến xlRet (xlRet.val.num = dblRet, sắp sửa đến dòng return &xlRet;.
    • Nhưng vì một lý do nào đó mà thread 2 đã đến xlRet.val.num = dblRet; trước, truyền kết quả 2 vào rồi đi xuống dòng return &xlRet và kết thúc hàm.
    • Thread 1 lúc này mới đi qua dòng return &xlRet và kết thúc hàm.
    Vì xlRet là biến dùng chung giữa các thread, cho nên hậu quả là kết quả tính toán của thread 1 đã bị ghi đè bởi kết quả tính toán của thread 2, rõ ràng là kết quả sai lệch, bởi vậy hàm Foo được xem là hàm không an toàn thread. Để giải quyết tình trạng này, chúng ta có hai cách:
    • Khai báo biến xlRet trên vùng nhớ heap. Hoặc,
    • Sử dụng Thread-Local Storage API của Microsoft.
    Hai cách trên đều đảm bảo mỗi thread khi gọi hàm Foo đều sẽ được cấp một vùng nhớ hoàn toàn riêng biệt, không chung chạ với ai, đảm bảo hàm được an toàn thread. Bài viết tiếp theo sẽ trình bày cách thức thực hiện hai phương pháp trên.
     
    Lần chỉnh sửa cuối:

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

    Back
    Top Bottom