Undocument Windows API và VBA (1 người xem)

Liên hệ QC

Người dùng đang xem chủ đề này

ThangCuAnh

Mới rờ Ét xeo
Tham gia
1/12/17
Bài viết
896
Được thích
793
Giới tính
Nam
Nghề nghiệp
Coder nghỉ hưu, RCE dạo
Cái laptop hư lâu lắc, phải gởi vào thành hồ chứa mưa sữa, mới lấy về.
Nên quay lại tiếp với cái gọi là "rờ chxx em" Windows và VBA.
Topic này tui sẽ đăng lần lượt những gì cu anh tui phát hiện ra trong quá trình "rờ em" Windows API, DLLs và VBAxxx.dll. Các tips, tricks này sẽ bảo đảm không có trên ông "Gấu gồ". Và dùng được cho VBA trên Offices. Chứ "ưn đồ cú mèn" API mà chỉ dùng được cho C/C++, Delphi... thì dân tin học VP ở đây thua.
Tui chỉ sẽ tập trung ở các Windows DLL sau: kernel32.dll, shell32.dll, shlwapi.dll, oleaut32.dll, ole32.dll, advapi32.dll... và 1 ít từ ntdll.dll (core usermode API của Windows). Trên VBExxx.dll thì tui chỉ tập trung vào VBE6 của Office 2007, VBE7 của Office2010 32 và 64bit, các VBExxx.dll khác cũng sẽ gần như tương đượng, không khác nhau mấy.
 
Lần chỉnh sửa cuối:
Đầu tiên, theo thứ tự export từ VBExxx.dll, chúng ta sẽ tìm hiểu về các hàm Space$, Space, Str$ và Str trong VBA
Hàm Space$ = BSTR __stdcall rtcSpaceBstr(int nLen) trong VBExxx.dll
Hàm Space = VARIANTARG *__stdcall rtcSpaceVar(VARIANTARG *v, int nLen) trong VBExxx.dll
Hàm String$ = rtcStringBstr trong VBExxx.dll
Hàm String = rtcStringVar trong VBExxx.dll
 
Lần chỉnh sửa cuối:
Upvote 0
MẠNH đặt 1 vé ghế số 1 hóng Sư Phụ Úp bài ;)
 
Upvote 0
Ăn đốc kiểu mặn tít chứ!
 
Upvote 0
Phiền admin xóa giùm topic này, vì nó nhiều tiểu tiết quá, phải chụp hình, post code, giải thích rất nhiều. Nên thôi. Để dịp khác, nhả tơ từ từ thôi.
Giờ quay lại cho xong cái patch VBExxx.dll đã. Chứ chưa cái nào xong cái nào.
 
Upvote 0
Phiền admin xóa giùm topic này, vì nó nhiều tiểu tiết quá, phải chụp hình, post code, giải thích rất nhiều. Nên thôi. Để dịp khác, nhả tơ từ từ thôi.
Giờ quay lại cho xong cái patch VBExxx.dll đã. Chứ chưa cái nào xong cái nào.

Em nhờ bác tìm giúp cái hàm TypeName() nhé?
 
Upvote 0
Hôm nay mình tự nhiên bệnh rồi, sáng giờ nhức đầu nóng lạnh. Tối qua nấu nước nóng tắm lại bị phỏng tay nữa chứ. Giờ nó rộp bộng nước lên.
Uhm, VarType với TypeName thì có ngay bạn Tuân à, mà code C API nhé.
 
Upvote 0
Bác Tuân muốn trên 32 hay 64bit ?
Nhờ các bác test giùm cu anh tui file này, xem giới hạn của SysAllocStringLen API trên Windows và máy các bác là bao nhiêu.
Theo lý thuyết, MS document thì trên 32bit là MAX_UINT = 0xFFFFFFFF nhưng hoàn toàn không phải vậy, nhỏ hơn nhiều.
Có 2 sub Test_xxx, các bạn chạy và chịu khó chờ xem ra kết quả bao nhiêu nhé. Máy càng mạnh, càng bộ nhớ nhiều càng tốt.
 

File đính kèm

Lần chỉnh sửa cuối:
Upvote 0
Cùng 1 code nhưng do compile trên VC++ trên 32bit và 64bit, compiler nó sinh mã khác nhau đôi chút bác. À mà hàm rtcTypeName phức tạp lắm bác Tuân à, dài khủng khiếp. Bác xem chi vậy ?
Hàm VarType của VBA = rtcVarType
Mã:
int __stdcall rtcVarType(VARIANT *pv)
{
    return (unsigned __int16) GetVarType(pv);
}

__int16 __stdcall GetVarType(VARIANT *pv)
{
    VARTYPE l_vt; // si@3
    LONG lVal; // ecx@4
    int nRet; // edi@7
    int vt; // eax@1
    char excpBuf[32]; // [sp+4h] [bp-30h]@7
    VARIANTARG pvarg; // [sp+24h] [bp-10h]@3

    vt = pv->vt & 0xBFFF;
    if ( vt != VT_DISPATCH )
    {
        return vt;
    }
    if ( pv->vt & VT_BYREF )
    {
        lVal = *pv->plVal;
    }
    else
    {
        lVal = pv->lVal;
    }

    pvarg.vt = 0;
    nRet = ErrObjDefault(lVal, 0, 0, 0, 0, 3, &pvarg, excpBuf);
    EbClearException(excpBuf);
    if ( nRet >= 0 )
    {
        l_vt = pvarg.vt;
        __vbaFreeVar(&pvarg);
        LOWORD(vt) = l_vt;
    }
    else
    {
        LOWORD(vt) = VT_DISPATCH;
    }
    return vt;
}
Bác thấy đấy VarType của VBA cũng như VarType của Delphi, chỉ cần check field vt của VARIANT, nếu VT_DISPATCH thì tùm lum thứ tiếp theo.
Hình như trong Delphi, unit Variants.pas, có hàm VarTypeAsString đó bác Tuân, nó cũng như TypeName (rtcTypeName) của VBA đó.
Bài đã được tự động gộp:

Bà con test giúp mình file ở post #8 nhé.
 
Lần chỉnh sửa cuối:
Upvote 0
Hàm rtcTypeName thì đúng là mình lùng bùng luôn :p
Mã:
VARIANT *__stdcall rtcTypeName(VARIANT *pVar)
{
    int vt; // edi@1
    int (__stdcall ***v2)(_DWORD, _DWORD, _DWORD); // esi@9
    const unsigned __int16 *v3; // ebx@11
    VARIANT *result; // eax@14
    unsigned int v5; // esi@18
    int v6; // eax@31
    int v7; // esi@34
    int v8; // eax@34
    int v9; // esi@38
    VARIANT *v10; // esi@40
    LONG v11; // eax@53
    LONG v12; // eax@55
    int v13; // eax@56
    int v14; // eax@57
    int v15; // [sp+Ch] [bp-14h]@35
    int v16; // [sp+10h] [bp-10h]@33
    unsigned int bCurrency; // [sp+14h] [bp-Ch]@1
    int v18; // [sp+18h] [bp-8h]@34
    BSTR bstrString; // [sp+1Ch] [bp-4h]@1

    vt = pVar->vt & 0x9FFF;
    bCurrency = (pVar->vt >> 13) & 1;
    bstrString = 0;
    if ( vt > VT_UNKNOWN )
    {
        if ( vt == VT_DECIMAL || vt == VT_UI1 )
        {
            goto DecimalOrUI1;
        }
        if ( vt != VT_RECORD )
        {
            goto LABEL_44;
        }
        if ( dword_65246024 )
        {
            EbRaiseExceptionCode(VT_UNKNOWN);
        }
        if ( !bCurrency )
        {
            if ( pVar->cyVal.Hi && (*(*pVar->cyVal.Hi + 28))(pVar->cyVal.Hi, &pVar) >= 0 )
            {
                return pVar;
            }
            goto LABEL_50;
        }
        if ( pVar->vt & VT_BYREF && (v11 = pVar->lVal, *v11) && *(*v11 + 2) & 0x20 )
        {
            v12 = *pVar->plVal;
        }
        else if ( pVar->vt & VT_BYREF || (v12 = pVar->lVal, !(*(v12 + 2) & 0x20)) || !*(v12 - 4) )
        {
            v3 = off_6524FDD0;
            goto LABEL_17;
        }
        v13 = (*(**(v12 - 4) + 28))(*(v12 - 4), &bstrString);
        if ( v13 < 0 )
        {
            v14 = EberrOfHresult(v13);
            EbRaiseExceptionCode(v14);
        }
        v3 = bstrString;
LABEL_17:
        if ( bCurrency )
        {
            v5 = wcslen(v3);
            pVar = SysAllocStringLen(0, v5 + 2);
            if ( !pVar )
            {
                EbRaiseExceptionCode(14);
            }
            sub_65012860(&pVar->vt, v5 + 3, v3);
            v6 = 2 * v5;
            *(&pVar->vt + v6) = 40;
            *(&pVar->wReserved1 + v6) = 41;
            *(&pVar->vt + v5 + 2) = 0;
            goto LABEL_13;
        }
LABEL_12:
        pVar = SysAllocString(v3);
        if ( !pVar )
        {
            EbRaiseExceptionCode(14);
        }
LABEL_13:
        SysFreeString(bstrString);
        return pVar;
    }
    if ( vt == VT_UNKNOWN )
    {
        goto LABEL_6;
    }
    if ( vt < 0 )
    {
LABEL_44:
        EbRaiseExceptionCode(458);
    }
    if ( vt <= 8 )
    {
DecimalOrUI1:
        v3 = (&off_6524FD88)[2 * vt];
        goto LABEL_17;
    }
    if ( vt != 9 )
    {
        if ( vt <= 9 )
        {
            goto LABEL_44;
        }
        if ( vt > 11 )
        {
            if ( vt == 12 )
            {
LABEL_19:
                if ( dword_65246024 )
                {
                    EbRaiseExceptionCode(VT_UNKNOWN);
                }
                goto DecimalOrUI1;
            }
            goto LABEL_44;
        }
        goto DecimalOrUI1;
    }
LABEL_6:
    if ( bCurrency )
    {
        goto LABEL_19;
    }
    if ( dword_65246024 )
    {
        EbRaiseExceptionCode(VT_UNKNOWN);
    }
    if ( pVar->vt & 0x4000 )
    {
        v2 = *pVar->plVal;
    }
    else
    {
        v2 = pVar->lVal;
    }
    if ( !v2 )
    {
        v3 = L"Nothing";
        goto LABEL_12;
    }
    if ( (**v2)(v2, &IID_IProvideClassInfo, &v16) < 0 )
    {
        if ( (**v2)(v2, &IID_IDispatch, &v15) < 0 )
        {
LABEL_41:
            v3 = (&off_6524FD88)[2 * vt];
            goto LABEL_12;
        }
        v7 = (*(*v15 + 16))(v15, 0, 1033, &v18);
        v8 = v15;
    }
    else
    {
        v7 = (*(*v16 + 12))(v16, &v18);
        v8 = v16;
    }
    (*(*v8 + 8))(v8);
    if ( v7 < 0 )
    {
        goto LABEL_41;
    }
    v9 = (*(*v18 + 48))(v18, -1, &pVar, 0, 0, 0);
    (*(*v18 + 8))(v18);
    if ( v9 < 0 )
    {
LABEL_50:
        v3 = pVar;
        goto LABEL_12;
    }
    result = pVar;
    if ( 95 != pVar->vt )
    {
        return result;
    }
    v10 = SysAllocStringLen(&pVar->wReserved1, (pVar[-1].cyVal.Hi >> 1) - 1);
    SysFreeString(&pVar->vt);
    result = v10;
    return result;
}
 

File đính kèm

  • 1.png
    1.png
    37.4 KB · Đọc: 51
Upvote 0
vãi kinh lấy được cả code ... lạy luôn
 
Upvote 0
Vì vậy lúc trước mình đã có nói bên topic Optimize code VBA là không nên dùng hàm VBA TypeName, chỉ nên dùng VarType, rất nhanh.
 
Upvote 0
Cùng 1 code nhưng do compile trên VC++ trên 32bit và 64bit, compiler nó sinh mã khác nhau đôi chút bác. À mà hàm rtcTypeName phức tạp lắm bác Tuân à, dài khủng khiếp. Bác xem chi vậy ?
Hàm VarType của VBA = rtcVarType
Mã:
int __stdcall rtcVarType(VARIANT *pv)
{
    return (unsigned __int16) GetVarType(pv);
}

__int16 __stdcall GetVarType(VARIANT *pv)
{
    VARTYPE l_vt; // si@3
    LONG lVal; // ecx@4
    int nRet; // edi@7
    int vt; // eax@1
    char excpBuf[32]; // [sp+4h] [bp-30h]@7
    VARIANTARG pvarg; // [sp+24h] [bp-10h]@3

    vt = pv->vt & 0xBFFF;
    if ( vt != VT_DISPATCH )
    {
        return vt;
    }
    if ( pv->vt & VT_BYREF )
    {
        lVal = *pv->plVal;
    }
    else
    {
        lVal = pv->lVal;
    }

    pvarg.vt = 0;
    nRet = ErrObjDefault(lVal, 0, 0, 0, 0, 3, &pvarg, excpBuf);
    EbClearException(excpBuf);
    if ( nRet >= 0 )
    {
        l_vt = pvarg.vt;
        __vbaFreeVar(&pvarg);
        LOWORD(vt) = l_vt;
    }
    else
    {
        LOWORD(vt) = VT_DISPATCH;
    }
    return vt;
}
Bác thấy đấy VarType của VBA cũng như VarType của Delphi, chỉ cần check field vt của VARIANT, nếu VT_DISPATCH thì tùm lum thứ tiếp theo.
Hình như trong Delphi, unit Variants.pas, có hàm VarTypeAsString đó bác Tuân, nó cũng như TypeName (rtcTypeName) của VBA đó.
Bài đã được tự động gộp:

Bà con test giúp mình file ở post #8 nhé.

Cảm ơn anh. EM tìm hàm TypeName trong Delphi chưa thấy cái nào đùng kiểu của VBA. Hàm này nó trả về tên của đối tượng.
Ví dụ
?TypeName(ActiveCell) kết quả là "Range"

Trên Userform nếu két một control nào đó vào rồi test
?TypeName(control) nó sẽ trả về tên class của control đo. Ví dụ. Kết quả trả về như là "TextBox", "CommandButton"

Còn VarType trong VBA giống với VarType trong Delphi nó kiểm tra kiểu giá trị của biến kiểu VARIANT. Giá trị trả về là số kiểu Word
VT_I2, VT_I4, VT_BSTR, VT_DATE,...

(*) Nhìn quả code tạo hàm TypeName() đúng là khoai. Trong code đó nó còn call nhiều hàm con ở đâu đó nữa. Bác xem cách trả về của hàm TypeName như em mô tả, có hàm nào trong Delphi làm được điều đó không bác?
 
Upvote 0
Các hàm IsXXX trong VBA mà các bạn hay dùng cũng hoàn toàn dựa vào hàm VarType/GetVarType
Mã:
// rtcIsArray = IsArray
unsigned int __stdcall rtcIsArray(VARIANT *pv)
{
    unsigned int varType; // eax@1

    LOWORD(varType) = GetVarType(pv);
    return -((varType >> 13) & 1);
}

// rtcIsEmpty = IsEmpty
__int16 __stdcall rtcIsEmpty(VARIANT *pv)
{
    return -(GetVarType(pv) == 0);
}

// rtcIsError = IsError
__int16 __stdcall rtcIsError(VARIANT *pv)
{
    return -(GetVarType(pv) == VT_ERROR);
}

// rtcIsMissing = IsMissing
signed int __stdcall rtcIsMissing(VARIANT *pVar)
{
    signed int result; // eax@2
    LONG lVal; // eax@4

    if ( 8204 != pVar->vt || (lVal = pVar->lVal, 1 != *lVal) || *(lVal + 16) )
    {
        result = rtIsMissing(pVar);
    }
    else
    {
        result = -1;
    }
    return result;
}

// rtcIsNull = IsNull
__int16 __stdcall rtIsNull(VARIANT *pv)
{
    return -(GetVarType(pv) == VT_NULL);
}

// rtcIsObject = IsObject
int __stdcall rtcIsObject(VARIANT *pV)
{
    return -((pV->vt & 0xBFFF) == VT_DISPATCH);
}
Bài đã được tự động gộp:

Hàm IsDate và IsNumeric hóa ra lại rất phức tạp
Mã:
signed __int16 __stdcall rtcIsNumeric(VARIANT *pVar)
{
    VARTYPE vt; // dx@1
    signed __int16 wRet; // di@1
    __int16 l_vType; // ax@2
    LONG lVal; // ecx@13
    int vErr; // esi@15
    const OLECHAR *bStr; // esi@23
    LCID v8; // eax@29
    char excpBuf[32]; // [sp+8h] [bp-38h]@15
    VARIANTARG pvarg; // [sp+28h] [bp-18h]@1
    DOUBLE pdblOut; // [sp+38h] [bp-8h]@29

    vt = pVar->vt;
    wRet = 0;
    pvarg.vt = 0;
    if ( vt & VT_ARRAY )
    {
        return wRet;
    }
    l_vType = vt & 0xBFFF;
    if ( !(vt & 0xBFFF)
      || l_vType >= VT_I2 && l_vType <= VT_CY
      || l_vType == VT_BOOL
      || l_vType == VT_UI1
      || l_vType == VT_DECIMAL )
    {
        return -1;
    }
    if ( l_vType == VT_DISPATCH )
    {
        if ( vt & VT_BYREF )
        {
            lVal = *pVar->plVal;
        }
        else
        {
            lVal = pVar->lVal;
        }
        vErr = ErrObjDefault(lVal, 0, 0, 0, 0, 3, &pvarg, excpBuf);
        EbClearException(excpBuf);
        if ( vErr < 0 )
        {
            goto LABEL_30;
        }
        if ( !pvarg.vt
          || pvarg.vt >= VT_I2 && pvarg.vt <= VT_CY
          || pvarg.vt == VT_BOOL
          || pvarg.vt == VT_UI1
          || pvarg.vt == VT_DECIMAL )
        {
            return -1;
        }
        if ( pvarg.vt == VT_BSTR )
        {
            bStr = pvarg.bstrVal;
LABEL_28:
            if ( bStr )
            {
                v8 = rtUserDefaultLCID();
                wRet = -(VarR8FromStr(bStr, v8, 0, &pdblOut) >= 0);
            }
            goto LABEL_30;
        }
        goto LABEL_30;
    }
    if ( l_vType == VT_BSTR )
    {
        if ( vt & VT_BYREF )
        {
            bStr = *pVar->plVal;
        }
        else
        {
            bStr = pVar->bstrVal;
        }
        goto LABEL_28;
    }
LABEL_30:
    __vbaFreeVar(&pvarg);
    return wRet;
}
Vậy nên tránh hàm IsNumeric, IsDate này.
Bài đã được tự động gộp:

@Nguyễn Duy Tuân: nó nằm ở khúc call IDispatch và IProvideClassInfo đó bác. Các Object của Excel hay Office implement các Interface này và trả về typename đó.
 
Lần chỉnh sửa cuối:
Upvote 0
Bác Tuân muốn trên 32 hay 64bit ?
Nhờ các bác test giùm cu anh tui file này, xem giới hạn của SysAllocStringLen API trên Windows và máy các bác là bao nhiêu.
Theo lý thuyết, MS document thì trên 32bit là MAX_UINT = 0xFFFFFFFF nhưng hoàn toàn không phải vậy, nhỏ hơn nhiều.
Có 2 sub Test_xxx, các bạn chạy và chịu khó chờ xem ra kết quả bao nhiêu nhé. Máy càng mạnh, càng bộ nhớ nhiều càng tốt.

Em test file của bác trên Excel 32-bit thì 2 lệnh gọi test SysAllowStringLen đều bị lỗi "Overflow".

Em đọc trên trang "tổ sư" nói về kiểu String trong VBA thì nói độ rộng tối đa là 2^16 (fixed-length ) hoặc 2^31 (variable-length). Nhưng test với 2^31 cũng lỗi :) .

Vụ TypeName em sẽ tìm hiểu cách khai thác IDispatch xem sao. Thank bác!
 
Upvote 0
Để mình add Virtual Method Table của mấy interface này vào "rờ em", debug tiếp. Mà chắc đúng rồi đó. Phải implement IProvideClassInfo mới trả về typename được. Bác Tuân check nếu là IDispatch thì QueryInterface lấy IProvideClassInfo về, call method GetClassInfo để lấy ITypeInfo về, tiếp tục call, mò các method của ITypeInfo tiếp ra gì.

Vụ len thì MS "đồ cú mần" nói không đúng đâu bác Tuân, mới 0x3FFFFFFF đã văng rồi, bác chạy coi I xuống tới bao nhiêu là Space, Space$ thành công.
 

File đính kèm

  • 1.png
    1.png
    17.1 KB · Đọc: 37
Lần chỉnh sửa cuối:
Upvote 0
Để mình add Virtual Method Table của mấy interface này vào "rờ em", debug tiếp. Mà chắc đúng rồi đó.

Không đúng đâu bác Tuân, mới 0x3FFFFFFF đã văng rồi, bác chạy coi I xuống tới bao nhiêu là Space, Space$ thành công.

Thành công ở thông báo "3F9277EA" nhé bác.
 
Upvote 0
1,017 MB. Gần 1GB. Sao lại là số này nhỉ ? Chờ các bạn khác test xem sao. Vì debug vào SysAllocStringLen nó phức tạp quá, xuống tới tận tầng kernel, nên mình thua, không tìm ra đoạn nào giới hạn cái size này.
Test_rtcSpaceVar và Test_rtcSpaceBstr ra I khác nhau đó bác @Nguyễn Duy Tuân. Bác chạy cả 2 chưa ?
 
Lần chỉnh sửa cuối:
Upvote 0
vãi kinh lấy được cả code ... lạy luôn
Thế nên giờ họ mới sinh ra mã nguồn mở, ngôn ngữ script (VBA cũng là 1 trong các dạng đó), và các ngôn ngữ lập trình dạng thông dịch lên ngôi,
Quan trọng là ý tưởng và xây dựng ý tưởng mới cần, không phải dăm ba cái code nhỏ
 
Upvote 0
À, máy befaint Office64 à, code mình chỉ mới test trên Office 32. Để mở VBE 64 xem giới hạn nó là bao nhiêu.
Bài đã được tự động gộp:

Office 2016 x64, VBE7.dll là v7.1.10.77 phải không @befaint ?
 
Upvote 0
Win10 phải không @befaint ? Befaint vào source mdlTest, chỉnh const MAX_LEN tăng lên 1 hay 2 rồi chạy lại thử xem ?
 
Upvote 0
Befaint và các bạn chịu khó xem kỹ cái hình mình mới post, sẽ thấy giới hạn 0x3FFFFFFF. Đọc code mình kỹ sẽ hiểu ý mình muốn nói
Bài đã được tự động gộp:

Số 5 với số 14 truyền vào hàm EbRaiseExceptionCode chính là Err. Number 5 và 14 đó
 
Lần chỉnh sửa cuối:
Upvote 0
Em test file của bác trên Excel 32-bit thì 2 lệnh gọi test SysAllowStringLen đều bị lỗi "Overflow".
Nếu tôi không đọc nhầm thì đâu có test SysAllowStringLen. Chỉ có Test_rtcSpaceBstr thôi. Mà nếu có lỗi thì đâu có khẳng định được là lỗi của SysAllowStringLen?

Bạn thử chạy SysAllowStringLen trong Delphi xem. Tôi tin là sẽ không có lỗi thậm chí với MaxInt = $7FFFFFFF = 2 147 483 647
 
Upvote 0
Đi tắm chợt nảy ra ý tưởng về vụ TypeName của bạn Tuân. Khỏi viết hàm Delphi chi cho mệt, dùng luôn hàm VBA rtcTypeName đã được export từ VBE6/7.dll. Prototype hàm đã có rồi đó, nhận pointer to VARIANT, trả về BSTR string.
VBExxx.dll chắc chắn đã load 100% vào Excel, bác chỉ cần GetProcAddress, call là xong.
Tương tự với các hàm export, undocument khác từ VBExxx.dll.
Sure là 100% chạy được, đúng.
Để mai mình làm file Excel VBA test cho.
 
Lần chỉnh sửa cuối:
Upvote 0
Nếu tôi không đọc nhầm thì đâu có test SysAllowStringLen. Chỉ có Test_rtcSpaceBstr thôi. Mà nếu có lỗi thì đâu có khẳng định được là lỗi của SysAllowStringLen?

Bạn thử chạy SysAllowStringLen trong Delphi xem. Tôi tin là sẽ không có lỗi thậm chí với MaxInt = $7FFFFFFF = 2 147 483 647

Ah, em chỉ test Test_rtcSpaceBstr thôi chứ không test cái còn lại. Như bác Cu Anh nói thì trong hàm Space$ dùng hàm SysAllowStringLen nên kết luận thông qua Space$. Cái này em cứ test theo bác chủ topic để bác ấy nghiên cứu tiếp.

Em đã test hàm SysAllowStringLen trong Delphi với con số lớn hơn 2^31 không có lỗi gì. Có lẽ tài liệu của MS nói về String Type là có quan hệ với khả năng lưu trữ ký tự trên Cell.Value của Excel?
Bài đã được tự động gộp:

Đi tắm chợt nảy ra ý tưởng về vụ TypeName của bạn Tuân. Khỏi viết hàm Delphi chi cho mệt, dùng luôn hàm VBA rtcTypeName đã được export từ VBE6/7.dll. Prototype hàm đã có rồi đó, nhận pointer to VARIANT, trả về BSTR string.
VBExxx.dll chắc chắn đã load 100% vào Excel, bác chỉ cần GetProcAddress, call là xong.
Tương tự với các hàm export, undocument khác từ VBExxx.dll.
Sure là 100% chạy được, đúng.
Để mai mình làm file Excel VBA test cho.

Em cũng tính dùng cái hàm họ đã exports để dùng luôn, khi nào có time thì xem cách lấy từ IDispacth. Tuy nhiên bác kiểm tra kỹ cho em thêm hàm rtcTypeName nó export theo stdcall hay safecall?
 
Upvote 0
Bạn Tuân test trên Win 64 hay 32,app 32 hay 64.
Trên Win64 thì memory cho user khỏi phải lo. befaint chạy Ok với 0x3FFFFFFF đó
Mình đang Re, test 32.
Tất cả hàm API đều là stdcall hết bạn. Quy ước rồi. COM Object methods mới safecall.
 
Upvote 0
Bạn Tuân test trên Win 64 hay 32,app 32 hay 64.
Trên Win64 thì memory cho user khỏi phải lo. befaint chạy Ok với 0x3FFFFFFF đó
Mình đang Re, test 32.

Em test trên Excel 64-bit, Win10 64-bit. Vụ này em nghi có sự can thiệp của Excel vì nó chỉ được cấp phát bộ nhớ giới hạn trong Windows. Tùy vào trạng thái bộ nhớ của Excel còn được phép bao nhiêu mà nó cho phép một vài hàm nào đó mở rộng vùng nhớ.

Tất cả hàm API đều là stdcall hết bạn. Quy ước rồi. COM Object methods mới safecall.

Nếu bác tự "Declare Function rtcTypeName ..." trong VBA chạy được thì chắc chắn là export với "stdcall" nếu không em nghi là "safecall". "SafeCall" dùng trong COM nhưng cũng dùng để reference trong TLB và những hàm đó sẽ export dạng safecall (vụ này em tự viết các hàm API riêng theo cả hai mẫu export).
Bài đã được tự động gộp:

Thông báo với bác CÙ Anh. Em đã kiểm tra và khai báo API với hàm rtcTypeName ok cho cả VBA6.dll và VBA7.DLL rồi nhé. Chính xác rtcTypeName đã được export theo stdcall. :)

Mã:
Option Explicit
#If VBA7 Then
Declare PtrSafe Function rtcTypeName Lib "VBE7.DLL" (v As Variant) As String
#Else
Declare Function rtcTypeName Lib "VBE6.DLL" (v As Variant) As String
#End If

Sub TestTypeNameAPI()
    Dim sName As String
    sName = rtcTypeName(ActiveCell)
    MsgBox StrConv(sName, vbFromUnicode)
End Sub
 
Lần chỉnh sửa cuối:
Upvote 0
Ox3FFFFFFF là giới hạn cho các hàm VBA nhận vào tham số là số ký tự rồi. Ms coder code cái VBExxx.dll đã code, quy định constant này, tới bản 2019 64bit của befaint vừa up cũng vậy.
Mình up hình rành rành và code comment vậy mà các bạn kg xem kỹ sao trời.
Nói lại cho rõ , trong hàm Space và Space$, nếu số ký tự yêu cầu lớn hơn 0x3FFFFFFF, lỗi sẽ được raise và Err.Number = 5.
Nếu nhỏ hơn, call SysAllocStringLen. Nếu trả về NULL thì raise error với Err.Number là 14.
Kg liên quan gì tới Excel ở đây, đâu dùng VBA cũng vậy. Chỉ là oleaut32.dll và kernel.dll, xuống tới kernel Windows và virtual memory manager.
 
Lần chỉnh sửa cuối:
Upvote 0
Chúc mừng bác. Thấy sự lợi hại của undocument API với "rờ cxx em chua
 
Upvote 0
Ox3FFFFFFF là giới hạn cho các hàm VBA nhận vào tham số là số ký tự rồi. Ms coder code cái VBExxx.dll đã code, quy định constant này, tới bản 2019 64bit của befaint vừa up cũng vậy.
Mình up hình rành rành và code comment vậy mà các bạn kg xem kỹ sao trời.
Nói lại cho rõ , trong hàm Space và Space$, nếu số ký tự yêu cầu lớn hơn 0x3FFFFFFF, lỗi sẽ được raise và Err.Number = 5.
Nếu nhỏ hơn, call SysAllocStringLen. Nếu trả về NULL thì raise error với Err.Number là 14.
Kg liên quan gì tới Excel ở đây, đâu dùng VBA cũng vậy. Chỉ là oleaut32.dll và kernel.dll, xuống tới kernel Windows và virtual memory manager.

Em quên không nhìn cái hình bác chụp, nó đã code rõ vấn đề giới hạn. Em cũng đã đoán thư viện VBA dll tự giới hạn phạm vi chứ không phải hàm SysAllowStringLen của Windows. :).

Bác tiếp tục tìm các hàm undocument nhé. EM đang đưa cái hàm rtcTypeName vào trong Delphi thử nhưng chưa thành công, hàm nó trả về '' :)
 
Upvote 0
Em thông báo lại công việc khai thác như sau

Trong VBA khai báo API đã thành công:
Mã:
Option Explicit
#If VBA7 Then
Declare PtrSafe Function rtcTypeName Lib "VBE7.DLL" (v As Variant) As String
#Else
Declare Function rtcTypeName Lib "VBE6.DLL" (v As Variant) As String
#End If

Sub TestTypeNameAPI()
    Dim sName As String
    sName = rtcTypeName(ActiveCell)
    MsgBox StrConv(sName, vbFromUnicode)
End Sub

Trong Delphi thì hàm trả về ''. Các lệnh gọi API tới hàm rtcTypeName đều tốt, nhưng không hiểu sao hàm lại trả về '' .
Mã:
program Test_VBA_API.dproj;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils,
  Winapi.Windows,
  ComObj,
  System.Variants;

type
  TFuncAVBAPI_rtcTypeName = function(v: OleVariant): WideString; stdcall;

var sName: WideString;
  h: HMODULE;
  pFunc: TFuncAVBAPI_rtcTypeName;
  App: OleVariant;
begin
  try
    App := GetActiveOleObject('Excel.Application');
    Writeln('Ung dung da ma la', ': ', App.Name);
    h := LoadLibrary('C:\Program Files (x86)\Common Files\Microsoft Shared\VBA\VBA7\VBE7.DLL');
    try
      pFunc := GetProcAddress(h, 'rtcTypeName');
      sName := TFuncAVBAPI_rtcTypeName(pFunc)(App.ActiveCell);
      Writeln('Gia tri bien la', ': ', sName);
    finally
      FreeLibrary(h);
      App := Unassigned;
    end;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;

  Readln;

end.
 
Upvote 0
Prototype nó ghi rõ, và mình cũng nói rồi mà bạn. Pointer to struct VARIANT, tức trên Delphi là @ biển kiểu Variant hay TVarData. Khai báo Delphi sửa v thành v: Pointer.
Ông VBA và Delphi, ông nào cũng lanh chanh chuyển kiểu ngầm hết, khổ.
 
Lần chỉnh sửa cuối:
Upvote 0
Prorotype nó ghi rõ, và mình cũng nói rồi mà bạn. Pointer to strucr VARIANT, tức trên Delphi là @ biển kiểu Variant hay TVarData. Khai báo Delphi sửa v thành v: Pointer.
Ông VBA và Delphi, ông nào cũng lanh chanh chuyển kiểu ngầm hết, khổ.

Thành công trên Delphi rồi bác nhé. Em quên nguyên tắc con trỏ khi khai báo hàm này :).
 
Upvote 0
Các bạn có ai thắc mắc là trong code VBA của bác Tuân, v as Variant, còn trong Delphi v: Pointer không nhỉ ?
Chuyện dài dòng đấy :P
 
Upvote 0
Các bạn có ai thắc mắc là trong code VBA của bác Tuân, v as Variant, còn trong Delphi v: Pointer không nhỉ ?
Chuyện dài dòng đấy :p
Có đấy tính hỏi trong Dephi viết sao cho nó hiểu đây .... và ứng dụng nó như thế nào là hiệu quả nhất (khác biệt nhất ) hay cũng như Msgbox Typename(x) như lâu nay vẫn làm thế trên VBA
 
Lần chỉnh sửa cuối:
Upvote 0
Bạn @kieu manh hỏi bác @Nguyễn Duy Tuân ấy, dùng ra sao !?
Tiếp tục với hàm unsigned int __stdcall StrLenChk(unsigned int nLen) này, hay tức là hằng 0x3FFFFFFF (1073741823) này.
Trong các VBExxx.dll 32bit, hàm này là hàm riêng, còn trên các VBExxx.dll 64bit, hàm này được inline compiled vào trực tiếp hàm caller.
Có rất nhiều các hàm VBA đều phải dùng hàm StrLenChk này. Ví dụ các hàm sau:
1. String$ = rtcStringBstr
2. String = rctStringVar
3. InStr với param truyền vào là kiểu string = _vbaInStr
4. InStr với param truyền vào là kiểu Variant = __vbaInStrVar
5. 1 đống các hàm InStr với tham số truyền vào là các kiểu khác như Char, __vbaInStrVarB
6. Hàm VBA InStr gốc ở ngoài = rtcInStr
7. Các VBA Input statement
8. Các hàm VBA Mid, Mid$, statement Mid, Mid$
....
Và rất rất nhiều nữa, các hàm A gọi hàm B, B gọi C, C gọi StrLenChk, đều văng Err.Number 5 lên hết.
Nên thôi, viết ra quá dài dòng, nhiều không xuể, nên các bác cứ nhớ giúp cu anh tui là với VBA, cái nào mà đòi hỏi, nhận tham số là số ký tự hay truyền vào Len string thì luôn luôn phải nhỏ hơn 0x3FFF FFFFF (&H3FFF FFFF: VBA, $3FFFF FFFF: Delphi, hay 1073741823 decimal). Vậy thôi. Coder MS code cái VBA DLLs nó đã quy định như vậy rồi.

Hì hì, mà nó quy định vậy là đúng đó, không phải sai hay document của MS nó sai đâu. Đố ai tìm ra được tại sao có cái hằng 0x3FFFFFFF này ???? Tui cũng chợt nhớ ra tối qua thôi, lúc tắm :)

@Nguyễn Duy Tuân: nick tui không phải là Cù Anh, hay Cù Anh Thắng mà hồi xưa nhiều báo đăng, nó đọc bình dân, nhà quê là Thằng Cu Anh thôi :p
Với lại nếu bác mang code rtcTypeName cho Delphi vào Addin Tools của bác, bác check xem thử là ở những điều kiện nào VBE6/7.dll không được Excel load lên, như disable Macro, disable ActiveX... Mà tui cũng nghĩ, cái AddIn Tools của bác mà lên được thì 100% chắc chắn VBExxx.dll đã load lên rồi :)
Mà nếu nó đã được load lên rồi, thì chỉ cần GetProcAddress(GetModuleHandle('VBEx.dll'), 'rtcTypeName')) thôi, chả cần đường dẫn gì cho phức tạp.
 
Lần chỉnh sửa cuối:
Upvote 0
Bạn @kieu manh hỏi bác @Nguyễn Duy Tuân ấy, dùng ra sao !?
Tiếp tục với hàm unsigned int __stdcall StrLenChk(unsigned int nLen) này, hay tức là hằng 0x3FFFFFFF (1073741823) này.
Trong các VBExxx.dll 32bit, hàm này là hàm riêng, còn trên các VBExxx.dll 64bit, hàm này được inline compiled vào trực tiếp hàm caller.
Có rất nhiều các hàm VBA đều phải dùng hàm StrLenChk này. Ví dụ các hàm sau:
1. String$ = rtcStringBstr
2. String = rctStringVar
3. InStr với param truyền vào là kiểu string = _vbaInStr
4. InStr với param truyền vào là kiểu Variant = __vbaInStrVar
5. 1 đống các hàm InStr với tham số truyền vào là các kiểu khác như Char, __vbaInStrVarB
6. Hàm VBA InStr gốc ở ngoài = rtcInStr
7. Các VBA Input statement
8. Các hàm VBA Mid, Mid$, statement Mid, Mid$
....
Và rất rất nhiều nữa, các hàm A gọi hàm B, B gọi C, C gọi StrLenChk, đều văng Err.Number 5 lên hết.
Nên thôi, viết ra quá dài dòng, nhiều không xuể, nên các bác cứ nhớ giúp cu anh tui là với VBA, cái nào mà đòi hỏi, nhận tham số là số ký tự hay truyền vào Len string thì luôn luôn phải nhỏ hơn 0x3FFF FFFFF (&H3FFF FFFF: VBA, $3FFFF FFFF: Delphi, hay 1073741823 decimal). Vậy thôi. Coder MS code cái VBA DLLs nó đã quy định như vậy rồi.

Hì hì, mà nó quy định vậy là đúng đó, không phải sai hay document của MS nó sai đâu. Đố ai tìm ra được tại sao có cái hằng 0x3FFFFFFF này ???? Tui cũng chợt nhớ ra tối qua thôi, lúc tắm :)

@Nguyễn Duy Tuân: nick tui không phải là Cù Anh, hay Cù Anh Thắng mà hồi xưa nhiều báo đăng, nó đọc bình dân, nhà quê là Thằng Cu Anh thôi :p
Với lại nếu bác mang code rtcTypeName cho Delphi vào Addin Tools của bác, bác check xem thử là ở những điều kiện nào VBE6/7.dll không được Excel load lên, như disable Macro, disable ActiveX... Mà tui cũng nghĩ, cái AddIn Tools của bác mà lên được thì 100% chắc chắn VBExxx.dll đã load lên rồi :)

Vâng, các hàm API trong VBAxxx mà bác khai thác cho các ứng dụng add-in là rất hay vì các hàm đó rất tốt hoặc đỡ cực nhọc viết lại. List ra danh dách các function thì có tool nhưng tìm được prototype thì rất khó lại "nội suy" từ Assempler ra C nữa thì có lẽ rất rất ít người làm được như bác @ThangCuAnh (Cu Anh) đó :) .

MS coder đưa ra cái giới hạn MAX_LEN=0x3FFFFFFF ( có lẽ đoán là StringType trong VB quy định độ đài tối đa là 2^31 (dec: 2147483648). Trong môi trường VBA ký tự đưa vào ở dạng ANSI - 1 byte vì thế nó đưa ra giới hạn 1073741823 để khi chuyển đổi sang unicode 2 byte cho 1 ký tự. Khi đó giải quyết với unicode tối đa số ký tự sẽ là 1073741823*2 = 2147483646, thiếu so với 2^31 là 2 byte, lý do trong C API nó tự thêm một ký tự ở cuối chuỗi CHR(0) gọi là NUL terminated. Vậy tính tất tần tật nó đùng bằng độ dài tối đa 2^31. Tài liệu MS nói là đúng :).

Trong VBA nó có hằng số định hướng biên dịch VBA7 với cách điều hướng #If VBA Then... và ta tùy ý đổi giữa VBA6xxx hoặc VBA7xxx. Vấn đề sử dụng API từ ngôn ngữ ngoài thì phải dùng LoadLibrary("file dll"). Và chúng ta kết hợp dùng GetProcAddress() để lấy con trỏ hàm. Như vậy chỉ càn biết mã phiên bản Application.Version là điều hướng được.

Từ vấn đề LoadLibrary("file dll") em muốn hỏi bác là, khi VBAxxx đã load rồi thì làm sao tìm được hMODULE của nó để đưa thẳng vào GetPrcAddress. Làm như vậy sẽ không cần phải check mã phiên bản ứng dụng mẹ là Excel. VBAxxx đã được Excel load khi khởi động nên ta có thể tận dụng cái này như thế nào?

Đề xuất thứ hai nhờ bác tìm cái prototype của hàm INSTR và INSTRREV để em "sờ" tiếp :) . Ah. cái sp của em là "Add-in A-Tools" (có chữ s cuối cùng bác a :P )
 
Upvote 0
Bác Tuân muốn trên 32 hay 64bit ?
Nhờ các bác test giùm cu anh tui file này, xem giới hạn của SysAllocStringLen API trên Windows và máy các bác là bao nhiêu.
Theo lý thuyết, MS document thì trên 32bit là MAX_UINT = 0xFFFFFFFF nhưng hoàn toàn không phải vậy, nhỏ hơn nhiều.
Có 2 sub Test_xxx, các bạn chạy và chịu khó chờ xem ra kết quả bao nhiêu nhé. Máy càng mạnh, càng bộ nhớ nhiều càng tốt.
Thông tin máy:

222130

Office 365
Kết quả test như sau:

 
Upvote 0
Cảm ơn bác @Hai Lúa Miền Tây, vậy là Win64 nó vượt qua ngưỡng SysAllocStringByLen 0x3FFFFFFF hết đó, nhẹ nhàng. Win32 thì may rủi, ì ạch thôi, đa số là gần 1 GB là văng.
 
Lần chỉnh sửa cuối:
Upvote 0
Cảm ơn bác @Hai Lúa Miền Tây, vậy là Win64 nó vượt qua ngưỡng SysAllocStringByLen 0x3FFFFFFF hết đó, nhẹ nhàng. Win32 thì may rủi, ì ạch thôi, đa số là cỡ 1 GB là văng.

Không hoàn toàn do Windows đâu. Windows 64-bit và đi cùng Excel 64-bit về cơ bản là đạt bộ nhớ tốt nhất. Tuy nhiên Excel có sự dịch chuyển về cấp pháp bộ nhớ tùy vào hiện trạng của nó. Em cũng Windows 64-bit, Excel 64-bit nhưng văng nhé. Vụ này em có kinh nghiệm vì vấp nhiều :) .
 
Upvote 0
Phiền admin xóa giùm topic này, vì nó nhiều tiểu tiết quá, phải chụp hình, post code, giải thích rất nhiều. Nên thôi. Để dịp khác, nhả tơ từ từ thôi.
Giờ quay lại cho xong cái patch VBExxx.dll đã. Chứ chưa cái nào xong cái nào.
Từ từ làm cũng được bạn, đề tài này cũng khá hay, được sự quan tâm của nhiều thành viên. Sao phải xóa? Cố gắng tiếp tục nhé bạn.
 
Upvote 0
Không hoàn toàn do Windows đâu. Windows 64-bit và đi cùng Excel 64-bit về cơ bản là đạt bộ nhớ tốt nhất. Tuy nhiên Excel có sự dịch chuyển về cấp pháp bộ nhớ tùy vào hiện trạng của nó. Em cũng Windows 64-bit, Excel 64-bit nhưng văng nhé. Vụ này em có kinh nghiệm vì vấp nhiều :) .
Thật ra khi chạy code đó đầu tiên trên máy của em nó bị văng ra và MS Office 365 đưa ra thông báo cập nhật phiên bản. Sau khi cập nhật xong thì chạy được kết quả như clip đăng ở trên.
 
Upvote 0
Các hàm Trim, LTrim, RTrim, Trim$, LTrim$, RTrim$ của VBA ngoài remove character space (0x20) còn remove luôn cả ký tự Unicode 0x3000. Chỉ 2 ký tự này.
 

File đính kèm

  • Untitled.png
    Untitled.png
    17.5 KB · Đọc: 18
Upvote 0
Sao giờ mới thấy post của bạn Tuân nhỉ. Dùng GetModuleHandle đó bạn. GetModuleHandle('vbe6.dll') không được, không phải Office 2007 trở xuống, lấy tiếp GetModuleHandle('vbe7.dll'). Chắc chắn phải return được HInstance của VBEx.dll thôi. 2019 của @befaint cũng VBE7.dll đó.

Chưa biết chừng nào tới VBE8.dll :p
Thôi can bác Tuân dùng hàm InStrRev, Delphi thắng chắc rồi. Nó có 1 hàm internal, không có function name, làm mọi việc phức tạp quá mức cần thiết.
Prototype nó đây:
Mã:
UINT __stdcall rtcInStrRev(LPWSTR pwszSrc, LPWSTR pwszFind, int nStart, UINT nCompareMode)
{
    LCID l_locale; // edi@1
    UINT l_nCompareMode; // ebx@1
    int nLenSrc; // esi@6
    UINT result; // eax@10

    l_locale = nCompareMode;
    l_nCompareMode = 1;
    if ( nCompareMode != 1 && nCompareMode )
    {
        if ( !CheckValidLocale(nCompareMode) )
        {
            EbRaiseExceptionCode(5);
        }
    }
    else
    {
        l_nCompareMode = nCompareMode;
        l_locale = rtUserDefaultLCID();
    }
    nLenSrc = nStart;
    if ( !nStart || nStart < -1 )
    {
        EbRaiseExceptionCode(5);
    }
    if ( !pwszSrc )
    {
        return 0;
    }
    if ( nStart == -1 )
    {
        nLenSrc = *((_DWORD *)pwszSrc - 1) >> 1;
    }
    else if ( (unsigned int)nStart > *((_DWORD *)pwszSrc - 1) >> 1 )
    {
        return 0;
    }
    if ( pwszFind )
    {
        result = InternalFunc_Unknown(pwszSrc, pwszFind, nLenSrc, l_locale, (void *)l_nCompareMode);
    }
    else
    {
        result = nLenSrc;
    }
    return result;
}
 
Lần chỉnh sửa cuối:
Upvote 0
Bác Tuân giải thích về vụ 0x3FFFFFFFF là đúng rồi đó.
Thử test hàm rtcInStrRev này với hàm Pos của Delphi và StrPos PChar của Delphi xem, xem thằng nào nhanh hơn, bao nhiêu %.
Và xem thử hàm Trim của Delphi nó có hơn VBA không ?

VBA vậy là không code multithread được hay code multithread không an toàn rồi. Nó dùng biến toàn cục cho các hàm rtcXXXVar nhiều quá.
Ví dụ biến này, share cho 180 hàm gọi. Kinh. Variant 0 đó.
 

File đính kèm

  • Untitled.png
    Untitled.png
    19.2 KB · Đọc: 10
Lần chỉnh sửa cuối:
Upvote 0
Sao giờ mới thấy post của bạn Tuân nhỉ. Dùng GetModuleHandle đó bạn. GetModuleHandle('vbe6.dll') không được, không phải Office 2007 trở xuống, lấy tiếp GetModuleHandle('vbe7.dll'). Chắc chắn phải return được HInstance của VBEx.dll thôi. 2019 của @befaint cũng VBE7.dll đó.

Chưa biết chừng nào tới VBE8.dll :p
Thôi can bác Tuân dùng hàm InStrRev, Delphi thắng chắc rồi. Nó có 1 hàm internal, không có function name, làm mọi việc phức tạp quá mức cần thiết.
Prototype nó đây:
Mã:
UINT __stdcall rtcInStrRev(LPWSTR pwszSrc, LPWSTR pwszFind, int nStart, UINT nCompareMode)
{
    LCID l_locale; // edi@1
    UINT l_nCompareMode; // ebx@1
    int nLenSrc; // esi@6
    UINT result; // eax@10

    l_locale = nCompareMode;
    l_nCompareMode = 1;
    if ( nCompareMode != 1 && nCompareMode )
    {
        if ( !CheckValidLocale(nCompareMode) )
        {
            EbRaiseExceptionCode(5);
        }
    }
    else
    {
        l_nCompareMode = nCompareMode;
        l_locale = rtUserDefaultLCID();
    }
    nLenSrc = nStart;
    if ( !nStart || nStart < -1 )
    {
        EbRaiseExceptionCode(5);
    }
    if ( !pwszSrc )
    {
        return 0;
    }
    if ( nStart == -1 )
    {
        nLenSrc = *((_DWORD *)pwszSrc - 1) >> 1;
    }
    else if ( (unsigned int)nStart > *((_DWORD *)pwszSrc - 1) >> 1 )
    {
        return 0;
    }
    if ( pwszFind )
    {
        result = InternalFunc_Unknown(pwszSrc, pwszFind, nLenSrc, l_locale, (void *)l_nCompareMode);
    }
    else
    {
        result = nLenSrc;
    }
    return result;
}

Việc gọi hModule với việc chỉ định tên DLL thì không khó, ý em là khi Excel đã load VBAxxx dll rồi thì có cách nào chộp nó trong đâu đó không :). Cái này có vẻ cá khoai.

Việc check VBA7 hay VBA6 em làm dựa theo version Excel đang chạy vì phiên bản DLL nó phải ăn theo ứng dụng mẹ mới chính xác. Vì có một số máy tính cài ít nhất 2 loại Excel. Ví dụ cả Excel 2003, 2016 :).

Với hàm InStrRev, theo prototype hai tham số đầu là LPWSTR. Trong Delphi, unit WinAPI.Windows định kiểu LPWSTR = PWideChar. Nếu khai báo theo y nguyên kiểu đó sang Delphi là sai mà bên Delphi phải chuyển sang WideString mới chạy đúng bác à. Không biết bác dịch ngược cái tên kiểu có hoàn toàn chính xác không?
 
Upvote 0
Bác Tuân cứ đúng theo prototype mà làm, vì nó code = C nên tất cả đều là Pointer. Thực ra hàm nó nhận vào parameter là kiểu BSTR đó. Nhưng viết vậy khó dùng cho bác và người khác, nên mình đổi thành PWideChar (LPWSTR).
 
Upvote 0
Bác Tuân giải thích về vụ 0x3FFFFFFFF là đúng rồi đó.
Thử test hàm rtcInStrRev này với hàm Pos của Delphi và StrPos PChar của Delphi xem, xem thằng nào nhanh hơn, bao nhiêu %.
Và xem thử hàm Trim của Delphi nó có hơn VBA không ?

VBA vậy là không code multithread được hay code multithread không an toàn rồi. Nó dùng biến toàn cục cho các hàm rtcXXXVar nhiều quá.
Ví dụ biến này, share cho 180 hàm gọi. Kinh. Variant 0 đó.

Em đã chuyển sang Delphi các hàm bâc gửi, em sẽ report tốc độ sau nhé. Thank bác.
Không viết được Multithread trong VBA bác à. Hoặc muốn viết để chạy dạng multithread trong VBA thì phải viết trong DLL ngoài và không dùng tới các hàm VBA.
Bài đã được tự động gộp:

Bác Tuân cứ đúng theo prototype mà làm, vì nó code = C nên tất cả đều là Pointer. Thực ra hàm nó nhận vào parameter là kiểu BSTR đó. Nhưng viết vậy khó dùng cho bác và người khác, nên mình đổi thành PWideChar (LPWSTR).

Em lập trình ứng dụng kết nối tới Application Server nên hiểu mấy kiển dữ liệu API này nên em mới biết và tự chuyển từ LPWSTR - > WideString (BSTR). Nên em hỏi bác là gốc trong C nó để kiểu gì mà sao lại không tương thích ở Delphi khi dùng tên nguyên vậy á.
 
Upvote 0
Hì hì, thấy bác Tuân nắm về Pointer hơi yếu đấy :)
Thực ra ở phương diện mã máy (Assembly) nó cũng không biết Pointer là gì đâu, nó chỉ đơn giản là số, là địa chỉ của vùng nhớ thôi. Truyền ULong (x32) hay ULongLong (64) vào cũng chả chết.
BSTR cũng là pointer tới 1 vùng nhớ WideChar NULL terminated, khác cái là trước vùng nhớ đó 4 bytes (x32) hay 8 bytes (x64) len thôi.
Bác xem source là biết, truyền gì cũng được, miễn là có xxx bytes len đằng trước, theo kiểu BSTR.
 
Upvote 0
Hì hì, thấy bác Tuân nắm về Pointer hơi yếu đấy :)
Thực ra ở phương diện mã máy (Assembly) nó cũng không biết Pointer là gì đâu, nó chỉ đơn giản là số, là địa chỉ của vùng nhớ thôi. Truyền ULong hay ULongLong (64) vào cũng chả chết.

ke ke. Bác cứ thử dùng PWideChar trong hàm rtcInStrRev sẽ ra kết quả sai ngay. Nhưng dùng sang WideString mới trả về kết quả đúng.

Mã:
type
  TFuncVBAPI_rtcInStrRev = function(StringCheck: LPWSTR ;
                                    StringMatch: LPWSTR ;
                                    const Start: Longint = -1;
                                    const Compare: Longint  = vbBinaryCompare): Longint; stdcall;

Hoặc
TFuncVBAPI_rtcInStrRev = function(StringCheck: PWideChar;
                                    StringMatch: PWideChar;
                                    const Start: Longint = -1;
                                    const Compare: Longint  = vbBinaryCompare): Longint; stdcall;

Đều cho ra kết quả sai.

Nhưng đưa về dạng này thì đúng

Mã:
type
  TFuncVBAPI_rtcInStrRev = function(StringCheck: WideString;
                                    StringMatch: WideString;
                                    const Start: Longint = -1;
                                    const Compare: Longint  = vbBinaryCompare): Longint; stdcall;

Bác giải thích sao về pointer ở trên?
 
Upvote 0
Lại ông nội Delphi chuyển kiểu ngầm nữa rồi. Bác chịu khó debug vào mã assembly của hàm rtcInStrRev đi, xem tại sao sai. Chứ tui không có cài Delphi ở đây nên không debug được.
Bác xem trong memory, biến truyền vào có theo cấu trúc BSTR không ? Nếu đúng thì truyền sao miễn đúng vậy là được (Pointer, UInt, Cardinal....)
Xem trong memory 1 biến kiểu WideString nó cấu trúc ra sao ?
Vì mình code C/C++ và Assembly nên cách nhìn của mình nó khác. Bạn Tuân cũng có thể khai báo StringCheck: Cardinal; StringMatch: Cardinal... cũng được, không sao cả, nhưng khi call thì @ vào, và vùng nhớ truyền điạ chỉ vào phải có cấu trúc của BSTR.
Pointer, PChar, PXXXX đều là diễn giải về mặt language cho coder thôi, chứ biên dịch ra mã máy rồi, nó chỉ đơn thuần là số.
 
Upvote 0
Tiếp tục với 2 hàm Upper và Lower, không có gì đặc biệt, chỉ là wrap cho SysAllocStringLen và CharUpper/CharLower API.
Hì hì, tìm ra điểm sai của VBA coder rồi. Và tìm ra lý do tại sao đụng tới tên file Unicode là VBA văng.
Lỗi do VBA code call các hàm API thuần Ansi cho các thao tác file, truyền vào parameter là Unicode string, convert = WideCharToMultiBytes nên sai bét.
Vd tui đang xem hàm VBA Kill = rtcKillFiles. Dùng toàn các hàm Ansi API và của char* của msvcrt.dll
Cái này bà con test thử đi nhé, gõ 1 tên file Unicode đã có vào 1 cell, truyền vào cho VBA, call hàm Kill. Nhớ bẫy lỗi On Error...
Các hàm về Directory cũng vậy, Ansi hết, chà chà....
Bài đã được tự động gộp:

Toàn bộ các hàm VBA về thao tác file, directory... đều là Ansi hết các bạn ơi. Đụng tới tên Unicode file/dir là văng hết đấy. Dùng Scripting thay thế đi.
Tai hại ghê nhỉ, tại sao VBA coder lại duy trì cái code này mà không port qua Unicode API nhỉ ?
 

File đính kèm

  • Untitled.png
    Untitled.png
    30.6 KB · Đọc: 14
Lần chỉnh sửa cuối:
Upvote 0
Mã:
type
  TFuncVBAPI_rtcInStrRev = function(StringCheck: LPWSTR ;
                                    StringMatch: LPWSTR ;
                                    const Start: Longint = -1;
                                    const Compare: Longint  = vbBinaryCompare): Longint; stdcall;

Hoặc
TFuncVBAPI_rtcInStrRev = function(StringCheck: PWideChar;
                                    StringMatch: PWideChar;
                                    const Start: Longint = -1;
                                    const Compare: Longint  = vbBinaryCompare): Longint; stdcall;
Đều cho ra kết quả sai.
Vẫn được.

Bạn thử xem
Mã:
type
  TFuncVBAPI_rtcInStrRev = function(StringCheck: PWideChar;
                                    StringMatch: PWideChar;
                                    const Start: Longint = -1;
                                    const Compare: Longint  = 0): Longint; stdcall;
...
a: Longint;
w1, w2: WideString;
..
w1 := 'hichic\hehe\hihi';
w2 := '\';
...
@pFunc := GetProcAddress(h, 'rtcInStrRev');
a := pFunc(PWideChar(w1),PWideChar(w2));
showmessage(inttostr(a));
Trong Delphi không có vbBinaryCompare :D
 
Upvote 0
Tiếp tục với 2 hàm Upper và Lower, không có gì đặc biệt, chỉ là wrap cho SysAllocStringLen và CharUpper/CharLower API.
Hì hì, tìm ra điểm sai của VBA coder rồi. Và tìm ra lý do tại sao đụng tới tên file Unicode là VBA văng.
Lỗi do VBA code call các hàm API thuần Ansi cho các thao tác file, truyền vào parameter là Unicode string, convert = WideCharToMultiBytes nên sai bét.
Vd tui đang xem hàm VBA Kill = rtcKillFiles. Dùng toàn các hàm Ansi API và của char* của msvcrt.dll
Cái này bà con test thử đi nhé, gõ 1 tên file Unicode đã có vào 1 cell, truyền vào cho VBA, call hàm Kill. Nhớ bẫy lỗi On Error...
Các hàm về Directory cũng vậy, Ansi hết, chà chà....
Bài đã được tự động gộp:

Toàn bộ các hàm VBA về thao tác file, directory... đều là Ansi hết các bạn ơi. Đụng tới tên Unicode file/dir là văng hết đấy. Dùng Scripting thay thế đi.
Tai hại ghê nhỉ, tại sao VBA coder lại duy trì cái code này mà không port qua Unicode API nhỉ ?

VBA chỉ hỗ trợ unicode với các hàm xử lý chuỗi - có quan hệ mật thiết với dữ liệu trên sheet. Các hàm tương tác ngoài thì gần như không hỗ trợ unicode như các hàm anh nói. Shell, Dir, ChDir, MkDir,... trong module VBA.FileSystem. Vấn đề MS không chuyển full unicode trong VBA chắc họ thích mối lái với các công nghệ ngoài của họ :D
Bài đã được tự động gộp:

Vẫn được.

Bạn thử xem
Mã:
type
  TFuncVBAPI_rtcInStrRev = function(StringCheck: PWideChar;
                                    StringMatch: PWideChar;
                                    const Start: Longint = -1;
                                    const Compare: Longint  = 0): Longint; stdcall;
...
a: Longint;
w1, w2: WideString;
..
w1 := 'hichic\hehe\hihi';
w2 := '\';
...
@pFunc := GetProcAddress(h, 'rtcInStrRev');
a := pFunc(PWideChar(w1),PWideChar(w2));
showmessage(inttostr(a));
Trong Delphi không có vbBinaryCompare :D

Em tự dò hằng số bên VBA để tạo bên Delphi với const vbBinaryCompare = 0 :D

Nếu khai báo theo đúng Prototype LPWSTR thì vẫn đúng với điều kiện khi truyền vào là biến kiểu WideString vào tham số bằng cách bao LPWSTR, em cũng test tình huống này ok. Nhưng nếu unit này mình viết sau đó dùng với kiểu điền giá trị không qua biến như sau sẽ bị sai

p := pFunc('hichic\hehe\hihi', '\') thì kết quả lại sai :D

Nên nếu Prototype em để WideString thay cho LPWSTR thì dùng kiểu gì cũng đúng :).
 
Lần chỉnh sửa cuối:
Upvote 0
Khổ ghê, kg như C, ép kiểu trong Dekphi, cũng 1 đống hàm internal được Delphi chèn vào đó.
Nên tui mới nói các bạn bật option build with dcu lên, và tập debug trên cửa sổ Assembly, xem tương quan 1 - 1 giữa code Delphi và ASM, xem Delphi compiler đã làm những gì với code của mình.
Tin tui đi. Nhớ kg lầm thì ép kiểu PWideChar(WideString biến) cũng phải qua hàm internal _UStrToPWChar đó. Hàm này được compiler chèn vào tự động.
Kiểu này phải cài RAD Studio lại quá.
 
Upvote 0
Khổ ghê, kg như C, ép kiểu trong Dekphi, cũng 1 đống hàm internal được Delphi chèn vào đó.
Nên tui mới nói các bạn bật option build with dcu lên, và tập debug trên cửa sổ Assembly, xem tương quan 1 - 1 giữa code Delphi và ASM, xem Delphi compiler đã làm những gì với code của mình.
Tin tui đi. Nhớ kg lầm thì ép kiểu PWideChar(WideString biến) cũng phải qua hàm internal _UStrToPWChar đó. Hàm này được compiler chèn vào tự động.
Kiểu này phải cài RAD Studio lại quá.

Bác chỉ nốt cái Prototype của hàm InStr để em test luôn nhé. Bác cài RAD Studio đi để ae dễ đối chiếu cùng :D
 
Upvote 0
Nếu hàm khai báo với PWideChar thì phải truyền PWideChar rồi. Tại sao lại sợ ép kiểu? Rằng nó phải qua hàm XYZ nào đó? Cứ cho là thế đi (vì ai dám chắc khi cũng không nhớ) thì đã sao? Nếu có WideString mà không dùng PWideChar thì phải dùng cái khác thôi. Để có được Pointer truyền vào hàm. Cái cách khác này chắc gì nhanh hơn hàm XYZ kia?
Cả thế giới người ta ép kiểu, tất nhiên trong những trường hợp bắt buộc, nhưng riêng mình lại khuyên không dùng. Khuyên không dùng hàng loạt hàm trong Delphi. Ông nào ngu mà viết những hàm không đáng dùng trong code nguồn Delphi , chỉ nên loại bỏ? Ngay cả trong code nguồn của Delphi cũng đầy chỗ ép kiểu. Sao cái ông viết những code nguồn Delphi thông minh kia lại không biết là nó không nên dùng nhỉ. Rằng ông không biết rằng ép kiểu thì sẽ phải qua hàm XYZ mà chính ông tự viết? Chả nhẽ ông quên là đã tự viết XYZ? Cả thế giới không biết tới những hàm undocumented chỉ có mỗi mình biết. Lạ thật. Mình thì vẫn nghĩ có rất nhiều người giỏi trên thế giới, hóa ra nhầm. Chỉ có điều những kẻ dốt ấy họ nghĩ ra đủ thứ để mình phải học theo, làm theo. Thế mà họ không biết làm cái mình làm được. Kể cũng lạ.
 
Lần chỉnh sửa cuối:
Upvote 0
Mình đang dùng đt nên chưa kiểm tra, mà VBA InStr sao thì rtcInStr cũng vậy, như rtcInStrRev thôi.
Mai mình kiếm tra và liệt kê hết các hàm VBA bị vụ Ansi này luôn
 
Upvote 0
Có một lỗi tinh vi trong vụ VarPtr này, mà mình nghĩ bạn Tuân cũng đang bị. VarPtr trên 1 object luôn trả lùi hay tăng lên đúng 16 byte so với địa chỉ thực của nó. Debug vào mã ASM của hàm rtcTypeName của VBA mới phát hiện ra. Bạn @Nguyễn Duy Tuân đọc và test thử file này.
Các bạn khác cứ vọc code, xem cho biết, giải thích ra rất chi "nà" dài dòng.
Và không biết có ai "thét mét" vụ StrConv vbFromUnicode không nhỉ ?
 

File đính kèm

Lần chỉnh sửa cuối:
Upvote 0
Cứ để ở đây, quay lại vụ các hàm VBA Ansi. Đã kiểm tra và các hàm VBA sau không dùng được cho tên file/thư mục Unicode, hay là có ký tự Unicode. Ví dụ tên file như thế này: "toi la mot file.txt" thì hợp lệ, vẫn dùng được, nhưng tên file này thì văng: "tôi là một file.txt". Tên thư mục cũng vậy.
Vì VBA coder của MS dùng hàm API WideCharToMultiByte để convert từ Unicode qua Ansi, nên các ký tự có dấu sẽ bị convert sai, và nhiều ký tự Unicode khác nữa.
Các hàm trong module VBA.FileSystem mà cứ có nhận path là string thì văng hết, gồm các hàm sau:
1. ChDir = rtcChangeDir
2. ChDrive
3. Dir
4. FileCopy
5. FileDateTime
6. FileLen
7. GetAttr
8. Kill
9. MkDir
10. RmDir
11. SetAttr
12. Open statement
 
Upvote 0
Có một lỗi tinh vi trong vụ VarPtr này, mà mình nghĩ bạn Tuân cũng đang bị. VarPtr trên 1 object luôn trả lùi hay tăng lên đúng 16 byte so với địa chỉ thực của nó. Debug vào mã ASM của hàm rtcTypeName của VBA mới phát hiện ra. Bạn @Nguyễn Duy Tuân đọc và test thử file này.
Các bạn khác cứ vọc code, xem cho biết, giải thích ra rất chi "nà" dài dòng.
Và không biết có ai "thét mét" vụ StrConv vbFromUnicode không nhỉ ?

Em đã chạy file anh gửi và thấy đúng là cách truyền con trỏ trong VBA có vấn đề gì đó. 16 byte như là kích thước của Variant 32-bit? Em đã test cả trên VBA 32 và 64 đều như vậy.
 
Upvote 0
Trước giờ tui nghĩ có lẽ hàm Len và LenB trong VBA có lẽ là hàm ngắn gọn, nhanh nhất của VBA chứ. Nhưng giờ xem thì lại không phải. Lại chậm, to.
Do ông nội tham số Variant truyền vào mà 2 hàm này phải gánh vậy.

1. Len VBA = rtcLenCharVar
Mã:
struct tagVARIANT *__stdcall rtcLenCharVar(struct tagVARIANT *pvDst, struct tagVARIANT *pvSrc)
{
    __vbaLenVar(pvDst, pvSrc);
    return pvDst;
}

2. LenB VBA = rtcLenVar
Mã:
struct tagVARIANT *__stdcall rtcLenVar(struct tagVARIANT *pvDst, struct tagVARIANT *pvSrc)
{
    __vbaLenVarB(pvDst, pvSrc);
    return pvDst;
}
2 hàm _vbaLenVarX là 2 hàm internal của VBA, không export ra ngoài, và thôi, nói ra thì "buồm", trong ruột nó khiếp lắm. Đa năng, đa mang thì phải chịu vậy thôi.

Các bạn có để ý là VBA không có hàm Len$ không, tức hàm Len nhận vào biến có kiểu tường minh là string đó. Thật ra nó có, nhưng internal, không export ra ngoài, nó là hàm __vbaLenBstr, trả về số ký tự, không phải số bytes nhé các bạn.
Mã:
DWORD __stdcall __vbaLenBstr(BSTR bstrSrc)
{
    DWORD result; // eax@1

    result = bstrSrc;
    if ( bstrSrc )
    {
        result = *(bstrSrc - 1) >> 1;
    }
    return result;
}
Bài tập cho bác @kieu manh đây, hàm này có lý đấy, viết hàm LenStr = Delphi cho VBLibrary, nhận vào BSTR (PWChar), trả về số ký tự, y chang như hàm __vbaLenStr vậy đó. LenStrB tương tự, call hàm LenStr và trả về * 2.
Bài đã được tự động gộp:

Đáng lý phải post bên topic Optimize VBA, nhưng thôi, nó cũng thuộc internal, undocument VBA, nên post tiếp ở đây.
Chúng ta sẽ nói tiếp tại sao tránh các hàm thao tác chuỗi như Left, Mid, Right... là các hàm không có $ ở cuối. Mà nên dùng tường minh các hàm có $ ở cuối.
Ví dụ: Với hàm VBA Left ta không dùng, chỉ dùng khi biến truyền vào là kiểu Variant và trả về cho biến kiểu Variant. Nếu biến truyền vào là string và biến nhận trả về kiểu là string thì dùng tường minh VBA Left$. Thậm chí biến nhận trả về của Left$ là Variant cũng được, VBA sẽ tự chuyển kiểu cho ta, nhưng sẽ chậm hơn chút.
Tức "nà" nên nhớ là khi biến truyền vào kiểu string thì luôn dùng hàm VBA xxx$ khi có hàm đó và khi có thể, sẽ nhanh hơn rất nhiều, ít nhất 3, 4 lần đó.
 
Lần chỉnh sửa cuối:
Upvote 0
đang lu xu bu làm cái bài tập về nhà ở bên kia thấy rối ... vợ keo tối ba Mạnh ôm máy tính mà ngủ he ... thấy hơi nhột ! :p ;)
đang coi cái này mà hại não quá ???
Capture.JPG
 
Lần chỉnh sửa cuối:
Upvote 0
Cái rtcTypeName đó hả, cái đó mà không làm được nữa thì cúp máy ôm vợ liền đi, để mai mốt làm :p
Uhm, coi kỹ cái topic Debugging Applications đó.
Nói thật ra thì mình đã gặp vô số coder rồi, trên mạng, ngoài đời, công việc, mà thấy là gần như 8-90% trong số họ rất yếu về kỹ năng debug. Thậm chí có người còn không biết. Chỉ biết bấm Compile rồi Run. Có lỗi thì nhét tá lã Message Box vào. Nên đa số bó tay khi gặp bug khó.
Cũng 1 phần do mình là dân "rờ em" nữa, nên mình đặt nặng, rất nặng về kỹ năng debug.
 
Lần chỉnh sửa cuối:
Upvote 0
222250
Thấy trong Help có mô tả
 
Upvote 0
Cũng hơi ngán đọc mã C, ASM, Delphi rồi, thôi tui quay lại với thằng em út "Vớ Bở À" :)
Để minh họa cho vụ các hàm có $ và không có $, tui làm cái file test nho nhỏ, bà con chỉ việc mở lên, bấm, chờ chút, coi kết quả chơi cho vui :)
Tui chỉ làm 2 hàm ví dụ là Chr và Left, các hàm khác tương tự, bà con tự thêm vào test được.
Nhớ đọc kỹ code và comment nhé. Không thì có người không tin, nói tui hù, nói như két, kkk :p
Các hàm khác bà con có thể test còn nhiều lắm, gồm các hàm có chử B ở cuối và không có B. Tui liệt kê ra cho bà con:
1. Format, Format$
2. Left, Left$
3. LTrim, LTrim$
4. Mid, Mid$
5. Right, Right$
6. RTrim, RTrim$
7. Space, Space$
8. String, String$
9. Trim, Trim$
10. UCase, UCase$
Với các hàm không có $, bà con phải test 4 trường hợp nhé: Variant = Variant, String = Variant, Variant = String và String = String
Còn có $ thì test type tường minh, String = String.

Đã Variant thì Variant hết, String thì String hết, không thì VBA lại mệt mỏi, mất time chuyển kiểu nữa. Nên trong ví dụ, bạn sẽ thấy String = LCase(Variant) cuối cùng lại chậm nhất. String = LCase$(String) là nhanh nhất.

Chúc bà con test vui và rút ra được kinh nghiệm cho mình :)
Bài đã được tự động gộp:

Để ý vấn đề này, khi thao tác bằng "Vớ Bở À" với dữ liệu lớn, các bạn sẽ cải thiện đáng kể tốc độ mà không cần phải sửa đổi code gì cả, chỉ cần buồn buồn thêm $ vào vài chỗ thôi, xem nó nhanh hay chậm hơn, nhanh hơn thì giữ, chậm hơn thì xóa :p
Nhớ các quy tắc rất đơn giản sau:
1. Tránh được kiểu Variant thì cố tránh, gái nhiều con thì sao bằng gái 1 con được :)
2. Dùng đúng kiểu truyền vào và trả về của hàm, tránh cho VBA phải convert kiểu
3. Dùng
$: đô la thần thánh của đế quốc Mẽo dãy hoài éo chết :p
 

File đính kèm

Lần chỉnh sửa cuối:
Upvote 0
Cũng hơi ngán đọc mã C, ASM, Delphi rồi, thôi tui quay lại với thằng em út "Vớ Bở À" :)
Để minh họa cho vụ các hàm có $ và không có $, tui làm cái file test nho nhỏ, bà con chỉ việc mở lên, bấm, chờ chút, coi kết quả chơi cho vui :)
Tui chỉ làm 2 hàm ví dụ là Chr và Left, các hàm khác tương tự, bà con tự thêm vào test được.
Nhớ đọc kỹ code và comment nhé. Không thì có người không tin, nói tui hù, nói như két, kkk :p
Các hàm khác bà con có thể test còn nhiều lắm, gồm các hàm có chử B ở cuối và không có B. Tui liệt kê ra cho bà con:
1. Format, Format$
2. Left, Left$
3. LTrim, LTrim$
4. Mid, Mid$
5. Right, Right$
6. RTrim, RTrim$
7. Space, Space$
8. String, String$
9. Trim, Trim$
10. UCase, UCase$
Với các hàm không có $, bà con phải test 4 trường hợp nhé: Variant = Variant, String = Variant, Variant = String và String = String
Còn có $ thì test type tường minh, String = String.

Đã Variant thì Variant hết, String thì String hết, không thì VBA lại mệt mỏi, mất time chuyển kiểu nữa. Nên trong ví dụ, bạn sẽ thấy String = LCase(Variant) cuối cùng lại chậm nhất. String = LCase$(String) là nhanh nhất.

Chúc bà con test vui và rút ra được kinh nghiệm cho mình :)
Bài đã được tự động gộp:

Để ý vấn đề này, khi thao tác bằng "Vớ Bở À" với dữ liệu lớn, các bạn sẽ cải thiện đáng kể tốc độ mà không cần phải sửa đổi code gì cả, chỉ cần buồn buồn thêm $ vào vài chỗ thôi, xem nó nhanh hay chậm hơn, nhanh hơn thì giữ, chậm hơn thì xóa :p
Nhớ các quy tắc rất đơn giản sau:
1. Tránh được kiểu Variant thì cố tránh, gái nhiều con thì sao bằng gái 1 con được :)
2. Dùng đúng kiểu truyền vào và trả về của hàm, tránh cho VBA phải convert kiểu
3. Dùng
$: đô la thần thánh của đế quốc Mẽo dãy hoài éo chết :p

Em cũng biết điều này từ trước vì đã lập trình với Variant nên biết kiểu gì cũng phải qua các hàm chuyển đổi nên chậm hơn nếu không phải chuyển đổi. Ví dụ của anh là mình họa dễ hiểu nhất cho các bạn muốn tối ưu tốc độ trong VBA đây. Làm dữ liệu ít thì cảm thấy không đáng kể chứ khối dữ liệu nhiều thì nó là sự khác biệt rõ ràng :).
 
Upvote 0
Về lý thuyết thì phải nhớ là $ nhanh hơn. Nhưng cũng nên nhớ là độ chênh lệch nằm ở hàng nào. Hàng chục, hàng trăm, hàng triệu?

Về thực tế thì trừ phi thao tác hàng chục triệu lần, hàng trăm triệu lần còn không thì khỏi nghĩ cho đầu thanh thản. Nếu thao tác hàng triệu lần mới tiết kiệm được 20 ms thì thôi dẹp, chả bõ. Mà thường ít khi phải thao tác hàng triệu lần. Tất nhiên phải ý thức được sự chênh lệch. Nhưng nó là hạng nào. Tiết kiệm 1 ms cho hàng trăn ngàn lần gọi thì khỏi lăn tăn. Tất nhiên nhanh hơn 1 ms cũng là nhanh nhưng có những sự chênh lệch nó nhỏ tới mức nó chỉ là đề tài trên bàn nhậu của các Giáo sư bàn về lý thuyết thôi. À quên, Gia sư.
 
Upvote 0
Về lý thuyết thì phải nhớ là $ nhanh hơn. Nhưng cũng nên nhớ là độ chênh lệch nằm ở hàng nào. Hàng chục, hàng trăm, hàng triệu?

Về thực tế thì trừ phi thao tác hàng chục triệu lần, hàng trăm triệu lần còn không thì khỏi nghĩ cho đầu thanh thản. Nếu thao tác hàng triệu lần mới tiết kiệm được 20 ms thì thôi dẹp, chả bõ. Mà thường ít khi phải thao tác hàng triệu lần. Tất nhiên phải ý thức được sự chênh lệch. Nhưng nó là hạng nào. Tiết kiệm 1 ms cho hàng trăn ngàn lần gọi thì khỏi lăn tăn. Tất nhiên nhanh hơn 1 ms cũng là nhanh nhưng có những sự chênh lệch nó nhỏ tới mức nó chỉ là đề tài trên bàn nhậu của các Giáo sư bàn về lý thuyết thôi. À quên, Gia sư.
Có thể do người ta kiếm tiền bằng cách cải thiện tốc độ của code. 1ms đối với bác không nhanh nhưng nó có thể là chỉ tiêu (benchmark) lấy tiền.
Ngược lại NẾU tôi dạy tiếng Anh thì càng dài tôi càng ăn tiền. Động từ cần chuyển sang dạng past participle tôi không gõ tắt, cắt "ed" ở sau giảm thời gian gõ 0,1 giây nhưng từ ngữ câu cú bị ngắn đi 2 ký tự. :p:p:p
 
Upvote 0
Excel là môi trường tính toán, logic tương tác các biểu thức ở các vùng rất tinh vi, nó như tế bào trong cơ thể. Nên người lập trình VBA viết các hàm tham gia trong các biểu thức tính toán trên bảng tính sẽ luôn phải chú ý đến tốc độ, xử lý bộ nhớ. Bao năm Excel cải tiến cũng rất chú trọng tốc độ và gia tăng khả năng lưu trữ. Đó là nguyện vọng của khách hàng.
 
Upvote 0
Không ai coi thường tốc độ. Nhưng nó ở hạng nào. Nếu ở hạng phải chạy hàng triệu lần để tiết kiệm 20 ms thì nó chả là gì cả.

Nếu chạy 100 000 lần mà tiết kiệm được 1 s = 1000 ms thì đáng suy nghĩ. Nhưng chạy 1 triệu lần mới tiết kiệm được 20 s? Bàn lý thuyết thì được thôi.
 
Upvote 0
Ngược lại NẾU tôi dạy tiếng Anh thì càng dài tôi càng ăn tiền. Động từ cần chuyển sang dạng past participle tôi không gõ tắt, cắt "ed" ở sau giảm thời gian gõ 0,1 giây nhưng từ ngữ câu cú bị ngắn đi 2 ký tự. :p:p:p
Kiểu cứ một chữ phải viết đúng, phát âm thật chuẩn mới coi là xong, mới dạy chữ mới thì bác ngồi đếm tiền mỏi tay mà con người ta vẫn chưa học xong 1 chữ. :D
 
Upvote 0
Kiểu cứ một chữ phải viết đúng, phát âm thật chuẩn mới coi là xong, mới dạy chữ mới thì bác ngồi đếm tiền mỏi tay mà con người ta vẫn chưa học xong 1 chữ. :D
Bán chữ đời nay không ăn tiền dễ như bác tưởng đâu.
Ví dụ tôi dạy từ "sorry". Học trò nó cứ nhất định muốn viết thành "sr" hay "sry". Tôi mất nửa tiếng đồng hồ để giải thích về luật chúng rằng "viết tắt, trước khi có sự thoả thuận, ví dụ như 1. cặp tình nhân, hay 3. nhóm bạn thân với nhau, thì còn lại chỉ được dùng 3. mặc định khi ta đứng trên địa vị người kia"
Học trò đợi tôi nói xong, không cần suy nghĩ thêm, trả lời ngay:
"thế thầy nghĩ ở đâu bố mẹ con có tiền trả cho thầy? đương nhiên là những lúc viết tắt ấy con áp dụng trường hợp thứ ba mà thầy nói rồi"
 
Upvote 0
Tiếp tục với chủ đề các bạn, ai bà 8 kệ bà 8.
Như tôi đã nói, hàm Len có thể là hàm nhanh nhất của VBA, nhưng lạ là tại sao VBA không cung cấp cho chúng ta hàm Len$.
Tui đã nghĩ, hay là viết hàm StrLen từ ngoài 1 Dll, rồi gọi trong VBA, sẽ nhanh hơn nhiều, và đã gợi ý đại ca @kieu manh viết hàm này.
Nhưng viết Dll thì lại rất phiền phức. Nên thử viết hàm = VBA thôi xem sao, xem nhanh hơn Len của VBA không ?
Và tui thử viết, nhưng thành thật chia "BUỒM", code tui lại chậm gấp đúng 10 lần của VBA. Quái, sao lạ vậy, bật ASM_DEBUG flag lên, debug vào trực tiếp mã VBA đã qua compile.
Hì hì, bởi vậy, đừng dễ nghĩ là vượt qua compiler bên trong VBExxx.dll của VBA được. Nó thừa thông minh để lệnh gọi Len(string) được inline trực tiếp với vài dòng mã ASM vào, không hề call tới VBExxx.dll.
Đó chính là lý do tại sao VBExxx.dll không hề export hàm nào liên quan đến Len(string).
Tui làm vd test hàm StrLen của tui và hàm Len VBA, các bạn có thể xem, vọc được gì thì vọc.
Và chú ý luôn nhé, Len(string) nhanh gấp đôi Len(Variant) nhé.

Đủ đồ chơi rồi, chúng ta sẽ dùng vào ví dụ thực tế nhé :)
 

File đính kèm

Upvote 0
Tiếp tục với chủ đề các bạn, ai bà 8 kệ bà 8.
Như tôi đã nói, hàm Len có thể là hàm nhanh nhất của VBA, nhưng lạ là tại sao VBA không cung cấp cho chúng ta hàm Len$.
Tui đã nghĩ, hay là viết hàm StrLen từ ngoài 1 Dll, rồi gọi trong VBA, sẽ nhanh hơn nhiều, và đã gợi ý đại ca @kieu manh viết hàm này.
Nhưng viết Dll thì lại rất phiền phức. Nên thử viết hàm = VBA thôi xem sao, xem nhanh hơn Len của VBA không ?
Và tui thử viết, nhưng thành thật chia "BUỒM", code tui lại chậm gấp đúng 10 lần của VBA. Quái, sao lạ vậy, bật ASM_DEBUG flag lên, debug vào trực tiếp mã VBA đã qua compile.
Hì hì, bởi vậy, đừng dễ nghĩ là vượt qua compiler bên trong VBExxx.dll của VBA được. Nó thừa thông minh để lệnh gọi Len(string) được inline trực tiếp với vài dòng mã ASM vào, không hề call tới VBExxx.dll.
Đó chính là lý do tại sao VBExxx.dll không hề export hàm nào liên quan đến Len(string).
Tui làm vd test hàm StrLen của tui và hàm Len VBA, các bạn có thể xem, vọc được gì thì vọc.
Và chú ý luôn nhé, Len(string) nhanh gấp đôi Len(Variant) nhé.

Đủ đồ chơi rồi, chúng ta sẽ dùng vào ví dụ thực tế nhé :)

Công nhận hàm của VBA chạy nhanh nhất nếu so sánh với hàm tự viết hoặc hàm API của Windows. Em chưa thử với hàm của Delphi.
Em khai báo hàm API trong thư viện Kernel32.dll của Windows thì đạt giải chạy chậm nhất :D

Private Declare PtrSafe Function lstrlenW Lib "kernel32" (ByVal s As String) As Long
 
Upvote 0
Vì lstrlenX đều phải quét tìm NULL cuối chuỗi hết Tuân ơi, nên châm. Còn BSTR thì như code StrLen mình và code ASM của VBEx.dll, nó lùi 4 byte lấy số bytes len thôi, nên nhanh. Code mình thua nó do là code VBA và call thêm API nữa.
 
Upvote 0
Thôi tranh thủ post cái này chút rồi fix bug tiếp với bác Tuân :)
Không biết có ai thét mét là tại sao tui không nói về undocument của User32.dll và GDI32.dll không nhỉ ?
Hè hè, thực ra đây là 2 thư viện cho GUI và xử lý GDI (Graphic Device Interface) của Windows, nằm trên kernel32.dll và ntdll.dll. Giới coder thế giới làm ở tầng trên đào nát nó rồi, nên chả còn gì hay để post cả. Và MS sợ mấy anh này quá nên viết document hết 99% cho nó rồi.
Trong user32.dll có 1 hàm tôi đã từng nói, mà tôi đã dùng nó rất nhiều, từ thời Win95 tới giờ, và giờ Win7, Win10 vẫn còn. Nó cực nhanh.
Sau này anh MS mới document, nhưng ảnh lại nói khác đi và không khuyến khích coder dùng.
Nếu các bạn code dùng GetWindowText nhiều thì nên chuyển qua dùng InternalGetWindowText.
Prototype và source nó như sau, trong user32.dll:
Mã:
int __stdcall InternalGetWindowText(HWND hWnd, LPWSTR pString, int cchMaxCount)
{
    int result; // eax@1

    result = NtUserInternalGetWindowText(hWnd, pString, cchMaxCount);
    if ( !result )
        *pString = 0;
    return result;
}
Hàm NtUserInternalGetWindowText là hàm undocument trong ntdll.dll, nó switch từ User mode sang Kernel mode, gọi hàm trong win32k.sys.
Win32k.sys là core ở Kernel của Windows, phục vụ tất cả các yêu cầu của GUI và GDI (user32.dll và GDI32.dll) đưa xuống. Nó rất hay, rất nhiều undocument, hì hì, nhưng tiếc là các bạn ở tuốt luốt trên cao "Vớ Bở À" dùng không được đâu.
 
Upvote 0
Hì hì, khỏe rồi, mất mấy ngày liền liên tục debug toàn mã ASM cho bác ấy, đã tìm ra bug, giờ bác ấy chỉ sửa code bác ấy thôi, và vì code bác ấy thuộc sp của cty bác ấy nên không thể share mình được. Mình chỉ debug trên file dll thôi.
KKK, khỏe, quay lại với "Ưn đồ cú mần" API thôi.
Giờ tạm nghĩ với dll của "Vớ Bở À", qua internal của cách gọi API từ VBA nó nà như thế lào :) Bà con hay mắc cái sai gì, tại sao.
 
Upvote 0
Rảnh rỗi rồi, tiếp với un đồ cú mần, với code VBA cho bạn nào dùng VBA với InternalGetWindowText. Hàm này thì phải nói cực nhanh rồi. vì mọi API trên user32.dll mà có get text đều phải đi qua nó. Tuy nhiên cái gì cũng có cái giá của nó, có nhiều Window thuộc tầng user quản lý thì nó không lấy được, nên tôi đã comment rõ trong code cách lấy khác khi failed. Xem như bài tập làm thêm VBA với API của các bạn, và chừa 1 bug nhỏ cho các bạn phát hiện và sữa.
Hì hì, muốn ăn phải lăn vào bếp mà :)
Mã:
Option Explicit

#If VBA7 Then
    Private Declare PtrSafe Function InternalGetWindowText Lib "user32.dll" (ByVal hWnd as LongPtr, ByVal pwszText as LongPtr, ByVal cchMaxCount as Long) as Long
#Else
    Private Declare Function InternalGetWindowText Lib "user32.dll" (ByVal hWnd As Long, ByVal pwszText As Long, ByVal cchMaxCount As Long) As Long
#End If

' Hàm này có the get Window text cua Window thuoc process khác
' Không check tính hop le cua tham so hWnd de bảo đảm tốc độ
#If VBA7 Then
Public Function VBAGetWndText(ByVal hWnd As LongPtr, ByRef strWndText As String) As Boolean
#Else
Public Function VBAGetWndText(ByVal hWnd As Long, ByRef strWndText As String) As Boolean
#End If
    Const MAX_BUFFER_SIZE As Long = 1024

    Dim strBuf As String
    Dim lRet As Long

    VBAGetWndText = False

    strBuf = String$(MAX_BUFFER_SIZE, vbNullChar)

    lRet = InternalGetWindowText(hWnd, StrPtr(strBuf), MAX_BUFFER_SIZE)
    If (lRet >= MAX_BUFFER_SIZE - 1) Then
        ' Buffer nho, cap them
        strBuf = String$(MAX_BUFFER_SIZE * 2, vbNullChar)
        lRet = InternalGetWindowText(hWnd, StrPtr(strBuf), MAX_BUFFER_SIZE * 2) ' Chac chan se du :P
    ElseIf lRet = 0 Then
        Debug.Print "InternalGetWindowText failed. Làm on check IsWindow và dung SendMessageW voi WM_GETTEXTLENGTH và WM_GETTEXT"
        Exit Function
    End If

    strWndText = Left$(strBuf, lRet)
    VBAGetWndText = True
End Function

Public Sub Test_VBAGetWndText()
    Dim str As String

    If VBAGetWndText(Application.hWnd, str) Then
        MsgBox str
    End If

    If VBAGetWndText(&H3001CA, str) Then ' Dùng WinSpy++ de tim hWnd can lay. Download tai http://www.catch22.net :)
        MsgBox str
    End If
End Sub
 
Lần chỉnh sửa cuối:
Upvote 0
Coi cũng mê lắm ... nhưng trình của Mạnh thuộc hạng ABC nên cũng ko biết ứng dụng sao ... vãi kinh :p ;)
 
Upvote 0
Lần chỉnh sửa cuối:
Upvote 0
Rảnh rỗi rồi, tiếp với un đồ cú mần, với code VBA cho bạn nào dùng VBA với InternalGetWindowText. Hàm này thì phải nói cực nhanh rồi. vì mọi API trên user32.dll mà có get text đều phải đi qua nó.

Lấy cái title của windows này ứng dụng vô việc nào vậy bác CuAnh?
 
Upvote 0
Thì cái nào phải dùng GetWindowText hay SendMessage WM_GETTEXT thì dùng nó thay.
Uhm mà nói với @ongke0711 chứ, với VBA của Office thì không cần đâu, biết cho vui thôi. Dùng cho viết ứng dụng = C/++, Delphi, C# thì được.
 
Lần chỉnh sửa cuối:
Upvote 0
Lần chỉnh sửa cuối:
Upvote 0
Upvote 0
Code cho cái bài của bạn @giaiphap@ongke0711 cho xong, thấy có nhu cầu cần dump memory các kiểu lên để xem, check, nên lọ mọ ngồi code hàm DumpMem, DumpString và DumpVariant (cùng 1 đống hàm con khác).
Hì hì, xong, test OK, thấy "mình ưng cái bụng người đồng bào mình" quá :) nên share lên đây để cùng bà con vọc chơi. Hàm này rất mạnh và trâu bò, hì hì, không dễ có thể đánh gục được nó đâu. Nhiệm vụ nó dump 1 memory address với số byte chỉ định ra string và output luôn ra cửa sổ Immediate của VBA, cùng của sổ DbgView huyền thoại của Windows.
Bà con có thể dùng hàm này và các hàm đi kèm khi cần debug 1 biến nào đó, cần xem rõ nó nằm trong memory như thế nào, chứa cái gì. Nó tựa tựa như cửa sổ Memory View trong các trình debugger của các tool debug hay Visual Studio IDE, Delphi IDE... hay các tool Hex Editors đó.
Hoặc dùng trong Excel với hàm DumpString để xem len của text trong cell là bao nhiêu, khớp với hàm LEN Excel không, có ký tự dị dạng gì không, mã nó là bao nhiêu...
Về phần convert mem to hex và hex to mem cho bạn @giaiphap thì coi như tạm được, tốc độ tạm chấp nhận được, dù không ưng ý lắm. Không có gì là tuyệt đối mà.
Bà con nhấn Alt-F11, vào modTest, thấy có cái Sub Test_DumpMem, bà con uncomment, test thỏa mái luôn, xem các kiểu dữ liệu của VBA nó lưu trữ ra sao. Ấn Ctrl-G để mở cữa sổ Immediate Window lên xem kết quả dump ra nhé.
Nhớ để ý address của các biến trong stack của VBA nhé, top-down đấy.
Nói chung là dùng pointer trong VBA cực quá, chán bỏ xừ.
Mong được góp ý, thét mét, phê bình lẫn "chửi bới" của bà con :)

Quên, cí 1 chỗ kg tối ưu trong hàm DumpMem, và 1 bug nho, mai sữa
 
Lần chỉnh sửa cuối:
Upvote 0
Hì hì, nhờ DumpMem mà đã tìm ra vụ tại sao 10 byte thêm cho 1 biến kiểu string của VB/VBA như trong document nó nói, 14 byte trên x64. Nghĩ ra 1 hàm chưa có trên Google, hoàn toàn đánh lừa VBA được để nhận 1 pointer ngoại lai BSTR trên C, WideString của Delphi làm con kiểu string chính thức của VBA, 100% hợp lệ. Hết lo memory leak, VBA tự động dọn dẹp, free cho mình.
Bà con thử google BSTR2Str nhé, hay BSTR to VBA string xem
 
Lần chỉnh sửa cuối:
Upvote 0
Chỉ sơ ý dư 1 dòng code trong vòng For mà hàm chạy chậm gấp đôi, xóa nó đi là giảm liền 1 nữa. Điểm nghẽn, thắt cổ chai của hàm DumpMem và HexStr2ByteArray1D là 3 hàm VBA: Hex$, Mid$ và CByte. Chưa tìm ra cách nào, hàm nào thay thế nhanh hơn.
Fix thêm vài bug nhỏ và bổ sung hàm VBA BSTR2Str. Bà con download và lại test nhé. Sub Test_DumpMem. An tâm test, cứ nghĩ ra mọi kiểu, mọi biến, mọi loại, mọi trường hợp... Gõ vào gọi DumpMem/DumpString/DumpVariant thôi.
Còn DumpObj và DumpArray nữa, ai siêng ráng mò con đường đi tới đi tới... dò từ DumpMem đầu tiên là sẽ ra hết :)
 

File đính kèm

Lần chỉnh sửa cuối:
Upvote 0
Bà con chạy test với đoạn code sau để hiểu rõ hơn về vbNullString, vbNullChar và "" (chuổi rỗng)
Mã:
Option Explicit

Public Sub Test_DumpMem()
    Dim str As String

    Debug.Print "str chua duoc khoi tao"
    DumpString str, True

    Debug.Print "str = vbNullString"
    str = vbNullString
    DumpString str, True
    DumpMem VarPtr(str), PTR_SIZE
    Debug.Print Len(str)
    Debug.Print LenB(str)

    Debug.Print "str = vbNullChar"
    str = vbNullChar
    DumpString str, True
    DumpMem VarPtr(str), PTR_SIZE
    Debug.Print Len(str)
    Debug.Print LenB(str)

    Debug.Print "str = """""
    str = ""
    DumpString str, True
    DumpMem VarPtr(str), PTR_SIZE
    Debug.Print Len(str)
    Debug.Print LenB(str)

    str = ""
    Debug.Print str = vbNullString
    Debug.Print str = vbNullChar
    Debug.Print vbNullString = vbNullChar
End Sub
Kết quả in ra trên máy tôi, x32:
Mã:
str chua duoc khoi tao
001FF42C: 00 00 00 00
str = vbNullString
001FF42C: 00 00 00 00
001FF42C: 00 00 00 00
0
0
str = vbNullChar
02 00 00 00 00 00 00 00
001FF42C: BC C6 2B 05
1
2
str = ""
00 00 00 00 00 00
001FF42C: 6C 4E 30 00
0
0
True
False
False
Nghĩa là với VBA string, biến nhỏ nhất chứa ít bộ nhớ nhất là biến chưa khởi tạo, hay = vbNullString. Trong ruột nó hoàn toàn là 4 (8) byte 00. Kế thứ 2 là đúng 10 (14) byte, biến string = "", và thứ 3 là ông vbNullChar: 12 (16) byte. Trên x64 thì + thêm 4 nhé.
Với Len/LenB của str = "" và str = vbNullString thì là 0/0, nhưng với ông vbNullChar thì là 1/2.
Và các phép so sánh "" = vbNullString = vbNullChar cho ra các kết quả khác nhau.
Nên các bạn cẩn thận với ông vbNullChar này. Theo tôi tốt nhất là cứ dùng vbNullString khi khởi tạo và dùng xong biến, giải phóng nó.
VBA string truyền xuống cho API cũng không cần dùng tới vbNullChar, vì bản thân VBA string luôn luôn sure 100% có 2 byte 00 chặn ở đít rồi :)
 
Lần chỉnh sửa cuối:
Upvote 0
Có vụ ByRef và ByVal này cũng hay ho, thú vị đây. Bà con có để ý là hàm DumpString tôi đã cố tình comment và để ByRef không ?
Dưới đây là kết quả DumpString ByRef, kéo hết 2 cột của Excel, sau đó Remove Duplicates, chỉ còn đúng vỏn vẹn 2 em. Tại sao lại là 2 em address mà không phải là 1, tại sao cứ xen kẽ giua 1 & 2, trong khi chuỗi gốc bên Excel là 1 ABCDEFGH
Ông Excel và VBA bắt tay lắm trò đây. VBA chỉ cấp đúng 2 vùng nhớ cho chuỗi truyền xuống từ Excel. Điều này hoàn toàn củng cố thêm nghi ngờ của tôi về khả năng về cache memory string của VBA, vì lúc trước vụ CopyMemory VARIANT string của bác Tuân, tôi đã tìm mọi cách phá erase, delete cái string nguồn, cho cấp memory hết cở, chạy tải nặng mà string trong dãy đích vẫn hoàn toàn hợp lệ, không bị dập. Cuối cùng bó tay, bỏ.
 

File đính kèm

  • 1.png
    1.png
    66.9 KB · Đọc: 12
  • 2.png
    2.png
    6 KB · Đọc: 12
Lần chỉnh sửa cuối:
Upvote 0
Sau khi sữa hàm DumpString từ ByRef sang ByVal, giờ nó chỉ lòi ra thêm đúng 1 string nữa thôi. 3. Hay nhỉ ?
À quên chứ, VBA cache hay oleaut32.dll cache. Hình như oleaut32.dll cache mới đúng, tôi nhớ có 1 biến môi trường set lên bắt thằng oleaut32.dll này không cache. Để tìm lại với ông Google.
Bài đã được tự động gộp:

Tìm ra rồi, biến môi trường OANOCACHE, new set nó lên 1 và test lại xem.
 

File đính kèm

  • 3.png
    3.png
    7.6 KB · Đọc: 12
Lần chỉnh sửa cuối:
Upvote 0
Sau khi set biến môi trường OANOCACHE, khởi động lại Excel, test lại thì từ 1048576 xuống còn 2695. Vẫn không là duy nhất, không hiểu nổi luôn Excel, VBA, oleaut32.dll của Windows nó bắt tay nhau làm cái giống gì ở trong.Nhưng sau đó cứ nhấn Remove Duplicates là nó cứ xuống dần xuống dần.
Thua :)
PS: Hì hì, mỗi lần chạy nó ra 1 khác bà con ơi, lần này tui lại còn hơn 5000
 

File đính kèm

  • 1.png
    1.png
    23.8 KB · Đọc: 4
Lần chỉnh sửa cuối:
Upvote 0
Có bạn nào máy CPU 4 nhân không nhỉ, test lại giúp xem: DumpString ByRef, không biến môi trường OENOCACHE, ABCDEFGH, kéo hết 2 cột A, B, Remove Duplicates xem còn bao nhiêu ? Có phải 4 không ?
Máy tui 2 CPU 2 nhân, máy ai 1 nhân test thử xem ?
 
Upvote 0
Bạn @kieu manh , test thử đi bạn. Gỏ ABCDEFGH hay bất kỳ vào A1, kéo hết cột A, bên B1 gõ = DumString(A1, True), rồi kéo hết cột B. Đợi chạy xong click Data, Remove Duplicates xem còn bao nhiêu hàng ?
 
Lần chỉnh sửa cuối:
Upvote 0
đang kẹt chút tối hay sáng mai làm cho.... giờ làm nó đơ máy chết mất
 
Upvote 0

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

Back
Top Bottom