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:
Gemini.cpp:
dllmain.cpp:
Response.h:
Sử dụng công thức trong Excel:

Người dùng có thể tìm thấy code đầy đủ đính kèm theo bài viết này.
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:

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