Một ví dụ về XLL add-in: Tương tác với Google Gemini dưới dạng công thức bảng tính (3 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 hoạt động
Tham gia
25/5/22
Bài viết
112
Được thích
101
Tiếp nối bài viết Thử lập trình XLL add-in bằng C/C++, nay tôi xin giới thiệu một ví dụ về việc sử dụng XLL add-in để tương tác với Google Gemini dưới dạng công thức bảng tính.
Google Gemini là một chat bot do Google phát triển nhằm chạy đua với ChatGPT của OpenAI, Microsoft Copilot của Microsoft và nhiều hãng công nghệ khác. Khởi đầu với cái tên Google Bard, nay nó đã phát triển vượt bậc với cái tên Gemini và tỏ ra không hề kém cạnh so với các sản phẩm chat bot đình đám hiện nay.
Tương tự như nhiều sản phẩm khác, Google cho phép tích hợp chat bot này vào các ứng dụng khác nhau dưới dạng API, dưới dạng gói dịch vụ miễn phí và trả phí. Để biết thêm thông tin chi tiết, người dùng có thể tham khảo bài viết này: Google AI Studio quickstart.
Sau khi đã tạo tài khoản và/hoặc đăng nhập, người dùng có thể lấy khóa API tại đây: Quickly test the Gemini API
Dưới đây là toàn bộ code về XLL add-in trong bài viết này, mấu chốt nằm ở class Gemini khi nó đảm nhận các công việc then chốt như tạo, gửi yêu cầu, xử lý kết quả trả về cho Excel hiển thị. Code đều được chú thích cụ thể, rõ ràng để mọi người có thể dễ theo dõi và tránh cho bài viết này dài dòng và lan man.
Lưu ý: Khóa API được gán trực tiếp vào code, điều này có thể không an toàn do người có chuyên môn có thể soi thấy khóa API, cho nên cách tốt nhất là người dùng nên sửa lại code cho phép lấy thông tin khóa API từ tập tin bên ngoài.

Gemini.h:

C++:
#pragma once
#import <winhttpcom.dll>
#include <comdef.h>
#include <comutil.h>
#include "nlohmann/json.hpp"
#include <vector>
#include "Response.h"

class Gemini
{
public:
    Gemini(PCWSTR lpszKey);
    virtual HRESULT AskGemini(PCWSTR szPrompt, Response& response, WCHAR** lppszError);
    BOOL ApiKey(PCWSTR lpszValue);
    PCWSTR GetApiKey() const;
private:
    PCWSTR lpszApiKey = L"";
};

Gemini.cpp:
C++:
#include "Gemini.h"

Gemini::Gemini(PCWSTR lpszKey) {
    size_t len;
    if (!lpszKey || !(len = wcslen(lpszKey))) lpszApiKey = L"";
    else lpszApiKey = lpszKey;
}

BOOL Gemini::ApiKey(PCWSTR lpszValue) {
    if (!lpszValue) return FALSE;
    lpszApiKey = lpszValue;
    return lpszApiKey ? TRUE : FALSE;
}

PCWSTR Gemini::GetApiKey() const {
    return lpszApiKey;
}

/*
    Hàm này chuyển đổi kiểu chuỗi BSTR thành kiểu chuỗi WCHAR (hay wchar_t)
    BSTR hay WCHAR thực chất đều giống nhau,
    chỉ khác ở chỗ BSTR quy định 4 byte đầu tiên chứa thông tin về độ dài chuỗi
    Tham khảo thêm: https://learn.microsoft.com/en-us/previous-versions/windows/desktop/automat/bstr
*/
static WCHAR* BstrToWchar(BSTR bstrVal) {
    size_t len;
    //Nếu biến bstrVal là con trỏ không hợp lệ (0) hoặc độ dài chuỗi là 0 thì trả về NULL (0)
    if (!bstrVal || !(len = SysStringLen(bstrVal))) return NULL;
    //Cấp phát bộ nhớ động cho chuỗi kết quả, chừa ra 2 byte để chứa ký tự rỗng kết thúc
    wchar_t* wstrRetVal = new wchar_t[len + 1];
    //Nếu không thể cấp phát bộ nhớ tức là hết bộ nhớ thì trả về NULL
    if (!wstrRetVal) return NULL;
    //Sao chép nội dung mà bstrVal trỏ đến vào biến wstrRetVal
    memcpy(wstrRetVal, bstrVal, len * sizeof(WCHAR));
    //Gán ký tự tự rỗng kết thúc vào vị trí cuối cùng của mảng chuỗi, sau đó trả về kết qủa
    wstrRetVal[len] = '\0';
    return wstrRetVal;
}

/*
    Hàm này chuyển đổi kiểu chuỗi BSTR thành chuỗi mã hóa (encode) UTF-8
    Để các hàm xử lý chuỗi JSON có thể làm việc
*/
static char* BstrToChar(BSTR bstrVal) {
    size_t len;
    //Nếu biến bstrVal là con trỏ không hợp lệ (0) hoặc độ dài chuỗi là 0 thì trả về NULL (0)
    if (!bstrVal || !(len = SysStringLen(bstrVal))) return NULL;
    //Xác định độ dài chuỗi cần thiết để chuẩn bị cho việc cấp phát bộ nhớ động
    int strLen = WideCharToMultiByte(CP_UTF8, 0, bstrVal, (int)len, NULL, 0, NULL, NULL);
    //Tiến hành cấp phát bộ nhớ động, chừa ra một byte để đánh dấu ký tự rỗng kết thúc
    char* strRetVal = new char[strLen + 1];
    //Nếu không thể cấp phát bộ nhớ tức là hết bộ nhớ thì trả về NULL
    if (!strRetVal) return NULL;
    //Trường hợp hàm WideCharToMultiByte trả về 0, nghĩa là quá trình chuyển đổi không thành công
    if (!WideCharToMultiByte(CP_UTF8, 0, bstrVal, (int)len, strRetVal, strLen, NULL, NULL)) {
        //Giải phóng bộ nhớ động đã cấp phát, sau đó thoát khỏi hàm
        delete[] strRetVal;
        return NULL;
    }
    //Gán ký tự tự rỗng kết thúc vào vị trí cuối cùng của mảng chuỗi, sau đó trả về kết quả
    strRetVal[strLen] = '\0';
    return strRetVal;
}

/*
    Hàm này chuyển đổi ký tự mã hóa UTF-8 thành mảng byte (byte array)
*/
static HRESULT CharToSafeArray(LPCSTR strVal, SAFEARRAY** ppsa) {
    //Nếu strVal là con trỏ không hợp lệ thì thoát khỏi hàm
    if (!strVal) return E_POINTER;
    //Xác định độ dài chuỗi
    size_t len = strlen(strVal);
    //Nếu là chuỗi rỗng (hay "") thì thoát khỏi hàm
    if (!len) return E_INVALIDARG;
    HRESULT hr;
    //Tiến hành khai báo kích thước mảng, cận dưới là 0 và số lượng phần tử tương ứng với độ dài chuỗi
    SAFEARRAYBOUND sab[1]{};
    sab[0].cElements = (ULONG)len;
    sab[0].lLbound = 0;
    /*
        Khai báo mảng SAFEARRAY
        Để biểu diễn một dãy byte thì chỉ cần kiểu số không âm VT_UI1 (hay ushort) là đủ
    */
    SAFEARRAY* psa = SafeArrayCreate(VT_UI1, 1, sab);
    //Nếu không thể khởi tạo mảng thì nghĩa là đã hết bộ nhớ
    if (!psa) return E_OUTOFMEMORY;
    //Khai báo biến lpData để truy cập mảng
    LPVOID lpData = NULL;
    //Tiến hành khóa và truy cập mảng
    hr = SafeArrayAccessData(psa, &lpData);
    //Nếu không thể khóa truy cập mảng thì tiến hành dọn dẹp bộ nhớ và thoát khỏi hàm
    if (FAILED(hr)) {
        SafeArrayDestroy(psa);
        return hr;
    }
    //Sao chép nội dung của biến strVal vào mảng
    memcpy(lpData, strVal, len);
    //Mở khóa truy cập mảng
    hr = SafeArrayUnaccessData(psa);
    //Nếu không thể mở khóa truy cập mảng thì tiến hành dọn dẹp bộ nhớ và thoát khỏi hàm
    if (FAILED(hr)) {
        SafeArrayDestroy(psa);
        return hr;
    }
    //Gán con trỏ kết quả
    *ppsa = psa;
    //Thông báo rằng hoạt động đã diễn ra thành công
    return S_OK;
}
//Các hàm ParsePart, ParseContent, ParseCandidate và ParseResponse chịu trách nhiệm xử lý chuỗi JSON
static HRESULT ParsePart(const nlohmann::json& json, Part& part) {
    try {
        if (json.empty()) return E_INVALIDARG;
        if (json.contains("text") && !json.at("text").empty() && !json.at("text").is_null() && json.at("text").is_string())
            json.at("text").get_to<std::string>(part.Text);
    }
    catch (nlohmann::json::type_error&) {
        return E_FAIL;
    }
    return S_OK;
}

static HRESULT ParseContent(const nlohmann::json& json, Content& result) {
    try {
        if (json.empty()) return E_INVALIDARG;
        if (json.contains("role") && !json.at("role").empty() && !json.at("role").is_null() && json.at("role").is_string())
            json.at("role").get_to<std::string>(result.Role);
        if (json.contains("parts") && !json.at("parts").empty() && !json.at("parts").is_null() && json.at("parts").is_array()) {
            std::vector<Part>parts;
            for (auto i = json["parts"].rbegin(); i < json["parts"].rend(); i++) {
                nlohmann::json item = *i;
                Part part{};
                HRESULT hr = ParsePart(item, part);
                if (FAILED(hr)) return hr;
                parts.push_back(part);
            }
            result.Parts = parts;
        }
    }
    catch (nlohmann::json::type_error&) {
        return E_FAIL;
    }
    return S_OK;
}

static HRESULT ParseCandidate(const nlohmann::json& json, Candidate& result) {
    try {
        if (json.empty()) return E_INVALIDARG;
        if (json.contains("finishReason") && !json.at("finishReason").empty() && !json.at("finishReason").is_null() && json.at("finishReason").is_string())
            json.at("finishReason").get_to<std::string>(result.FinishReason);
        if (json.contains("avgLogprobs") && !json.at("avgLogprobs").empty() && !json.at("avgLogprobs").is_null() && json.at("avgLogprobs").is_number_float())
            json.at("avgLogprobs").get_to<float>(result.AvgLogprobs);
        if (json.contains("content") && !json.at("content").empty() && !json.at("content").is_null() && !json.at("content").is_array()) {
            Content content{};
            nlohmann::json e = json["content"];
            HRESULT hr = ParseContent(e, content);
            if (FAILED(hr)) return hr;
            result.Content = content;
        }
    }
    catch (nlohmann::json::type_error&) {
        return E_FAIL;
    }
    return S_OK;
}

static HRESULT ParseResponse(BSTR bstrResponse, Response& result) {
    if (!bstrResponse) return E_POINTER;
    size_t len = SysStringLen(bstrResponse);
    if (!len) return E_INVALIDARG;
    char* strResponse = BstrToChar(bstrResponse);
    if (!strResponse) return E_OUTOFMEMORY;
    Response response{};
    try {
        nlohmann::json json = nlohmann::json::parse(strResponse);
        delete[] strResponse;
        if (json.contains("candidates") && !json.at("candidates").is_null() && json.at("candidates").is_array()) {
            std::vector<Candidate>candidates;
            for (auto i = json["candidates"].rbegin(); i < json["candidates"].rend(); i++) {
                nlohmann::json item = *i;
                Candidate candidate{};
                HRESULT hr = ParseCandidate(item, candidate);
                if (FAILED(hr)) return hr;
                candidates.push_back(candidate);
            }
            result.Candidates = candidates;
        }
        if (json.contains("modelVersion") && !json.at("modelVersion").is_null() && json.at("modelVersion").is_string())
            json.at("modelVersion").get_to<std::string>(result.ModelVersion);
    }
    catch (nlohmann::json::type_error&) {
        delete[] strResponse;
        return E_FAIL;
    }
    return S_OK;
}

/*
    Hàm này có nhiệm vụ chính sau:
    1. Chuẩn bị chuỗi JSON để gửi đi thông qua thư viện WinHTTP COM
    2. Xử lý chuỗi JSON nhận được và trả kết quả
*/
HRESULT Gemini::AskGemini(PCWSTR lpszPrompt, Response& response, WCHAR** lppszError) {
    using namespace WinHttp;
    size_t nPromptLen, nApiKeyLen;
    //Kiểm tra các con trỏ đầu vào phải đạt yêu cầu, nếu không phải thoát khỏi hàm
    if (!lpszPrompt || (nPromptLen = lstrlen(lpszPrompt)) == 0 || !lpszApiKey || (nApiKeyLen = lstrlen(lpszApiKey)) == 0) return E_POINTER;
    /*
        Xác định độ dài chuỗi cần thiết để chuẩn bị cấp phát bộ nhớ động
        Lý do sử dụng hàm WideCharToMultiByte là vì
        Thư viện nlohmann chỉ hỗ trợ xử lý chuỗi JSON dưới dạng mã hóa UTF-8
    */
    int nPromptNumChars = WideCharToMultiByte(CP_UTF8, 0, lpszPrompt, (int)nPromptLen, NULL, 0, NULL, NULL);
    //Cấp phát bộ nhớ động
    char* szUtf8Prompt = new char[nPromptNumChars + 1];
    //Nếu không thể cấp phát bộ nhớ tức là đã hết bộ nhớ, cần thoát khỏi hàm
    if (!szUtf8Prompt) return E_OUTOFMEMORY;
    //Tiến hành chuyển đổi chuỗi sang dạng mã hóa UTF-8, nếu không thành công thì dọn dẹp bộ nhớ và thoát khỏi hàm
    if (WideCharToMultiByte(CP_UTF8, 0, lpszPrompt, (int)nPromptLen, szUtf8Prompt, nPromptNumChars, NULL, NULL) == 0) {
        delete[] szUtf8Prompt;
        return E_FAIL;
    }
    //Gán ký tự rỗng kết thúc vào phần tử cuối cùng của mảng chuỗi
    szUtf8Prompt[nPromptNumChars] = '\0';
    try {
        //Khối lệnh này chịu trách nhiệm khởi tạo đối tượng JSON để gửi yêu cầu đến Google Gemini
        nlohmann::json jsonPart;
        jsonPart["text"] = szUtf8Prompt;
        delete[] szUtf8Prompt;
        nlohmann::json jsonParts = nlohmann::json::array({ jsonPart });
        nlohmann::json jsonContent;
        jsonContent["parts"] = jsonParts;
        nlohmann::json jsonContents = nlohmann::json::array({ jsonContent });
        nlohmann::json json;
        json["contents"] = jsonContents;
        //Khởi tạo thư viện COM
        HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
        if (FAILED(hr)) return hr;
        //Khởi tạo đối tượng dùng để gửi yêu cầu đến Google Gemini
        IWinHttpRequestPtr pWinHttp;
        hr = pWinHttp.CreateInstance(__uuidof(WinHttpRequest));
        //Nếu không thể khởi tạo đối tượng thì thoát khỏi hàm
        if (FAILED(hr)) return hr;
        /*
            Chuẩn bị URL để kết nối với Google Gemini
            Chuẩn bị các tham số cần thiết
        */
        _bstr_t bstrUrl("https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=" + _bstr_t(lpszApiKey));
        hr = pWinHttp->Open(_bstr_t("POST"), bstrUrl, VARIANT_TRUE);
        if (FAILED(hr)) {
            CoUninitialize();
            return hr;
        }
        hr = pWinHttp->SetRequestHeader(_bstr_t("Content-Type"), _bstr_t("application/json"));
        if (FAILED(hr)) {
            CoUninitialize();
            return hr;
        }
        //Chuyển đối tượng JSON thành dạng chuỗi
        std::string strJson(json.dump());
        //Khởi tạo mảng SAFEARRAY chứa chuỗi JSON ở dạng mảng byte
        SAFEARRAY* psa = NULL;
        hr = CharToSafeArray(strJson.c_str(), &psa);
        if (FAILED(hr)) {
            CoUninitialize();
            return hr;
        }
        VARIANT vtRequestBody{};
        VariantInit(&vtRequestBody);
        vtRequestBody.vt = VT_ARRAY | VT_UI1;
        vtRequestBody.parray = psa;
        //Gửi yêu cầu đi, nếu hoạt động không thành công thì dọn dẹp bộ nhớ và thoát khỏi hàm
        hr = pWinHttp->Send(vtRequestBody);
        if (FAILED(hr)) {
            CoUninitialize();
            VariantClear(&vtRequestBody);
            return hr;
        }
        //Dọn dẹp bộ nhớ sau khi dùng xong
        VariantClear(&vtRequestBody);
        /*
            Nếu quá thời gian 30 giây mà vẫn chưa nhận được phản hồi từ Google Gemini
            Xuất ra chuỗi thông báo, sau đó dọn dẹp bộ nhớ và thoát khỏi hàm
        */
        if (!pWinHttp->WaitForResponse(30)) {
            WCHAR lpszRequestTimedOut[] = L"Error: Request timed out";
            size_t len = wcslen(lpszRequestTimedOut);
            WCHAR* lpszText = new WCHAR[len + 1];
            if (!lpszText) return NULL;
            memcpy(lpszText, lpszRequestTimedOut, len * sizeof(WCHAR));
            lpszText[len] = '\0';
            *lppszError = lpszText;
            CoUninitialize();
            return E_FAIL;
        }
        //Kiểm tra status code trả về
        long nStatus = pWinHttp->GetStatus();
        //Nếu trả về mã 200 tức là yêu cầu đã thành công
        if (nStatus == 200) {
            //Lưu trữ kết quả dạng chuỗi
            _bstr_t bstrResponse(pWinHttp->GetResponseText());
            //Gọi hàm chuyển đổi (deserialize), hay giải tuần tự hóa, đối tượng JSON thành dạng struct
            hr = ParseResponse(bstrResponse, response);
            if (FAILED(hr)) {
                CoUninitialize();
                return E_FAIL;
            }
        }
        //Trường hợp trả về mã khác
        else {
            //Xuất ra chuỗi thông báo lỗi, dọn dẹp bộ nhớ và thoát khỏi hàm
            _bstr_t bstrError(pWinHttp->GetResponseText());
            WCHAR* wstrError = BstrToWchar(bstrError);
            *lppszError = wstrError;
            CoUninitialize();
            return E_FAIL;
        }
        CoUninitialize();
    }
    //Xử lý ngoại lệ không thể xử lý chuỗi thành đối tượng JSON
    catch (const std::exception&) {
        return E_FAIL;
    }
    //Thông báo rằng hoạt động đã diễn ra thành công
    return S_OK;
}

dllmain.cpp:

C++:
#include "XLCALL.H"
#include "FRAMEWRK.H"
#include "string"
#include "Gemini.h"
#include "framework.h"

#pragma comment(lib, "XLCALL32.LIB")

/*
    Khai báo biến lưu trữ chỉ mục giúp phân biệt vùng nhớ cục bộ cho mỗi thread
    Điều này cần thiết khi Excel áp dụng tính năng multithreaded recalculation, multithread calculation
    Tức là sử dụng đa luồng để tính toán các công thức trong bảng tính
    Lúc này mỗi thread sẽ được cấp phát động một vùng nhớ riêng biệt
    Giúp đảm bảo tính an toàn thread (thread safety)
*/
DWORD dwTlsIndex;

//Khai báo cấu trúc dữ liệu cần dùng
struct TlsData {
    XLOPER12 data;
    XLOPER12 data2;
};

//Hàm này thực hiện việc cấp phát, trả về con trỏ trỏ đến vùng nhớ theo chỉ mục được gán cho mỗi thread
TlsData* GetTlsData(void) {
    //Khởi tạo và xác định vùng nhớ đã được cấp phát
    TlsData* pTlsData = reinterpret_cast<TlsData*>(TlsGetValue(dwTlsIndex));
    //Nếu chưa được cấp phát bộ nhớ thì tiến hành cấp phát động bộ nhớ
    if (!pTlsData) {
        //Tiến hành cấp phát động bộ nhớ
        pTlsData = new TlsData[1];
        //Trường hợp hết bộ nhớ thì thoát khỏi hàm
        if (!pTlsData) return NULL;
        //Ngược lại, gán vùng nhớ được cấp phát động theo chỉ mục
        TlsSetValue(dwTlsIndex, pTlsData);
    }
    //Trả về con trỏ trỏ đến vùng nhớ
    return pTlsData;
}

//Hàm này khởi tạo thread-local storage, hay vùng nhớ cục bộ của mỗi thread
BOOL InitializeTls(DWORD DllMainCallReason) {
    //Tùy thuộc vào trạng thái khi Excel sử dụng/ngừng sử dụng DLL
    switch (DllMainCallReason) {
    case DLL_PROCESS_ATTACH:
        //Khi Excel sử dụng DLL thì tiến hành cấp chỉ số cho thread
        if ((dwTlsIndex = TlsAlloc()) == TLS_OUT_OF_INDEXES)
            return FALSE;
        break;
        //Khi Excel ngừng sử dụng DLL thì tiến hành dọn dẹp vùng nhớ đã cấp cho thread theo chỉ mục
    case DLL_PROCESS_DETACH:
        TlsFree(dwTlsIndex);
        break;
    }
    return TRUE;
}

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    return InitializeTls(ul_reason_for_call);
}

/*
    Hàm này chuyển đổi chuỗi wide string thành dạng wide string mà Excel chấp nhận
    Dạng chuỗi wide string mà Excel chấp nhận cơ bản không khác gì mấy so với dạng wide string thông thường
    Khác biệt ở chỗ phần tử đầu tiên trong mảng chuỗi chứa thông tin về độ dài chuỗi
*/
static WCHAR* WCharToXl12Str(const WCHAR* wstrValue) {
    size_t len;
    //Kiểu tra điều kiện tiên quyết của chuỗi đầu vào
    if (!wstrValue || !(len = wcslen(wstrValue))) return NULL;
    //Tiến hành cắt gọt chuỗi theo độ dài tối đa mà Excel quy định
    if (len > 32767u) len = 32767u;
    //Cấp phát bộ nhớ động để lưu trữ kết quả
    WCHAR* wstrRetVal = new WCHAR[len + 2];
    //Nếu hết bộ nhớ thì thoát khỏi hàm
    if (!wstrRetVal) return NULL;
    /*
        Sao chép nội dung của chuỗi đầu vào vào chuỗi kết quả
        Vị trí là phần tử thứ hai của mảng chuỗi cho đến hết
        Lý do là vì Excel yêu cầu phần tử đầu tiên trong mảng chuỗi phải chứa thông tin về độ dài chuỗi
    */
    memcpy(wstrRetVal + 1, wstrValue, (len + 1) * sizeof(WCHAR));
    //Gán thông tin độ dài chuỗi vào phần tử đầu tiên của mảng chuỗi
    wstrRetVal[0] = (WCHAR)len;
    //Trả về kết quả
    return wstrRetVal;
}

//Đây là hàm sẽ xuất hiện trong bảng tính
extern "C" __declspec(dllexport) LPXLOPER12 WINAPI AskGemini(WCHAR* lpszPrompt) {
    //Xác định vùng nhớ được cấp phát riêng cho thread
    TlsData* tlsData = GetTlsData();
    if (!tlsData) return NULL;
    //Khai báo biến chứa kết quả, lấy từ vùng nhớ đã cấp phát riêng cho thread
    LPXLOPER12 result = &(tlsData->data);
    size_t len;
    //Kiểm tra các điều kiện tiên quyết của chuỗi đầu vào, nếu không đạt yêu cầu thì báo lỗi
    if (!lpszPrompt || !(len = wcslen(lpszPrompt))) {
        result->val.err = xlerrNA;
        result->xltype = xltypeErr;
        return result;
    }
    //Khai báo class để làm việc với Google Gemini
    Response response{};
    //Khóa API
    Gemini gemini{L"Điền khóa API vào đây"};
    //Khai báo con trỏ trỏ đến vùng nhớ chứa chuỗi thông báo lỗi
    WCHAR* wstrError = NULL;
    //Gửi yêu cầu đi
    HRESULT hr = gemini.AskGemini(lpszPrompt, response, &wstrError);
    //Nếu thất bại
    if (FAILED(hr)) {
        //Xác định thông báo lỗi
        WCHAR* wstrXl12Err = WCharToXl12Str(wstrError);
        //Nếu hết bộ nhớ thì tiến hành dọn dẹp và trả về lỗi
        if (!wstrXl12Err) {
            delete[] wstrError;
            result->val.err = xlerrNA;
            result->xltype = xltypeErr;
            return result;
        }
        //Ngược lại, tiến hành dọn dẹp bộ nhớ và trả về lỗi
        delete[] wstrError;
        result->val.str = (wchar_t*)wstrXl12Err;
        result->xltype = xltypeStr | xlbitDLLFree;
        return result;
    }
    //Kiểm tra xem liệu kết quả trả về có chứa thông tin không
    if (!response.Candidates.size() || !response.Candidates[0].Content.Parts.size()) {
        result->val.err = xlerrNA;
        result->xltype = xltypeErr;
        return result;
    }
    //Xác định thông tin cần lấy
    std::string strAnswer(response.Candidates[0].Content.Parts[0].Text);
    /*
        Do thư viện nlohmann chỉ chấp nhận chuỗi mã hóa UTF-8, cho nên
        Những khối lệnh sử dụng hàm MultiByteToWideChar dưới đây
        Có nhiệm vụ chuyển đổi chuỗi mã hóa UTF-8 thành chuỗi wide string
    */
    //Xác định độ dài chuỗi để chuẩn bị cho việc cấp phát bộ nhớ động
    int nAnswerlen = MultiByteToWideChar(CP_UTF8, 0, strAnswer.c_str(), -1, NULL, 0);
    //Cấp phát bộ nhớ động
    WCHAR* wstrRetVal = new WCHAR[nAnswerlen];
    //Nếu hết bộ nhớ thì thông báo lỗi
    if (!wstrRetVal) {
        result->val.err = xlerrNA;
        result->xltype = xltypeErr;
        return result;
    }
    //Nếu không thể thực hiện việc chuyển đổi thì trả về lỗi
    if (!MultiByteToWideChar(CP_UTF8, 0, strAnswer.c_str(), -1, wstrRetVal, nAnswerlen)) {
        delete[] wstrRetVal;
        result->val.err = xlerrNA;
        result->xltype = xltypeErr;
        return result;
    }
    //Chuyển đổi chuỗi thành dạng mà Excel yêu cầu
    WCHAR* wstrResult = WCharToXl12Str(wstrRetVal);
    //Dọn dẹp bộ nhớ sau khi dùng xong
    delete[] wstrRetVal;
    //Nếu hàm WCharToXl12Str không thể xử lý thì trả về lỗi
    if (!wstrResult) {
        result->val.err = xlerrNA;
        result->xltype = xltypeErr;
        return result;
    }
    /*
        Tổ chức kết quả theo cấu trúc để Excel có thể xử lý được
        xltypeStr tức là mang kiểu chuỗi
        Do kết quả trả về là kiểu chuỗi được cấp phát động
        Cho nên cần phải dùng bit cờ xlbitDLLFree
        Để thông báo cho Excel biết rằng nó cần phải dọn dẹp bộ nhớ sau khi sử dụng xong
        Thông qua hàm gọi lại (callback) xlAutoFree12
    */
    result->xltype = xltypeStr | xlbitDLLFree;
    result->val.str = (WCHAR*)wstrResult;
    return result;
}

extern "C" __declspec(dllexport) int WINAPI xlAutoOpen(void)
{
    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, 12, (LPXLOPER12)&xlDllName, //Đăng ký tên của DLL
            (LPXLOPER12)TempStr12(L"AskGemini"), //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"AskGemini"), //Tên hàm trong trình Function Wizard
            (LPXLOPER12)TempStr12(L"Prompt"), //Mô tả tham số trong trình Function Wizard
            (LPXLOPER12)TempStr12(L"1"), //Kiểu macro
            (LPXLOPER12)TempStr12(L"Google AI Assistant"), //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"Ask Gemini for assistance"), //Mô tả hàm khi được chọn trong trình Function Wizard
            (LPXLOPER12)TempStr12(L"Prompt to send to Gemini"), //Mô tả tham số khi được chọn trong trình Function Wizard (tối đa lên đến 255 tham số)
            (LPXLOPER12)TempStr12(L""));
        //Giải phóng biến xDLL
        Excel12f(xlFree, NULL, 1, (LPXLOPER12)&xlDllName);
    }
    return 1;
}

extern "C" __declspec(dllexport) int WINAPI xlAutoClose(void) {
    //Hủy đăng ký hàm trước khi ngừng sử dụng add-in
    XLOPER12 xlFuncId;
    if (Excel12f(xlfEvaluate, &xlFuncId, 1, (LPXLOPER12)TempStr12(L"AskGemini")) == xlretSuccess) {
        Excel12f(xlfUnregister, NULL, 1, (LPXLOPER12) & xlFuncId);
        Excel12f(xlFree, NULL, 1, (LPXLOPER12)&xlFuncId);
        Excel12f(xlfSetName, NULL, 1, (LPXLOPER12)TempStr12(L"AskGemini"));
    }
    return 1;
}

/*
    Hàm này Excel sẽ gọi
    Khi kết quả trả về cho Excel chứa một các bit cờ xlbitDLLFree và xlbitXLFree
    Những kiểu như xltypeStr, xltypeRef và xltypeMulti sẽ được gắn cờ xlbitDLLFree
    Còn kiểu XLOPER12 được cấp phát bộ nhớ động thì sẽ được gắn cờ xlbitXLFree
*/
extern "C" __declspec(dllexport) void WINAPI xlAutoFree12(LPXLOPER12 p_oper) {
    //Nếu kết quả chứa kiểu chuỗi thì dọn dẹp vùng nhớ
    if (p_oper->xltype & xltypeStr)
        delete[] p_oper->val.str;
    //Nếu là mảng
    else if (p_oper->xltype & xltypeMulti) {
        //Xác định kích thước của mảng, hay số lượng phần tử có trong mảng
        int size = p_oper->val.array.columns * p_oper->val.array.rows;
        LPXLOPER12 p = p_oper->val.array.lparray;
        //Tiến hành duyệt qua mảng
        for (; size-- > 0; p++) {
            /*
                Khi loại trừ các bit cờ xlbitDLLFree và xlbitXLFree
                Nếu là kiểu chuỗi thì dọn dẹp bộ nhớ
            */
            if ((p->xltype & ~(xlbitDLLFree | xlbitXLFree)) == xltypeStr) {
                if (p->xltype & xlbitDLLFree)
                    delete[] p->val.str;
                //Nếu là kiểu XLOPER12 được cấp phát động thì dọn dẹp bộ nhớ
                else if (p->xltype & xlbitXLFree)
                    Excel12f(xlFree, 0, 1, p);
            }
        }
        //Dọn dẹp bộ nhớ đã cấp phát cho mảng
        delete p_oper->val.array.lparray;
    }
    //Những trường hợp cần dọn dẹp bộ nhớ còn lại
    else if (p_oper->xltype & xltypeRef)
        delete p_oper->val.mref.lpmref;
    else if (p_oper->xltype & xlbitDLLFree) delete p_oper;
}

//Hàm này đăng ký thông tin add-in với Excel
extern "C" __declspec(dllexport) LPXLOPER12 WINAPI xlAddInManagerInfo12(LPXLOPER12 pxAction) {
    TlsData* pTlsData = GetTlsData();
    if (!pTlsData) return NULL;
    LPXLOPER12 lpResult = &(pTlsData->data);
    LPXLOPER12 lpIntAction = &(pTlsData->data2);
    if (Excel12f(xlCoerce, lpIntAction, 1, pxAction) == xlretSuccess) {
        if (lpIntAction->val.w == 1) {
            lpResult->xltype = xltypeStr;
            lpResult->val.str = (WCHAR*) L"Google Gemini Assistant";
        }
        else {
            lpResult->xltype = xltypeErr;
            lpResult->val.err = xlerrValue;
        }
    }
    return lpResult;
}

Response.h:

C++:
#pragma once
#include <string>
#include <vector>

struct Part {
    std::string Text;
};

struct Content {
    std::vector<Part>Parts;
    std::string Role;
};

struct Candidate {
    Content Content;
    std::string FinishReason;
    float AvgLogprobs;
};

struct CandidatesTokensDetail {
    std::string Modality;
    unsigned long TokenCount;
};

struct PromptTokensDetail {
    std::string Modality;
    unsigned long TokenCount;
};

struct UsageMetadata {
    unsigned long PromptTokenCount;
    unsigned long CandidatesTokenCount;
    unsigned long TotalTokenCount;
    std::vector<PromptTokensDetail>PromptTokensDetails;
    std::vector< CandidatesTokensDetail> CandidatesTokensDetails;
};

struct Response {
    std::vector<Candidate>Candidates;
    UsageMetadata UsageMetadata;
    std::string ModelVersion;
};

Sử dụng công thức trong Excel:
1746117683856.png

Người dùng có thể tìm thấy code đầy đủ đính kèm theo bài viết này.
 

File đính kèm

Web KT

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

Back
Top Bottom