Undocument Windows API và VBA

Liên hệ QC

ThangCuAnh

Mới rờ Ét xeo
Tham gia
1/12/17
Bài viết
896
Được thích
792
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:
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

  • test_DumpMem.xlsm
    49.8 KB · Đọc: 16
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
Web KT

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

Back
Top Bottom