Một lỗi rất khó chịu của VBA khi đọc chuỗi Unicode (1 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
202
Được thích
178
Không biết mọi người có hay gặp trường hợp này không: đọc chuỗi Unicode xong, khi xuất ra kết quả thì chuỗi bị vỡ hoặc bị chèn thêm ký tự ngoằn ngoèo vô nghĩa. Tình trạng này rất hay gặp khi làm việc với đối tượng COM.
VD: Giả sử cần gọi web API để lấy kết quả là JSON nhờ sử dụng thư viện COM của WinHTTP.
Với VBA,
Mã:
Private Sub TestAPI()
    Dim objWinHttp As WinHttp.WinHttpRequest
    Set objWinHttp = New WinHttp.WinHttpRequest
    With objWinHttp
        .Open "GET", "https://iq.vietcap.com.vn/api/iq-insight-service/v1/events?ticker=GMD&fromDate=20160328&toDate=20260328&eventCode=DIV,ISS&page=0&size=250", True
        .SetRequestHeader "Accept", "application/json"
        .SetRequestHeader "Referer", "iq.vietcap.com.vn"
        .Send
        If .WaitForResponse(5) Then
            Debug.Print .ResponseText
            Dim objFSO As Scripting.FileSystemObject
            Set objFSO = New Scripting.FileSystemObject
            Dim objFile As Scripting.TextStream
            Set objFile = objFSO.CreateTextFile("Z:\response.json", True, True)
            objFile.Write .ResponseText
            objFile.Close
            Debug.Print "Response has been written to disk"
        Else
            Debug.Print "Request timed out"
        End If
    End With
End Sub
kết quả là nội dung JSON cứ chỗ nào có ký tự Unicode là chỗ đó bị lỗi.
JSON:
{
    "id": "68c0c56c6a7bab4d8b740a07",
    "organCode": "GMD",
    "eventNameVi": "Phát hà nh cỠphiếu",
    "eventNameEn": "Share Issue",
    "organNameEn": "Gemadept Corporation",
    "organNameVi": "Công ty CỠphần Gemadept",
    "ticker": "GMD",
    "eventCode": "ISS",
    "eventTitleVi": "Phát hà nh cỠphiếu - Phát hà nh cho CBCNV tỠlỠ1.5%",
    "eventTitleEn": "Share Issue - ESOP ratio 1.5%",
    "displayDate1": "2025-10-02T00:00:00",
    "displayDate2": "2025-10-06T00:00:00",
    "publicDate": "2025-10-06T00:00:00",
    "recordDate": "2025-10-02T00:00:00",
    "exrightDate": "2025-10-02T00:00:00",
    "listingDate": "2028-10-02T00:00:00",
    "exerciseRatio": 0.015,
    "category": "DIVIDEND"
}
Tương tự, nhưng lần này là C++.
C++:
#include <stdio.h>
#import <winhttpcom.dll> raw_interfaces_only
#pragma comment(lib, "winhttp.lib")
using namespace WinHttp;

int main()
{
    HRESULT hr = CoInitialize(NULL);
    if (FAILED(hr)) {
        wprintf(L"CoInitialize failed: %i\n", hr);
        return hr;
    }
    CLSID clsid = {};
    hr = CLSIDFromProgID(OLESTR("WinHttp.WinHttpRequest.5.1"), &clsid);
    if (FAILED(hr)) {
        CoUninitialize();
        wprintf(L"CLSIDFromProgID failed: %i\n", hr);
        return hr;
    }
    IWinHttpRequest* pRequest = NULL;
    hr = CoCreateInstance(clsid, NULL, CLSCTX_INPROC_SERVER, __uuidof(IWinHttpRequest), reinterpret_cast<LPVOID*>(&pRequest));
    if (FAILED(hr)) {
        CoUninitialize();
        wprintf(L"CoCreateInstance failed: %i\n", hr);
        return hr;
    }
    BSTR bstrUrl = SysAllocString(OLESTR("https://iq.vietcap.com.vn/api/iq-insight-service/v1/events?ticker=GMD&fromDate=20160328&toDate=20260328&eventCode=DIV,ISS&page=0&size=250"));
    if (!bstrUrl) {
        CoUninitialize();
        wprintf(L"Out of memory\n");
        pRequest->Release();
        return 1;
    }
    BSTR bstrMethod = SysAllocString(OLESTR("GET"));
    if (!bstrMethod) {
        CoUninitialize();
        wprintf(L"Out of memory\n");
        SysFreeString(bstrUrl);
        pRequest->Release();
        return 1;
    }
    VARIANT varAsync = {};
    VariantInit(&varAsync);
    varAsync.vt = VT_BOOL;
    varAsync.boolVal = VARIANT_TRUE;
    hr = pRequest->Open(bstrMethod, bstrUrl, varAsync);
    VariantClear(&varAsync);
    SysFreeString(bstrMethod);
    bstrMethod = NULL;
    SysFreeString(bstrUrl);
    bstrUrl = NULL;
    if (FAILED(hr)) {
        CoUninitialize();
        pRequest->Release();
        wprintf(L"'Open' method failed: %i\n", hr);
        return hr;
    }
    BSTR bstrAcceptHeader = SysAllocString(OLESTR("Accept"));
    if (!bstrAcceptHeader) {
        wprintf(L"Out of memory\n");
        pRequest->Release();
        CoUninitialize();
        return 1;
    }
    BSTR bstrAcceptHeaderValue = SysAllocString(OLESTR("application/json"));
    if (!bstrAcceptHeaderValue) {
        wprintf(L"Out of memory\n");
        SysFreeString(bstrAcceptHeader);
        pRequest->Release();
        CoUninitialize();
        return 1;
    }
    hr = pRequest->SetRequestHeader(bstrAcceptHeader, bstrAcceptHeaderValue);
    SysFreeString(bstrAcceptHeader);
    bstrAcceptHeader = NULL;
    SysFreeString(bstrAcceptHeaderValue);
    bstrAcceptHeaderValue = NULL;
    if (FAILED(hr)) {
        wprintf(L"'SetRequestHeader' method failed: %i\n", hr);
        pRequest->Release();
        CoUninitialize();
        return hr;
    }
    BSTR bstrRefererHeader = SysAllocString(OLESTR("Referer"));
    if (!bstrRefererHeader) {
        wprintf(L"Out of memory\n");
        pRequest->Release();
        CoUninitialize();
        return 1;
    }
    BSTR bstrRefererHeaderValue = SysAllocString(OLESTR("iq.vietcap.com.vn"));
    if (!bstrRefererHeaderValue) {
        wprintf(L"Out of memory\n");
        SysFreeString(bstrRefererHeader);
        pRequest->Release();
        CoUninitialize();
        return 1;
    }
    hr = pRequest->SetRequestHeader(bstrRefererHeader, bstrRefererHeaderValue);
    SysFreeString(bstrRefererHeader);
    bstrRefererHeader = NULL;
    SysFreeString(bstrRefererHeaderValue);
    bstrRefererHeaderValue = NULL;
    if (FAILED(hr)) {
        wprintf(L"'SetRequestHeader' method failed: %i\n", hr);
        pRequest->Release();
        CoUninitialize();
        return hr;
    }
    hr = pRequest->Send();
    if (FAILED(hr)) {
        wprintf(L"'Send' method failed: %i\n", hr);
        pRequest->Release();
        CoUninitialize();
        return hr;
    }
    VARIANT varTimeOut = {};
    VariantInit(&varTimeOut);
    varTimeOut.vt = VT_I4;
    varTimeOut.lVal = 5;
    VARIANT_BOOL varfWaitSucceeded = VARIANT_FALSE;
    hr = pRequest->WaitForResponse(varTimeOut, &varfWaitSucceeded);
    VariantClear(&varTimeOut);
    if (FAILED(hr)) {
        wprintf(L"'WaitForResponse' method failed: %i\n", hr);
        pRequest->Release();
        CoUninitialize();
        return hr;
    }
    if (varfWaitSucceeded == VARIANT_FALSE) {
        wprintf(L"Request timed out\n");
        pRequest->Release();
        CoUninitialize();
        return 2;
    }
    long lStatus = 0;
    hr = pRequest->get_Status(&lStatus);
    if (FAILED(hr)) {
        wprintf(L"'get_Status' method failed: %i\n", hr);
        pRequest->Release();
        CoUninitialize();
        return hr;
    }
    if (lStatus != 200) {
        wprintf(L"Status code: %i\nThe server sent an invalid response\n", lStatus);
        pRequest->Release();
        CoUninitialize();
        return 0;
    }
    BSTR bstrResponse = NULL;
    hr = pRequest->get_ResponseText(&bstrResponse);
    if (FAILED(hr)) {
        wprintf(L"'get_ResponseText' method failed: %i\n", hr);
        pRequest->Release();
        CoUninitialize();
        return hr;
    }
    wprintf(L"Response from the server:\n%s\n", bstrResponse);
    SysFreeString(bstrResponse);
    bstrResponse = NULL;
    IStream* pStream = NULL;
    VARIANT varResponseBody = {};
    VariantInit(&varResponseBody);
    hr = pRequest->get_ResponseStream(&varResponseBody);
    if (FAILED(hr)) {
        wprintf(L"'get_ResponseStream' method failed: %i\n", hr);
        pRequest->Release();
        CoUninitialize();
        return hr;
    }
    if (varResponseBody.vt & VT_STREAM || varResponseBody.vt & VT_UNKNOWN) {
        hr = varResponseBody.punkVal->QueryInterface(IID_IStream, reinterpret_cast<LPVOID*>(&pStream));
        VariantClear(&varResponseBody);
        if (FAILED(hr)) {
            wprintf(L"'QueryInterface' method failed: %i\n", hr);
            pRequest->Release();
            CoUninitialize();
            return hr;
        }
        HANDLE hFile = CreateFile(L"Z:\\response_c++.json", GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
        DWORD dwError = GetLastError();
        if (INVALID_HANDLE_VALUE == hFile) {
            wprintf(L"CreateFile failed: %lu\n", dwError);
            pStream->Release();
            pRequest->Release();
            CoUninitialize();
            return (int)dwError;
        }
        DWORD dwBytesRead = 0;
        LPVOID lpvBuffer[8192] = {};
        do {
            hr = pStream->Read(lpvBuffer, 8192, &dwBytesRead);
            if (FAILED(hr)) {
                wprintf(L"'Read' method of IStream failed: %i\n", hr);
                CloseHandle(hFile);
                pStream->Release();
                pRequest->Release();
                CoUninitialize();
                return (int)dwError;
            }
            if (!WriteFile(hFile, lpvBuffer, dwBytesRead, NULL, NULL)) {
                dwError = GetLastError();
                wprintf(L"WriteFile failed %lu\n", dwError);
                CloseHandle(hFile);
                pStream->Release();
                pRequest->Release();
                CoUninitialize();
                return (int)dwError;
            }
            memset(lpvBuffer, 0, 8192);
        } while (dwBytesRead);
        pStream->Release();
        CloseHandle(hFile);
        wprintf(L"Response has been written to disk\n");
    }
    pRequest->Release();
    CoUninitialize();
    return 0;
}
Kết quả trả về thì rất tốt, không vấn đề gì.
JSON:
            {
                "id": "68c0c56c6a7bab4d8b740a07",
                "organCode": "GMD",
                "eventNameVi": "Phát hành cổ phiếu",
                "eventNameEn": "Share Issue",
                "organNameEn": "Gemadept Corporation",
                "organNameVi": "Công ty Cổ phần Gemadept",
                "ticker": "GMD",
                "eventCode": "ISS",
                "eventTitleVi": "Phát hành cổ phiếu - Phát hành cho CBCNV tỉ lệ 1.5%",
                "eventTitleEn": "Share Issue - ESOP ratio 1.5%",
                "displayDate1": "2025-10-02T00:00:00",
                "displayDate2": "2025-10-06T00:00:00",
                "publicDate": "2025-10-06T00:00:00",
                "recordDate": "2025-10-02T00:00:00",
                "exrightDate": "2025-10-02T00:00:00",
                "listingDate": "2028-10-02T00:00:00",
                "exerciseRatio": 0.015,
                "category": "DIVIDEND"
            }
Nhiều khả năng đây là lỗi (bug) của VBA rồi.
 
Chuyên gia code lại không nhận ra chuỗi UTF8, thấy cũng lạ lạ. UTF8 là chuẩn toàn cầu mã hóa cho unicode. Trong VBA có 3 cách chuyển thành Unicode:

1. MultiByteToWideChar
2. Adodb Stream
3. MSXML2.DomDocument

Lý do server web không trả về Unicode trực tiếp là vì Unicode có thể bị lợi dụng để tấn công.

Nếu lấy dữ liệu chứng khoán thì tham khảo XStock, XFiin tôi đã phát triển cách đây nhiều năm.
Thắm thoát đã 10 năm.
 
Upvote 0
Chuyên gia code lại không nhận ra chuỗi UTF8, thấy cũng lạ lạ. UTF8 là chuẩn toàn cầu mã hóa cho unicode. Trong VBA có 3 cách chuyển thành Unicode:

1. MultiByteToWideChar
2. Adodb Stream
3. MSXML2.DomDocument

Lý do server web không trả về Unicode trực tiếp là vì Unicode có thể bị lợi dụng để tấn công.

Nếu lấy dữ liệu chứng khoán thì tham khảo XStock, XFiin tôi đã phát triển cách đây nhiều năm.
Thắm thoát đã 10 năm.
Vậy chuyên gia có biết unicode ở đây là gì không, kiểu chuỗi của VBA thực chất là kiểu gì? Tại sao khi làm việc với cùng một thư viện COM của WinHTTP thì VBA bị lỗi chuỗi còn C++ không bị gì.
Đề nghị chuyên gia đọc kỹ lại tài liệu về WinHTTP trước khi nói: WinHttpRequest Object Reference
 
Upvote 0

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

Back
Top Bottom