VBA 调用 dll 优化执行效率
问题描述
之前excel 用vba写过一个应用,请求的是aws lambda 后端, 但是受限于是云端服务,用起来响应特别慢,最近抽了点时间准备优化下,先加了点日志看看是哪里慢了
主方法代码如下,函数的主要目的是将 Excel 工作簿的数据(包括每个单元格的值和格式)转换为 JSON 格式并发送到指定的后端服务,以便进一步处理或存储。
该 VBA 函数 SendWorkbookSnapshotToBackend 的主要作用是将 Excel 工作簿的快照(即每个工作表的数据和格式)发送到一个指定的后端 API。以下是函数的详细解释:
功能概述
-
参数:
router:用于构建 API URL 的路径部分。函数首先定义了 API 的基本 URL,并将router参数附加到 URL 末尾。 -
初始化变量:创建多个变量,包括用于存储 HTTP 请求对象、工作表对象、数据范围、快照字典等。
-
遍历工作表:
- 函数遍历当前工作簿中的每个工作表,获取每个工作表的最后一行和最后一列的索引。
- 如果工作表中有数据,创建一个字典
sheetData来存储每个单元格的值和格式。
-
收集数据:
- 使用双重循环遍历每个单元格,记录其地址、值和格式,并将它们添加到
sheetData字典中。 - 最后将每个工作表的快照添加到
snapshot字典中,以工作表名称为键。
- 使用双重循环遍历每个单元格,记录其地址、值和格式,并将它们添加到
-
创建请求:将快照字典嵌套在另一个字典
sts中,并进一步嵌套在payload字典中,以形成最终的 JSON 请求参数。 -
发送 HTTP 请求:
- 使用
MSXML2.ServerXMLHTTP对象发送 POST 请求,内容类型为 JSON。 - 函数记录请求的执行时间,并处理可能的错误。
- 使用
-
解析响应:
- 接收和解析响应内容,检查是否存在
workbook_snapshot字段。 - 如果存在,则调用
ProcessWorkbookSnapshot函数处理响应中的工作簿快照。
- 接收和解析响应内容,检查是否存在
-
处理窗口操作:如果响应中包含
workbook_snapshot_operate字段,则调用ProcessWindowOperations函数处理这些操作。
Function SendWorkbookSnapshotToBackend(router As String) As StringDim API_URL As StringAPI_URL = "http://xxx:xx/"Dim url As Stringurl = API_URL + routerDim http As ObjectDim ws As WorksheetDim lastRow As LongDim lastCol As LongDim cell As RangeDim snapshot As ObjectDim sheetData As ObjectDim jsonSnapshot As StringDim response As StringDim sheetName As VariantDim updatedSheetData As ObjectDim cellKey As VariantSet snapshot = CreateObject("Scripting.Dictionary")Dim startTime As DoubleDim endTime As DoublestartTime = TimerFor Each ws In ThisWorkbook.SheetsSet sheetData = CreateObject("Scripting.Dictionary")lastRow = ws.UsedRange.Rows.CountlastCol = ws.UsedRange.Columns.CountIf lastRow > 0 And lastCol > 0 ThenDim values As VariantDim formats As VariantDim dataRange As RangeSet dataRange = ws.Range(ws.Cells(1, 1), ws.Cells(lastRow, lastCol))values = dataRange.ValueFor r = 1 To lastRowFor c = 1 To lastColDim addr As Stringaddr = ws.Cells(r, c).Address(False, False)sheetData.Add addr, values(r, c)sheetData.Add addr & "_Format", ws.Cells(r, c).NumberFormatNext cNext rsnapshot.Add ws.Name, sheetDataEnd IfNext wsendTime = TimerDebug.Print "snapshot Add executed in " & Format(endTime - startTime, "0.0000") & " seconds"Set sts = CreateObject("Scripting.Dictionary")sts.Add "Sheets", snapshotSet payload = CreateObject("Scripting.Dictionary")payload.Add "workbook_snapshot", stsDim jsonPayload As StringjsonPayload = JsonConverter.ConvertToJson(payload)Set http = CreateObject("MSXML2.ServerXMLHTTP")On Error Resume NextstartTime = Timerhttp.Open "POST", url, Falsehttp.setRequestHeader "Content-Type", "application/json"http.send jsonPayloadendTime = TimerDebug.Print "http.send executed in " & Format(endTime - startTime, "0.0000") & " seconds"If Err.Number <> 0 ThenMsgBox "HTTP: " & Err.Description & " " & Err.Number & ")"SendWorkbookSnapshotToBackend = "Error: HTTP request failed - " & Err.NumberExit FunctionEnd IfOn Error GoTo 0startTime = Timerresponse = http.responseTextSet result = JsonConverter.ParseJson(response)endTime = TimerDebug.Print "ParseJson executed in " & Format(endTime - startTime, "0.0000") & " seconds"' update excel cell valueIf result.Exists("workbook_snapshot") ThenstartTime = TimerCall ProcessWorkbookSnapshot(result("workbook_snapshot"))endTime = TimerDebug.Print "ProcessWorkbookSnapshot executed in " & Format(endTime - startTime, "0.0000") & " seconds"End If' done the window operationIf result.Exists("workbook_snapshot_operate") ThenCall ProcessWindowOperations(result("workbook_snapshot_operate"))End IfSendWorkbookSnapshotToBackend = "Success: " & responseSet http = NothingSet snapshot = NothingSet result = Nothing
End Function
日志显示

可以看到整个处理耗时在非请求的部分挺多的, json解析的效率也不高,考虑把这些逻辑都挪到dll库里,让VBA调用dll来提升效率
安装MinGW

GitHub 上较新版的压缩包的命名又分为 msvcrt 和 ucrt。MSVCRT(Microsoft Visual C++ Runtime)和 UCRT(Universal C Runtime)是 Microsoft Windows 上的两种 C 运行时库。MSVCRT 在所有 Windows 版本上均可用,把./mingw64/bin 配置到PATH

有鉴于我的C语言都还给老师了, vcpkg 安装库依赖一直在报错,所以手写造了下cur 和cjson的轮子,放在一个文件里来打dll 文件
#include <windows.h>
#include <objbase.h>
#include <ole2.h>
#include <wininet.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>// Configuration: Backend URL
#define BACKEND_URL "http://127.0.0.1:5000"
#define LOG_FILE "D:\\xxxx\debug.log"void log_message(const char* message) {FILE* fp = fopen(LOG_FILE, "a");if (fp) {fprintf(fp, "%s\n", message);fclose(fp);}
}// BSTR conversion functions for MinGW
char* ConvertBSTRToString(BSTR bstr) {if (!bstr) return _strdup("");int len = WideCharToMultiByte(CP_UTF8, 0, bstr, -1, NULL, 0, NULL, NULL);char* str = (char*)malloc(len);WideCharToMultiByte(CP_UTF8, 0, bstr, -1, str, len, NULL, NULL);return str;
}BSTR ConvertStringToBSTR(const char* str) {if (!str) return SysAllocString(L"");int len = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0);wchar_t* wstr = (wchar_t*)malloc(len * sizeof(wchar_t));MultiByteToWideChar(CP_UTF8, 0, str, -1, wstr, len);BSTR bstr = SysAllocString(wstr);free(wstr);return bstr;
}// Simplified JSON parser and generator
typedef enum { JSON_OBJECT, JSON_ARRAY, JSON_STRING, JSON_NUMBER } JsonType;typedef struct JsonValue {JsonType type;union {struct { char* key; struct JsonValue* value; } pair; // For object key-value pairsstruct { struct JsonValue* items; size_t count; } array; // For arrayschar* string; // For stringsdouble number; // For numbers} value;struct JsonValue* next; // For linked lists (object pairs or array items)
} JsonValue;JsonValue* json_create_object() {JsonValue* obj = (JsonValue*)calloc(1, sizeof(JsonValue));obj->type = JSON_OBJECT;return obj;
}JsonValue* json_create_array() {JsonValue* arr = (JsonValue*)calloc(1, sizeof(JsonValue));arr->type = JSON_ARRAY;return arr;
}JsonValue* json_create_string(const char* str) {JsonValue* val = (JsonValue*)calloc(1, sizeof(JsonValue));val->type = JSON_STRING;val->value.string = _strdup(str);return val;
}JsonValue* json_create_number(double num) {JsonValue* val = (JsonValue*)calloc(1, sizeof(JsonValue));val->type = JSON_NUMBER;val->value.number = num;return val;
}void json_add_to_object(JsonValue* obj, const char* key, JsonValue* value) {if (obj->type != JSON_OBJECT) return;JsonValue* pair = (JsonValue*)calloc(1, sizeof(JsonValue));pair->type = JSON_OBJECT;pair->value.pair.key = _strdup(key);pair->value.pair.value = value;pair->next = obj->value.pair.value;obj->value.pair.value = pair;
}void json_add_to_array(JsonValue* arr, JsonValue* value) {if (arr->type != JSON_ARRAY) return;value->next = arr->value.array.items;arr->value.array.items = value;arr->value.array.count++;
}void json_free(JsonValue* json) {if (!json) return;if (json->type == JSON_OBJECT) {JsonValue* pair = json->value.pair.value;while (pair) {JsonValue* next = pair->next;free(pair->value.pair.key);json_free(pair->value.pair.value);free(pair);pair = next;}} else if (json->type == JSON_ARRAY) {JsonValue* item = json->value.array.items;while (item) {JsonValue* next = item->next;json_free(item);item = next;}} else if (json->type == JSON_STRING) {free(json->value.string);}free(json);
}char* json_to_string(JsonValue* json) {char* result = (char*)calloc(1, 1024 * 1024); // Large buffersize_t pos = 0;char temp[256];if (json->type == JSON_OBJECT) {result[pos++] = '{';JsonValue* pair = json->value.pair.value;int first = 1;while (pair) {if (!first) result[pos++] = ',';first = 0;snprintf(temp, sizeof(temp), "\"%s\":", pair->value.pair.key);strcat(result + pos, temp);pos += strlen(temp);char* sub = json_to_string(pair->value.pair.value);strcat(result + pos, sub);pos += strlen(sub);free(sub);pair = pair->next;}result[pos++] = '}';} else if (json->type == JSON_ARRAY) {result[pos++] = '[';JsonValue* item = json->value.array.items;int first = 1;while (item) {if (!first) result[pos++] = ',';first = 0;char* sub = json_to_string(item);strcat(result + pos, sub);pos += strlen(sub);free(sub);item = item->next;}result[pos++] = ']';} else if (json->type == JSON_STRING) {snprintf(temp, sizeof(temp), "\"%s\"", json->value.string);strcat(result + pos, temp);pos += strlen(temp);} else if (json->type == JSON_NUMBER) {snprintf(temp, sizeof(temp), "%f", json->value.number);strcat(result + pos, temp);pos += strlen(temp);}result[pos] = '\0';return result;
}// Simplified JSON parser
JsonValue* json_parse(const char* str) {// Basic parser: expects {key:value} or [values], skips whitespaceJsonValue* result = NULL;const char* p = str;while (*p && isspace(*p)) p++;if (*p == '{') {result = json_create_object();p++;while (*p && *p != '}') {while (isspace(*p)) p++;if (*p != '"') break;p++;char key[256] = {0};int k = 0;while (*p && *p != '"' && k < sizeof(key) - 1) key[k++] = *p++;if (*p != '"') break;p++;while (isspace(*p)) p++;if (*p != ':') break;p++;while (isspace(*p)) p++;JsonValue* value = NULL;if (*p == '"') {p++;char val[256] = {0};int v = 0;while (*p && *p != '"' && v < sizeof(val) - 1) val[v++] = *p++;if (*p != '"') break;p++;value = json_create_string(val);} else if (isdigit(*p) || *p == '-') {char num[32] = {0};int n = 0;while ((isdigit(*p) || *p == '.' || *p == '-') && n < sizeof(num) - 1) num[n++] = *p++;value = json_create_number(atof(num));} else if (*p == '{') {value = json_parse(p);while (*p && *p != '}') p++;if (*p == '}') p++;} else {break;}json_add_to_object(result, key, value);while (isspace(*p)) p++;if (*p == ',') p++;}if (*p == '}') p++;} else if (*p == '[') {result = json_create_array();p++;while (*p && *p != ']') {JsonValue* value = json_parse(p);if (!value) break;json_add_to_array(result, value);while (*p && *p != ',' && *p != ']') p++;if (*p == ',') p++;}if (*p == ']') p++;}return result;
}JsonValue* json_get_object_item(JsonValue* obj, const char* key) {if (obj->type != JSON_OBJECT) return NULL;JsonValue* pair = obj->value.pair.value;while (pair) {if (strcmp(pair->value.pair.key, key) == 0) return pair->value.pair.value;pair = pair->next;}return NULL;
}// HTTP client using WinINet
typedef struct {char* memory;size_t size;
} HttpResponse;BOOL http_post(const char* url, const char* data, HttpResponse* response) {HINTERNET hInternet = InternetOpenA("ExcelSnapshot", INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);if (!hInternet) {log_message("Error: InternetOpenA failed");return FALSE;}HINTERNET hConnect = InternetOpenUrlA(hInternet, url, NULL, 0, INTERNET_FLAG_RELOAD, 0);if (!hConnect) {log_message("Error: InternetOpenUrlA failed");InternetCloseHandle(hInternet);return FALSE;}// Set headersconst char* headers = "Content-Type: application/json\r\n";DWORD dataLen = strlen(data);BOOL success = HttpAddRequestHeadersA(hConnect, headers, strlen(headers), HTTP_ADDREQ_FLAG_ADD);if (!success) {log_message("Error: HttpAddRequestHeadersA failed");InternetCloseHandle(hConnect);InternetCloseHandle(hInternet);return FALSE;}// Send requestDWORD bytesWritten;success = InternetWriteFile(hConnect, data, dataLen, &bytesWritten);if (!success || bytesWritten != dataLen) {log_message("Error: InternetWriteFile failed");InternetCloseHandle(hConnect);InternetCloseHandle(hInternet);return FALSE;}// Read responseresponse->memory = (char*)calloc(1, 1024 * 1024); // Large bufferresponse->size = 0;DWORD bytesRead;while (InternetReadFile(hConnect, response->memory + response->size, 1024, &bytesRead) && bytesRead > 0) {response->size += bytesRead;}response->memory[response->size] = '\0';InternetCloseHandle(hConnect);InternetCloseHandle(hInternet);return TRUE;
}// Hash table implementation (simplified for dictionary)
typedef struct {char* key;VARIANT value;
} HashEntry;typedef struct {HashEntry* entries;int size;int capacity;
} HashTable;HashTable* create_hash_table(int capacity) {HashTable* table = (HashTable*)malloc(sizeof(HashTable));table->entries = (HashEntry*)calloc(capacity, sizeof(HashEntry));table->size = 0;table->capacity = capacity;return table;
}void hash_table_add(HashTable* table, const char* key, VARIANT value) {if (table->size >= table->capacity) return; // Simplified, no resizingHashEntry* entry = &table->entries[table->size++];entry->key = _strdup(key);VariantInit(&entry->value);VariantCopy(&entry->value, &value);
}void free_hash_table(HashTable* table) {for (int i = 0; i < table->size; i++) {free(table->entries[i].key);VariantClear(&table->entries[i].value);}free(table->entries);free(table);
}// Main DLL exported function
__declspec(dllexport) BSTR __stdcall SendWorkbookSnapshotToBackend(BSTR router) {HRESULT hr;IDispatch* pExcel = NULL;IDispatch* pWorkbook = NULL;IDispatch* pWorksheets = NULL;JsonValue* snapshot = json_create_object();JsonValue* sheets = json_create_object();char* jsonPayload = NULL;HttpResponse response = {0};BSTR result = NULL;log_message("Starting SendWorkbookSnapshotToBackend");// Initialize COMhr = CoInitialize(NULL);if (FAILED(hr)) {log_message("Error: COM initialization failed");result = SysAllocString(L"Error: COM initialization failed");goto cleanup;}log_message("COM initialized");// Get Excel applicationCLSID clsid;hr = CLSIDFromProgID(L"Excel.Application", &clsid);if (FAILED(hr)) {log_message("Error: Excel not found");result = SysAllocString(L"Error: Excel not found");goto cleanup;}log_message("Excel ProgID found");hr = CoCreateInstance(&clsid, NULL, CLSCTX_LOCAL_SERVER, &IID_IDispatch, (void**)&pExcel);if (FAILED(hr)) {log_message("Error: Failed to create Excel instance");result = SysAllocString(L"Error: Failed to create Excel instance");goto cleanup;}log_message("Excel instance created");// Get active workbookVARIANT vResult;VariantInit(&vResult);DISPID dispid;OLECHAR* szMember = L"ActiveWorkbook";hr = pExcel->lpVtbl->GetIDsOfNames(pExcel, &IID_NULL, &szMember, 1, LOCALE_USER_DEFAULT, &dispid);if (FAILED(hr)) {log_message("Error: Failed to get ActiveWorkbook");result = SysAllocString(L"Error: Failed to get ActiveWorkbook");goto cleanup;}log_message("ActiveWorkbook ID obtained");DISPPARAMS params = { NULL, NULL, 0, 0 };hr = pExcel->lpVtbl->Invoke(pExcel, dispid, &IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_PROPERTYGET, ¶ms, &vResult, NULL, NULL);if (FAILED(hr) || vResult.vt != VT_DISPATCH) {log_message("Error: Failed to access ActiveWorkbook");result = SysAllocString(L"Error: Failed to access ActiveWorkbook");goto cleanup;}pWorkbook = vResult.pdispVal;log_message("ActiveWorkbook accessed");// Get worksheetsszMember = L"Worksheets";hr = pWorkbook->lpVtbl->GetIDsOfNames(pWorkbook, &IID_NULL, &szMember, 1, LOCALE_USER_DEFAULT, &dispid);if (FAILED(hr)) {log_message("Error: Failed to get Worksheets");result = SysAllocString(L"Error: Failed to get Worksheets");goto cleanup;}log_message("Worksheets ID obtained");hr = pWorkbook->lpVtbl->Invoke(pWorkbook, dispid, &IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_PROPERTYGET, ¶ms, &vResult, NULL, NULL);if (FAILED(hr) || vResult.vt != VT_DISPATCH) {log_message("Error: Failed to access Worksheets");result = SysAllocString(L"Error: Failed to access Worksheets");goto cleanup;}pWorksheets = vResult.pdispVal;log_message("Worksheets accessed");// Iterate worksheetsszMember = L"Count";hr = pWorksheets->lpVtbl->GetIDsOfNames(pWorksheets, &IID_NULL, &szMember, 1, LOCALE_USER_DEFAULT, &dispid);if (FAILED(hr)) {log_message("Error: Failed to get Worksheets count");result = SysAllocString(L"Error: Failed to get Worksheets count");goto cleanup;}log_message("Worksheets count ID obtained");hr = pWorksheets->lpVtbl->Invoke(pWorksheets, dispid, &IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_PROPERTYGET, ¶ms, &vResult, NULL, NULL);if (FAILED(hr)) {log_message("Error: Failed to access Worksheets count");result = SysAllocString(L"Error: Failed to access Worksheets count");goto cleanup;}int sheetCount = vResult.lVal;log_message("Worksheets count obtained");for (int i = 1; i <= sheetCount; i++) {// Get worksheetVariantClear(&vResult);szMember = L"Item";hr = pWorksheets->lpVtbl->GetIDsOfNames(pWorksheets, &IID_NULL, &szMember, 1, LOCALE_USER_DEFAULT, &dispid);if (FAILED(hr)) {log_message("Error: Failed to get Worksheet Item");continue;}VARIANT vIndex;VariantInit(&vIndex);vIndex.vt = VT_I4;vIndex.lVal = i;params.rgvarg = &vIndex;params.cArgs = 1;hr = pWorksheets->lpVtbl->Invoke(pWorksheets, dispid, &IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_PROPERTYGET, ¶ms, &vResult, NULL, NULL);if (FAILED(hr) || vResult.vt != VT_DISPATCH) {log_message("Error: Failed to access Worksheet");continue;}IDispatch* pWorksheet = vResult.pdispVal;log_message("Worksheet accessed");// Get worksheet nameVariantClear(&vResult);szMember = L"Name";hr = pWorksheet->lpVtbl->GetIDsOfNames(pWorksheet, &IID_NULL, &szMember, 1, LOCALE_USER_DEFAULT, &dispid);if (FAILED(hr)) {log_message("Error: Failed to get Worksheet Name");pWorksheet->lpVtbl->Release(pWorksheet);continue;}hr = pWorksheet->lpVtbl->Invoke(pWorksheet, dispid, &IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_PROPERTYGET, ¶ms, &vResult, NULL, NULL);if (FAILED(hr) || vResult.vt != VT_BSTR) {log_message("Error: Failed to access Worksheet Name");pWorksheet->lpVtbl->Release(pWorksheet);continue;}char* sheetName = ConvertBSTRToString(vResult.bstrVal);SysFreeString(vResult.bstrVal);log_message("Worksheet name obtained");// Get UsedRangeVariantClear(&vResult);szMember = L"UsedRange";hr = pWorksheet->lpVtbl->GetIDsOfNames(pWorksheet, &IID_NULL, &szMember, 1, LOCALE_USER_DEFAULT, &dispid);if (FAILED(hr)) {log_message("Error: Failed to get UsedRange");free(sheetName);pWorksheet->lpVtbl->Release(pWorksheet);continue;}hr = pWorksheet->lpVtbl->Invoke(pWorksheet, dispid, &IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_PROPERTYGET, ¶ms, &vResult, NULL, NULL);if (FAILED(hr) || vResult.vt != VT_DISPATCH) {log_message("Error: Failed to access UsedRange");free(sheetName);pWorksheet->lpVtbl->Release(pWorksheet);continue;}IDispatch* pRange = vResult.pdispVal;log_message("UsedRange accessed");// Get row and column countVariantClear(&vResult);szMember = L"Rows";hr = pRange->lpVtbl->GetIDsOfNames(pRange, &IID_NULL, &szMember, 1, LOCALE_USER_DEFAULT, &dispid);if (FAILED(hr)) {log_message("Error: Failed to get Rows");pRange->lpVtbl->Release(pRange);free(sheetName);pWorksheet->lpVtbl->Release(pWorksheet);continue;}hr = pRange->lpVtbl->Invoke(pRange, dispid, &IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_PROPERTYGET, ¶ms, &vResult, NULL, NULL);if (FAILED(hr) || vResult.vt != VT_DISPATCH) {log_message("Error: Failed to access Rows");pRange->lpVtbl->Release(pRange);free(sheetName);pWorksheet->lpVtbl->Release(pWorksheet);continue;}IDispatch* pRows = vResult.pdispVal;szMember = L"Count";hr = pRows->lpVtbl->GetIDsOfNames(pRows, &IID_NULL, &szMember, 1, LOCALE_USER_DEFAULT, &dispid);hr = pRows->lpVtbl->Invoke(pRows, dispid, &IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_PROPERTYGET, ¶ms, &vResult, NULL, NULL);long lastRow = vResult.lVal;pRows->lpVtbl->Release(pRows);log_message("Row count obtained");VariantClear(&vResult);szMember = L"Columns";hr = pRange->lpVtbl->GetIDsOfNames(pRange, &IID_NULL, &szMember, 1, LOCALE_USER_DEFAULT, &dispid);hr = pRange->lpVtbl->Invoke(pRange, dispid, &IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_PROPERTYGET, ¶ms, &vResult, NULL, NULL);IDispatch* pCols = vResult.pdispVal;szMember = L"Count";hr = pCols->lpVtbl->GetIDsOfNames(pCols, &IID_NULL, &szMember, 1, LOCALE_USER_DEFAULT, &dispid);hr = pCols->lpVtbl->Invoke(pCols, dispid, &IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_PROPERTYGET, ¶ms, &vResult, NULL, NULL);long lastCol = vResult.lVal;pCols->lpVtbl->Release(pCols);log_message("Column count obtained");// Create sheet dataJsonValue* sheetData = json_create_object();for (long r = 1; r <= lastRow; r++) {for (long c = 1; c <= lastCol; c++) {// Get cellVariantClear(&vResult);szMember = L"Cells";hr = pWorksheet->lpVtbl->GetIDsOfNames(pWorksheet, &IID_NULL, &szMember, 1, LOCALE_USER_DEFAULT, &dispid);VARIANT vRow, vCol;VariantInit(&vRow);VariantInit(&vCol);vRow.vt = VT_I4;vRow.lVal = r;vCol.vt = VT_I4;vCol.lVal = c;VARIANT args[2] = { vCol, vRow };params.rgvarg = args;params.cArgs = 2;hr = pWorksheet->lpVtbl->Invoke(pWorksheet, dispid, &IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_PROPERTYGET, ¶ms, &vResult, NULL, NULL);if (FAILED(hr) || vResult.vt != VT_DISPATCH) {log_message("Error: Failed to access Cell");continue;}IDispatch* pCell = vResult.pdispVal;// Get cell addressVariantClear(&vResult);szMember = L"Address";hr = pCell->lpVtbl->GetIDsOfNames(pCell, &IID_NULL, &szMember, 1, LOCALE_USER_DEFAULT, &dispid);VARIANT vFalse;VariantInit(&vFalse);vFalse.vt = VT_BOOL;vFalse.boolVal = VARIANT_FALSE;VARIANT argsAddr[2] = { vFalse, vFalse };params.rgvarg = argsAddr;params.cArgs = 2;hr = pCell->lpVtbl->Invoke(pCell, dispid, &IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_PROPERTYGET, ¶ms, &vResult, NULL, NULL);char* cellAddr = ConvertBSTRToString(vResult.bstrVal);SysFreeString(vResult.bstrVal);log_message("Cell address obtained");// Get cell valueVariantClear(&vResult);szMember = L"Value";hr = pCell->lpVtbl->GetIDsOfNames(pCell, &IID_NULL, &szMember, 1, LOCALE_USER_DEFAULT, &dispid);params.rgvarg = NULL;params.cArgs = 0;hr = pCell->lpVtbl->Invoke(pCell, dispid, &IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_PROPERTYGET, ¶ms, &vResult, NULL, NULL);char valueStr[256];if (vResult.vt == VT_EMPTY) {strcpy(valueStr, "");} else if (vResult.vt == VT_R8) {sprintf(valueStr, "%f", vResult.dblVal);json_add_to_object(sheetData, cellAddr, json_create_number(vResult.dblVal));} else if (vResult.vt == VT_BSTR) {char* tmp = ConvertBSTRToString(vResult.bstrVal);strncpy(valueStr, tmp, sizeof(valueStr) - 1);valueStr[sizeof(valueStr) - 1] = '\0';json_add_to_object(sheetData, cellAddr, json_create_string(tmp));free(tmp);} else {strcpy(valueStr, "");}log_message("Cell value obtained");free(cellAddr);pCell->lpVtbl->Release(pCell);}}json_add_to_object(sheets, sheetName, sheetData);free(sheetName);pRange->lpVtbl->Release(pRange);pWorksheet->lpVtbl->Release(pWorksheet);log_message("Sheet processed");}// Create payloadJsonValue* sts = json_create_object();json_add_to_object(sts, "Sheets", sheets);JsonValue* payload = json_create_object();json_add_to_object(payload, "workbook_snapshot", sts);jsonPayload = json_to_string(payload);log_message("JSON payload created");// Send HTTP requestchar url[512];char* routerStr = ConvertBSTRToString(router);snprintf(url, sizeof(url), "%s/%s", BACKEND_URL, routerStr);free(routerStr);log_message("Sending HTTP request");if (!http_post(url, jsonPayload, &response)) {log_message("Error: HTTP request failed");result = SysAllocString(L"Error: HTTP request failed");goto cleanup;}log_message("HTTP response received");// Parse responseJsonValue* jsonResponse = json_parse(response.memory);if (!jsonResponse) {log_message("Error: Failed to parse JSON response");result = SysAllocString(L"Error: Failed to parse JSON response");goto cleanup;}log_message("JSON response parsed");// Process workbook snapshotJsonValue* workbookSnapshot = json_get_object_item(jsonResponse, "workbook_snapshot");if (workbookSnapshot) {JsonValue* sheets = json_get_object_item(workbookSnapshot, "Sheets");if (sheets && sheets->type == JSON_OBJECT) {JsonValue* sheet = sheets->value.pair.value;while (sheet) {char* sheetName = sheet->value.pair.key;JsonValue* sheetData = sheet->value.pair.value;IDispatch* pWorksheet = NULL;VariantClear(&vResult);szMember = L"Worksheets";hr = pWorkbook->lpVtbl->GetIDsOfNames(pWorkbook, &IID_NULL, &szMember, 1, LOCALE_USER_DEFAULT, &dispid);hr = pWorkbook->lpVtbl->Invoke(pWorkbook, dispid, &IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_PROPERTYGET, ¶ms, &vResult, NULL, NULL);pWorksheets = vResult.pdispVal;log_message("Worksheets for update accessed");VARIANT vSheetName;VariantInit(&vSheetName);vSheetName.vt = VT_BSTR;vSheetName.bstrVal = ConvertStringToBSTR(sheetName);params.rgvarg = &vSheetName;params.cArgs = 1;szMember = L"Item";hr = pWorksheets->lpVtbl->GetIDsOfNames(pWorksheets, &IID_NULL, &szMember, 1, LOCALE_USER_DEFAULT, &dispid);hr = pWorksheets->lpVtbl->Invoke(pWorksheets, dispid, &IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_PROPERTYGET, ¶ms, &vResult, NULL, NULL);SysFreeString(vSheetName.bstrVal);if (FAILED(hr) || vResult.vt != VT_DISPATCH) {log_message("Error: Failed to access Worksheet for update");pWorksheets->lpVtbl->Release(pWorksheets);sheet = sheet->next;continue;}pWorksheet = vResult.pdispVal;pWorksheets->lpVtbl->Release(pWorksheets);log_message("Worksheet for update accessed");// Update cellsJsonValue* cell = sheetData->value.pair.value;while (cell) {char* cellKey = cell->value.pair.key;JsonValue* cellValue = cell->value.pair.value;VariantClear(&vResult);szMember = L"Range";hr = pWorksheet->lpVtbl->GetIDsOfNames(pWorksheet, &IID_NULL, &szMember, 1, LOCALE_USER_DEFAULT, &dispid);VARIANT vCellKey;VariantInit(&vCellKey);vCellKey.vt = VT_BSTR;vCellKey.bstrVal = ConvertStringToBSTR(cellKey);params.rgvarg = &vCellKey;params.cArgs = 1;hr = pWorksheet->lpVtbl->Invoke(pWorksheet, dispid, &IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_PROPERTYGET, ¶ms, &vResult, NULL, NULL);SysFreeString(vCellKey.bstrVal);if (FAILED(hr) || vResult.vt != VT_DISPATCH) {log_message("Error: Failed to access Cell for update");cell = cell->next;continue;}IDispatch* pCell = vResult.pdispVal;// Check for formulaVariantClear(&vResult);szMember = L"Formula";hr = pCell->lpVtbl->GetIDsOfNames(pCell, &IID_NULL, &szMember, 1, LOCALE_USER_DEFAULT, &dispid);hr = pCell->lpVtbl->Invoke(pCell, dispid, &IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_PROPERTYGET, ¶ms, &vResult, NULL, NULL);if (vResult.vt == VT_BSTR && vResult.bstrVal[0] == L'=') {log_message("Skipping cell with formula");pCell->lpVtbl->Release(pCell);cell = cell->next;continue;}// Set valueszMember = L"Value";hr = pCell->lpVtbl->GetIDsOfNames(pCell, &IID_NULL, &szMember, 1, LOCALE_USER_DEFAULT, &dispid);VARIANT vValue;VariantInit(&vValue);if (cellValue->type == JSON_NUMBER) {vValue.vt = VT_R8;vValue.dblVal = cellValue->value.number;} else if (cellValue->type == JSON_STRING) {vValue.vt = VT_BSTR;vValue.bstrVal = ConvertStringToBSTR(cellValue->value.string);} else {vValue.vt = VT_EMPTY;}params.rgvarg = &vValue;params.cArgs = 1;hr = pCell->lpVtbl->Invoke(pCell, dispid, &IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_PROPERTYPUT, ¶ms, NULL, NULL, NULL);SysFreeString(vValue.bstrVal);pCell->lpVtbl->Release(pCell);log_message("Cell updated");cell = cell->next;}pWorksheet->lpVtbl->Release(pWorksheet);log_message("Sheet updated");sheet = sheet->next;}}}result = SysAllocString(L"Success");log_message("Success");cleanup:if (response.memory) free(response.memory);if (jsonPayload) free(jsonPayload);if (jsonResponse) json_free(jsonResponse);if (payload) json_free(payload);if (pWorksheets) pWorksheets->lpVtbl->Release(pWorksheets);if (pWorkbook) pWorkbook->lpVtbl->Release(pWorkbook);if (pExcel) pExcel->lpVtbl->Release(pExcel);VariantClear(&vResult);CoUninitialize();log_message("Cleanup completed");return result;
}// DLL entry point
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {switch (ul_reason_for_call) {case DLL_PROCESS_ATTACH:case DLL_THREAD_ATTACH:case DLL_THREAD_DETACH:case DLL_PROCESS_DETACH:break;}return TRUE;
}
编译为dll文件
D:\mingw32\bin\gcc.exe -shared -o D:\userData\dll\excel_snapshot_32.dll .\excel_snapshot.c -lole32 -loleaut32 -lwininet -luuid "-Wl,--out-implib,D:\userData\dll\excel_snapshot_32.lib" > compile.log 2>&1
-
-shared:- 这个选项告诉
gcc创建一个共享库(DLL)。这是生成 DLL 文件所必需的。
- 这个选项告诉
-
-o D:\userData\dll\excel_snapshot_32.dll:-o选项后面跟着的是输出文件的名称和路径。这里指定了生成的 DLL 文件名为excel_snapshot_32.dll,并将其放在D:\userData\dll\目录下。
-
.\excel_snapshot.c:- 这是要编译的 C 源文件。相对路径表示当前目录下的
excel_snapshot.c文件。
- 这是要编译的 C 源文件。相对路径表示当前目录下的
-
-lole32 -loleaut32 -lwininet -luuid:- 这些是链接库的选项:
-lole32: 链接 OLE (Object Linking and Embedding) 库。-loleaut32: 链接 OLE 自动化库。-lwininet: 链接 Windows Internet API 库。-luuid: 链接 UUID(通用唯一标识符)库,用于 COM 编程。
- 这些是链接库的选项:
-
"-Wl,--out-implib,D:\userData\deng\dll\excel_snapshot_32.lib":-Wl,表示将后面的选项传递给链接器。--out-implib,D:\userData\deng\dll\excel_snapshot_32.lib指定生成一个导入库(.lib文件),该库可以用于其他项目中链接到此 DLL。
VBA 里添加对dll主函数的调用
Private Declare PtrSafe Function SendWorkbookSnapshotToBackend Lib "D:\userData\dll\excel_snapshot_32.dll" (ByVal router As LongPtr) As LongPtrSub CallDLL(router As String)On Error GoTo ErrorHandlerDim result As LongPtrIf Len(Trim(router)) = 0 ThenMsgBox "Error: Router parameter cannot be empty.", vbCriticalExit SubEnd IfCall SendWorkbookSnapshotToBackend(StrPtr(router))Exit SubErrorHandler:MsgBox "Error " & Err.Number & ": " & Err.Description, vbCritical
End Sub
尝试调用了下报错
Can't find DLL entry point SendWorkbookSnapshotToBackend in D:\userData\deng\dll\excel_snapshot_32.dll
说明 VBA 无法在 DLL 中找到名为 SendWorkbookSnapshotToBackend 的导出函数。这通常有几个常见原因,逐个排查下
在 objdump 输出中看到的是:

这是 标准的 Windows StdCall 函数修饰符格式,说明我的 DLL 使用了 __stdcall 调用约定,函数被导出为 SendWorkbookSnapshotToBackend@4
而 VBA 默认寻找不带修饰符的函数名,比如 SendWorkbookSnapshotToBackend。
解决方案 :用别名指定导出名
修改 Declare 语句,加上别名匹配导出名
Private Declare PtrSafe Function SendWorkbookSnapshotToBackend Lib "D:\userData\dll\excel_snapshot_32.dll" Alias "SendWorkbookSnapshotToBackend@4" (ByVal router As LongPtr) As LongPtr
执行成功,excel上的单元格逻辑执行了, 效率只能说稍微强了那么一丢丢
相关文章:
VBA 调用 dll 优化执行效率
问题描述 之前excel 用vba写过一个应用,请求的是aws lambda 后端, 但是受限于是云端服务,用起来响应特别慢,最近抽了点时间准备优化下,先加了点日志看看是哪里慢了 主方法代码如下,函数的主要目的是将 Excel 工作簿的…...
【机器学习-周总结】-第4周
以下是本周学习内容的整理总结,从技术学习、实战应用到科研辅助技能三个方面归纳: 文章目录 📘 一、技术学习模块:TCN 基础知识与结构理解🔹 博客1:【时序预测05】– TCN(Temporal Convolutiona…...
Django-Friendship 项目常见问题解决方案
Django-Friendship 项目常见问题解决方案 django-friendship Django app to manage following and bi-directional friendships 项目地址: https://gitcode.com/gh_mirrors/dj/django-friendship Django-Friendship 是一个基于 Django 的应用,它允许创建和管…...
C语言用if else求三个数最小值的一题多解
一、问题引入 假设x,y,z为整数,使用if else语句求x,y,z三个数中的最小值? 二、三种解法 第一种解法: #include<stdio.h> int main(){int x,y,z,min;printf("请输入三个整数:");scanf_s("%d %d %d", &x, &y, &z);//初始值…...
AI时代下 你需要和想要了解的英文缩写含义
在AI智能时代下,越来愈多的企业都开始重视并应用以及开发AI相关产品,这个时候都会或多或少的涉及到英文,英文还好,但是如果是缩写,如果我们没有提前了解过,我们往往很难以快速Get到对方的意思。在这里&…...
uniApp小程序保存定制二维码到本地(V3)
这里的二维码组件用的 uv-ui 的二维码 可以按需引入 QRCode 二维码 | 我的资料管理-uv-ui 是全面兼容vue32、nvue、app、h5、小程序等多端的uni-app生态框架 <uv-qrcode ref"qrcode" :size"280" :value"payCodeUrl"></uv-qrcode>&l…...
2025年对讲机选购指南:聚焦核心参数与场景适配
在无线通信领域,对讲机始终占据着专业通讯工具的独特地位。随着5G时代到来和物联网技术深化,2025年的对讲机市场正呈现智能化、专业化、场景化的升级趋势。面对琳琅满目的产品,选购者需从通信性能、环境适应性、智能集成度三个维度进行综合考…...
C/C++ 动态链接详细解读
1. 为什么要动态链接? 1.1 静态链接浪费内存和磁盘空间 静态链接的方式对于计算机内存和磁盘空间浪费非常严重,特别是多进程操作系统的情况下,静态链接极大的浪费了内存空间。在现在的Linux系统中,一个普通的程序会使用的C 语言静…...
力扣-hot100(无重复字符的最长子串)
3. 无重复字符的最长子串 中等 给定一个字符串 s ,请你找出其中不含有重复字符的 最长 子串 的长度。 示例 1: 输入: s "abcabcbb" 输出: 3 解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。暴力直观解法一࿱…...
python flask 项目部署
文章目录 概述 windows 部署准备工作使用 Waitress 部署 Flask 应用 linux 部署**2. 使用 WSGI 服务器**示例:使用 Gunicorn nginx反向代理**5. 使用进程管理工具**示例:使用 Systemd 概述 在 Windows 上使用 Waitress 部署 Flask 应用是一个不错的选择…...
Java课程内容大纲(附重点与考试方向)
本文是在传统 Java 教程框架基础上,加入了重点提示与考试思路,适合用于课程备考、知识查漏与面试准备。 第1章:Java语言基础 ⭐ 重点知识: Java平台特点(跨平台性、JVM) JDK、JRE、JVM 区别 Java 程序的…...
实现AWS Lambda函数安全地请求企业内部API返回数据
需要编写一个Lambda函数在AWS云上运行,它需要访问企业内部的API获取JSON格式的数据,企业有网关和防火墙,API有公司的okta身份认证,通过公司的域账号来授权访问,现在需要创建一个专用的域账号,让Lambda函数访…...
面试题--随机(一)
MySQL事务中的ACID特性? A 原子性 事务是一组SQL语句,不可分割 C 一致性 事务中的SQL语句要么同时执行,即全部执行成功,要么全部不执行,即执行失败 I 隔离性 MySQL中的各个事务通过不同的事务隔离等级,产生…...
200+短剧出海平台:谁能成为“海外红果”?
2025年,短剧的国际市场表现令人瞩目。仅在两年前,业界关注的焦点仍是美国市场,如今国产短剧应用已成功打入包括印尼、巴西、美国、墨西哥、印度、菲律宾、泰国、日本、哥伦比亚及韩国在内的多个国家,轻松获得超过500万次下载。 市…...
Visio导出清晰图片步骤
在Visio里画完图之后如何导出清晰的图片?👇 ①左上角单击【文件】 ②导出—更改文件类型—PNG/JPG ③分辨率选择【打印机】,大小选择【源】,即可。 ④选择保存位置并命名 也可以根据自己需要选择是否需要【透明底】哈。 选PNG 然…...
Linux系统:详解进程等待wait与waitpid解决僵尸进程
本节重点 理解进程等待的相关概念掌握系统调用wait与waitpid的使用方法输出型status参数的存储结构阻塞等待与非阻塞等待 一、概念 进程等待是操作系统中父进程与子进程协作的核心机制,指父进程通过特定方式等待子进程终止并回收其资源的过程。这一机制的主要目的…...
6.7 ChatGPT自动生成定时任务脚本:Python与Cron双方案实战指南
ChatGPT自动生成定时任务脚本:Python与Cron双方案实战指南 关键词:定时任务调度, ChatGPT 代码生成, Cron 脚本开发, Python 调度器, 自动化更新系统 6.3 使用 ChatGPT 生成 Cron 调度脚本 在 GitHub Sentinel 的定期更新功能中,定时任务调度是核心模块。本节演示如何通过…...
K8S运维实战之集群证书升级与容器运行时更换全记录
第一部分:Kubernetes集群证书升级实战 tips:此博文只演示一个节点作为示范,所有的集群节点步骤都可以参考。 项目背景 某金融业务系统Kubernetes集群即将面临生产证书集中过期风险(核心组件证书剩余有效期不足90天),…...
IntelliJ IDEA clean git password
IntelliJ IDEA clean git password 清除git密码 方法一:(这个要特别注意啊,恢复默认设置,你的插件什么要重新下载了) File->Manage IDE Settings->Restore Default Settings以恢复IDEA的默认设置(可选); 清空…...
【已更新完毕】2025泰迪杯数据挖掘竞赛C题数学建模思路代码文章教学:竞赛智能客服机器人构建
完整内容请看文末最后的推广群 基于大模型的竞赛智能客服机器人构建 摘要 随着国内学科和技能竞赛的增多,参赛者对竞赛相关信息的需求不断上升,但传统人工客服存在效率低、成本高、服务不稳定和用户体验差的问题。因此,设计一款智能客服机器…...
2025年4月19日 记录大模型出现的计算问题
2025年4月19日 记录大模型出现的计算问题,用了四个大模型计算json的数值,3个错误,1个正确 问题 Class Train Val answer 2574 853 screen 5025 1959 blackBoard 7847 3445 teacher 8490 3228 stand…...
ACI EP Learning Whitepaper 3. Disabling IP Data-plane Learning 功能
目录 1. 使用场景 1.1 未disable IP data-plane learning时 1.2 disable IP data-plane learning后 2. 一代Leaf注意事项 3. L2 未知单播注意事项 1. 使用场景 Windows网卡的动态负载均衡绑定模式等。或多个设备共享相同VIP并通过ARP/GARP/ND来宣告VIP切换时,这些外部设…...
C++入门七式——模板初阶
目录 函数模板 函数模板概念 函数模板格式 函数模板的原理 函数模板的实例化 模板参数的匹配原则 类模板 类模板的定义格式 类模板的显式实例化 当面对下面的代码时,大家会不会有一种无力的感觉?明明这些代码差不多,只是因为类型不…...
计算机网络 - 在浏览器中输入 URL 地址到显示主页的过程?
第一步,浏览器通过 DNS 来解析 URL,得到相应的 ip 地址(到哪里找) 和 方法(做什么) 第二步,浏览器于服务器建立 TCP 三次握手连接 第三步,建立好连接后,浏览器会组装 HTTP 请求报文…...
【教程】检查RDMA网卡状态和测试带宽 | 附测试脚本
转载请注明出处:小锋学长生活大爆炸[xfxuezhagn.cn] 如果本文帮助到了你,欢迎[点赞、收藏、关注]哦~ 目录 检查硬件和驱动状态 测试RDMA通信 报错修复 对于交换机的配置,可以看这篇: 【教程】详解配置多台主机通过交换机实现互…...
(二)Trae 配置C++ 编译
Trae配置c编译 零 CMake 编译C0.1 下载安装0.2 安装设置0.3 三种编译方式(见 下文 一 二 三)0.4 调试 (见 下文四) 一 使用MSVC方式编译1.1 安装编译环境1.2安装插件1.3 设置文件 二 使用GCC方式2.1 安装编译环境2.1.1下载:[MinGw](https://gcc-mcf.lhmouse.com/)2.1.2安装:(以…...
Doris 本地部署集群重启后报错
报错描述 Docker 版本: apache/doris:fe-2.1.9 apache/doris:be-2.1.9 连接 MySQL 报错: ERROR 2003 (HY000): Cant connect to MySQL server on 127.0.0.1:9030 (111)FE 日志: INFO (UNKNOWN fe_e7cff187_69d4_42ee_90be_147e87310549(-1…...
日本公司如何实现B2B商城订货系统的自动化和个性化?
在日本构建具备前后台日文本地化、业务员代客下单、一客一价、智能拆单发货的B2B电商系统,需结合日本商业习惯与技术实现。以下是关键模块的落地方案: 一、系统架构设计 1. 前端本地化 语言与UI适配 采用全日语界面,包含敬语体系(…...
自动化测试相关协议深度剖析及A2A、MCP协议自动化测试应用展望
一、不同协议底层逻辑关联分析 1. OPENAPI协议 OPENAPI 协议核心在于定义 API 的规范结构,它使用 YAML 或 JSON 格式来描述 API 的端点、请求参数、响应格式等信息。其底层逻辑是构建一个清晰、标准化的 API 描述文档,方便不同的客户端和服务端进行对接…...
ReAct、CoT 和 ToT:大模型提示词推理架构的对比分析
ReAct、CoT 和 ToT:大模型提示词推理架构的对比分析 在大型语言模型(LLM)的研究与应用中,如何有效提升模型在复杂任务上的推理能力是关键问题之一。目前,ReAct(Reasoning and Acting)、CoT&…...
