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:
Hình bên kia với kết quả dump nó ra rõ ràng vậy mà Thủy chưa hiểu hả trời
 
Upvote 0
Tiếp tục Todo list, do bà con không có góp ý gì cả, nên tui đưa thêm cái của tui vào.
8. Bản chất thật sự của ByVal ByRef là gì, ByVal biến kiểu String nó hành VBA khổ ra sao, sự nguy hiểm khi truyền ByVal cho biến kiểu Variant và Object ? Bản chất của hàm return về String, Variant, Object là gì, nguy hiểm gì, nặng nề, vất vã cho VBA ra sao.
Hiểu được rồi chúng ta sẽ hiểu tại sao các hàm trong VBEx.dll, MS coder luôn truyền = Pointer: pointer to VARIANT, BSTR pointer, tại sao thằng VARIANT *pResult luôn luôn được truyền đầu tiên và trả về chính nó.

Nói chứ càng phân tích thấy càng tội VBA, thà cứ như C/C++ 1-1 thì MS coder khỏe biết mấy rồi, nó chạy siêu nhanh rồi. Do phải vừa đảo bảm sự tương thích với Windows củ, Office cũ, bảo đãm tính dễ học, dễ viết, dễ dùng cho người dùng, nên nó phải âm thầm vất vả cáng đáng quá nhiều thứ, rồi bị đổ thêm tội "lanh chanh" nữa.
Đeophai nó cũng có lanh chanh, nhưng nó sát với C/C++ nên cái tội lanh chanh của nó còn chấp nhận được, bỏ qua khi coder hiểu.
Đúng là cái gì cũng có cái giá của nó, ông trời không bao giờ cho không ai cái gì :(
 
Lần chỉnh sửa cuối:
Upvote 0
Class Dictionary của VBA: Scripting.Dictionary trong scrrun.dll được dùng nhiều nhất với dân dùng VBA. Chúng ta sẽ mổ xẻ nó, từ VBA ta sẽ lấy cho được 1 con số quan trọng nhất của nó: Reference Count. Con số này quyết định sự sống chết của Dictionary Object và mọi COM Object trên Windows và Offices khác mà dân VBA thấy, vd từ Excel ta thấy mọi thứ như ActiveCell, ActiveSheet, Range.... Tất tần tật.
Class Dictonary được Implement trong Scrrun.dll với tên class là VBADictionary. Nó implement các interface sau theo thứ tự từ cha đến con:
1. IUnknown với QueryInterface, AddRef, Release methods.
2. IDispatch với GetTypeInfoCount, GetTypeInfo, GetIDsOfNames và Invoke
3. IProvideClassInfo: vài method các bạn không thấy trong hình do nằm dưới.
4. IExternalConnection: nt
5. VBAEnumSupport: Internal declare của VBA, phục vụ cho vụ For Each, Next....
Bảng VMT table của VBADictionary còn thiếu vài field đầu, do nó được assign trước contructor
Mã:
this[0] = vtable of VBADictionary
this[1] = vtable of IUnknown
this[2] = vtable of IDispatch
Tiếp theo là 3 và xxx thì các bạn thấy trong hình.
Xem cho biết thôi, chúng ta chỉ cần xem cái this[5], cái Reference Counter, nó chính là VALUE AT ADDRESS = ObjPtr(dic) + 5 * PTR_SIZE. PTR_SIZE = 4 TRÊN OFFICE 32, 8 TRÊN OFFICE 64.


Untitled.png
 
Lần chỉnh sửa cuối:
Upvote 0
Hiện Mạnh đang xài 2 kiểu Dic trong Delphi ( Từ ngữ hay thuật ngữ code có thẻ Mạnh viết sai bét nhè ... nên ko cố chấp sai thì sửa thế thôi ?! )
Kiểu 1 theo cách hay viết trên VBA
Mã:
Dic := CreateOleObject('Scripting.Dictionary');
Kiểu 2 theo cách Mạnh Học trên Google
Mã:
Dic := TDictionary<string, Integer>.create;
Vậy có cách nào khác khi viết trên Delphi ta kết nối trực tiếp vào Dic đó trong thư viện C/c++ của Bill ko Or có cách nào đó hay hơn 2 cách trên khi @ThangCuAnh mở sẻ thư viện của Bill vvv...
 
Upvote 0
Các bạn nào đã lập trình COM/OLE sẽ hiểu được tầm quan trong của 2 method AddRef và Release của IUnknown: interface cha, cố nội của mọi inteface. Luôn luôn theo quy ước: AddRef: tăng reference counter lên 1, Release giảm reference counter xuống 1, nếu nó xuống tới 0: tự nó giải phóng nó.
Trên VBA, AddRef sẽ được gọi khi nào, khi chúng ta create nó = New hay CreateObject, khi chúng ta assign vào biến khác, khi chúng ta truyền nó đi bằng ByVal, khi chúng ta trả nó về từ kết quả hàm.
Release được gọi khi nào, dạ, cu anh em xin trả lời ạ, nó được VBA gọi khi Set nó = Nothing, khi nó ra khỏi scope: tầm vực, ví dụ tầm vực của hàm, khi object cha nó chết...
Vậy làm sao cho không bị leak, tức nó cứ sống hoài, thì cứ phải theo nguyên tắc 1 - 1, có AddRef thì phải có Release, tức có tăng 1 thì phải có giảm 1. Để khi xuống tới 0 nó tự delete nó.1.png2.png
Bài đã được tự động gộp:

TDictionary là class của Delphi, viết = Object Pascal, có source nó đâu đó đó trong thư mục source của Delphi đó. Nó không dính dáng, liên quan gì đến Dictionary của VBA cả.
Muốn thì dùng cái nào cũng được, nhưng cái Dic của VBA chắc chắn sẽ chạy nhanh hơn TDict của Delphi.
 
Upvote 0
vậy là có Set Dic = ... thì bắt buột phải có Set Dic = nothing ???
nếu ko set Dic = nothing thì điều gỉ sẻ xẩy ra ???

Vậy nếu sử dụng Dic của VBA nhanh hơn ... thì trong Delphi ngoài cách sau thì có cách khai báo nào khác hay hơn
Mã:
Dic := CreateOleObject('Scripting.Dictionary');
 
Upvote 0
Thì Reference counter nó vẫn còn dương. Xem lại bài https://www.giaiphapexcel.com/diendan/threads/undocument-windows-api-và-vba.144002/post-933191.
Tui đã comment rất kỹ vào đó, dù đã có set obj = nothing, xuống 1, nhưng nó vẫn còn sống do nó được assign vào v nên reference counter nó lên 2 rồi xuống 1.
Cái tật đọc code và comment không kỹ, cứ lướt lướt :)
Bài đã được tự động gộp:

Tạm gác nó lại đi, code lấy VBA Dic counter đó các bạn đọc không hiểu đâu, chúng ta sẽ đi lại từ post 169. Giải thích rõ ràng cho các bạn hiểu về memory, DumpMem, VarPtr, ObjPtr, StrPtr.
Và tui sẽ viết thêm 2 hàm VBA không có là DWordPtr và VarIsNothing cho bé @thuyyeu99, và code hàm Đeo phai: Deophai_TypeName(const pv: POleVariant): AnsiString cho bài bên Delphi video kia.
 
Upvote 0
vậy là có Set Dic = ... thì bắt buột phải có Set Dic = nothing ???
nếu ko set Dic = nothing thì điều gỉ sẻ xẩy ra ???

Nếu obj được DIM trong Sub, Function khi chạy xong VBA tự Set obj = Nothing cho mình. Nhưng về khía cạnh lập trình developer nên chủ ý thực hiện việc này nó đẹp hơn và tường mình code hơn. Có New thì có Nothing. Trong Delphi có Create thì có Destroy từ obj.Free.
 
Upvote 0
Nếu obj được DIM trong Sub, Function khi chạy xong VBA tự Set obj = Nothing cho mình. Nhưng về khía cạnh lập trình developer nên chủ ý thực hiện việc này nó đẹp hơn và tường mình code hơn. Có New thì có Nothing. Trong Delphi có Create thì có Destroy từ obj.Free.
Vậy là ta khai báo như sau theo như Bạn đã chỉ cho Mạnh bên thớt Delphi Kia
Mã:
dic := TDictionary<string, TMyData>.create;
  try
    data := TMyData.Create;
    ....
    data.Free;
    end;
  finally
    dic.Free;
 
Upvote 0
Chà, sao tới lúc cần giải thích, viết dài thì mình cứ nổi cái bệnh lười lên ta ! Thôi ai hỏi gì nói đó cho rồi, tập trung vào post 167, 168, 169 nhen các bạn.
 
Upvote 0
Vậy là ta khai báo như sau theo như Bạn đã chỉ cho Mạnh bên thớt Delphi Kia
Mã:
dic := TDictionary<string, TMyData>.create;
  try
    data := TMyData.Create;
    ....
    data.Free;
    end;
  finally
    dic.Free;

Trong Delphi bạn làm thế là chuẩn đó, Nhưng viết lại rõ hơ nếu bạn nào mới học Delphi hiểu chắc chắn hơn là
Mã:
dic := TDictionary<string, TMyData>.create;
  try
    data := TMyData.Create;
    try
....
    finnaly
       data.Free;
    end;
  finally
    dic.Free;;
  end;

Quan trọng là nhớ việc Free khi chắc chắn không còn dùng đến hoặc mẹ của nó thoát. Lúc thì phải Free trong khối TRY..FINALLY, đôi khi Free trong khối mẹ của nó khi mẹ nó chạy OnDestroy.
 
Upvote 0
Chúng ta sẽ tiếp tục dùng DumpMem để xem reference count quyết định sự sống còn của 1 COM object nhé. Ở đây là object Dictionary. Và chúng ta cũng read trực tiếp từ bộ nhớ của 1 Dictionary object cái giá trị lưu cho property Count. Thú vị chút thôi nhé. Nhớ xem comment code và nhìn cho kỹ kết quả dump ra ở Immediate Windows.
Code này chạy cho Office 32bit nhé các bạn, Office 64 thì khác 1 chút, tui cần có scrrun.dll của Win64.
Mã:
Option Explicit

Public Sub Test_Dictionary()
    Dim obj As Object
    Dim v As Variant

#If VBA7 Then
    Dim lObjPtr As LongPtr
#Else
    Dim lObjPtr As Long
#End If
   
    Debug.Print "------- obj duoc khoi tao tu 1 class -------"
    Set obj = CreateObject("Scripting.Dictionary")  ' ref count = 1
    DumpMem VarPtr(obj), PTR_SIZE
    lObjPtr = ObjPtr(obj)
    Debug.Print "Pointer to the object instance of obj = " & Hex$(lObjPtr)
   
    DumpMem lObjPtr, PTR_SIZE   ' pointer to vtable
    DumpMem lObjPtr + 5 * PTR_SIZE, PTR_SIZE    ' ref count
   
    obj.Add 0, 1
    obj.Add 1, 2
    Debug.Print "Dictionary object có Count = " & obj.count
    DumpMem lObjPtr + 6 * PTR_SIZE, PTR_SIZE    ' dict count, read directly from memory

    Debug.Print "------- v duoc set = obj -------------------"
    Set v = obj     ' ref count = 2
    DumpVariant v
    DumpMem lObjPtr + 5 * PTR_SIZE, PTR_SIZE    ' ref count
    DumpMem lObjPtr + 6 * PTR_SIZE, PTR_SIZE    ' dict count
   
    Debug.Print "------- obj duoc set = nothing tro lai -----"
    Set obj = Nothing   ' ref count xuong còn 1
    DumpMem VarPtr(obj), PTR_SIZE
    DumpMem lObjPtr + 5 * PTR_SIZE, PTR_SIZE    ' ref count
    DumpMem lObjPtr + 6 * PTR_SIZE, PTR_SIZE    ' dict count

    Debug.Print "------- v van giu pointer to obj dict ------"
    DumpVariant v
   
    v.Add 2, 3
    v.Add 3, 4
   
    Debug.Print "Dictionary object có Count = " & v.count
    DumpMem lObjPtr + 6 * PTR_SIZE, PTR_SIZE    ' dict count, read directly from memory

    DumpVariant v
   
    Debug.Print "------- v = Empty, Erase obj dict ----------"
    v = Empty           ' ref count = 0, object dictionary duoc giai phóng (free)
    DumpVariant v
    DumpMem lObjPtr + 5 * PTR_SIZE, PTR_SIZE    ' ref count
    DumpMem lObjPtr + 6 * PTR_SIZE, PTR_SIZE    ' dict count

    ' v.Add 4, 5    ' <= die here
End Sub
 
Upvote 0
cái biết cái ko nhưng lúc rảnh cũng giáng học chút ... máy mạnh nó ra như sau
Mã:
------- obj duoc khoi tao tu 1 class -------
025AECF0: 58 4C B1 07
Pointer to the object instance of obj = 7B14C58
07B14C58: 1C 15 80 6D
07B14C6C: 01 00 00 00
Dictionary object có Count = 2
07B14C70: 02 00 00 00
------- v duoc set = obj -------------------
025AECE0: 09 00 00 00 00 00 00 00 58 4C B1 07 00 00 00 00
VarType:
09 00
Reserved1-3:
00 00 00 00 00 00
Data:
58 4C B1 07 00 00 00 00
07B14C6C: 02 00 00 00
07B14C70: 02 00 00 00
------- obj duoc set = nothing tro lai -----
025AECF0: 00 00 00 00
07B14C6C: 01 00 00 00
07B14C70: 02 00 00 00
------- v van giu pointer to obj dict ------
025AECE0: 09 00 00 00 00 00 00 00 58 4C B1 07 00 00 00 00
VarType:
09 00
Reserved1-3:
00 00 00 00 00 00
Data:
58 4C B1 07 00 00 00 00
Dictionary object có Count = 4
07B14C70: 04 00 00 00
025AECE0: 09 00 00 00 00 00 00 00 58 4C B1 07 00 00 00 00
VarType:
09 00
Reserved1-3:
00 00 00 00 00 00
Data:
58 4C B1 07 00 00 00 00
------- v = Empty, Erase obj dict ----------
025AECE0: 00 00 00 00 00 00 00 00 70 B3 77 0B 00 00 00 00
VarType:
00 00
Reserved1-3:
00 00 00 00 00 00
Data:
70 B3 77 0B 00 00 00 00
07B14C6C: 00 00 00 00
07B14C70: 00 00 00 00
 
Upvote 0
Đọc kỹ kết quả đi, xem hiểu gì không, nhất là code và comment
 
Upvote 0
Đừng quan tâm tới code, hãy để ý kỹ các con số. Tất cả mọi thứ trên máy tính chỉ đơn giản là con số địa chỉ và các byte values. Hiểu vậy và dùng DumpMem cho tất cả mọi thứ bạn nghĩ ra, các bạn sẽ thấy VBA nó đơn giản thế nào, ra sao.
 
Upvote 0
Object Dictionary đã được giải phóng, reference count của nó đã là 0 và count cũng đã là 0, tức mọi phần tử trong Items và Keys của nó đã được clear và giải phóng rồi, trả bộ nhớ về cho OS.
Gợi ý cậu Mạnh dòng đầu tiên nhé, rồi sẽ từ từ sáng ra:
Mã:
025AECF0: 58 4C B1 07
Tức biến obj có địa chỉ trên stack là &H025AECF0 (do biến khai báo trong hàm), nó chiếm 4 byte trên Win32 và 8 byte trên Win64. Giá trị của từng byte từ thấp đến cao là 58 4C B1 07, tức là 07B14C58 (=Long/LongPtr/DWord). Trên Win64 thì địa chỉ là 8 byte (tức 16 chử số Hex) và giá trị của nó cũng là 16 chữ số hex. 2 chử số Hex là 1 byte.
Vì hệ máy PC của chúng ta thuộc hệ "vợ bé" (Litle Endian) tức byte thấp đứng trước, byte lớn đứng sau, word thấp đứng trước, word lớn đứng sau, dword nhỏ đứng trước, dword lớn đứng sau. Nên khi đọc dãy hex, muốn đổi sang kiểu nào thì ta đổi từ phải sang trái, cao xuống thấp. Tức lật ngược lại đó.
Vd 58 4C B1 07 để biểu diễn cho 2 số Word thì nó sẽ là 4C58 và 07B1, nếu cho số DWord thì 07 B1 4C 58.
Hệ "vợ lớn" (Big Endian) thì ngược lại, lớn trước nhỏ sau, đọc từ trái qua phải sao thì số nó vậy.
VarPtr cho ta địa chỉ của 1 biến trong vùng nhớ. Hàm này không cách nào chúng ta tự viết được, phải dùng của VBA cho. Còn hai hàm StrPtr và ObjPtr thực ra đơn giản chỉ là lấy ra giá trị 4 (Win32) hay 8 byte (Win64) tại địa chỉ đó trả về cho ta.
Viết nôm na trong Delphi hay C/C++ thì
StrPtr := PDWord_Ptr(@1 biến string) ^;
ObjPtr := PDWord_Ptr(@1 biến obj)^;
Và:
VarPtr := @biến;

Mã:
025AECF0: 58 4C B1 07
Pointer to the object instance of obj = 7B14C58
07B14C58: 1C 15 80 6D
07B14C6C: 01 00 00 00
Dictionary object có Count = 2
07B14C70: 02 00 00 00
Cho nên cậu thấy ObjPtr(obj) = (0)7B14C58
Tại địa chỉ 07B14C58, chúng ta có giá trị quan trọng nhất quyết định mã chạy của 1 object, đó là con trỏ tới Virtual Method Table (VMT). Theo máy cậu lúc đó thì nó là 6D80151C (1C 15 80 6D), Nó trỏ tới 1 array of DWORD_PTR (4 byte 32, 8 byte 64), ngay phần tử đầu tiên của array. Mỗi phần tử trong array này chứa địa chỉ bắt đầu của 1 hàm trong bảng VMT của object. Tại địa chỉ đó chúng ta sẽ thấy mã máy của mov edi, edi hay push ebp, mov ebp, esp. Và cái chúng ta cần thử là đã có địa chỉ này rồi, làm sao gọi thẳng vào đó luôn.
Tại 07B14C6C, tức cách ObjPtr(obj) + 5 * 4 byte, chúng ta có 1 biến của obj, chiếm 4 byte, chính là số reference count, và nó là 01 00 00 00 tức = 1
Tại 07B14C70, tức cách ObjPtr(obj) + 6 * 4 byte, chúng ta có 1 biến nữa, chính là biến mà khi ta gọi property Count của Dictionary, nó sẽ lấy từ đây trả về cho chúng ta, đang là 02 00 00 00 tức = 2
2 biến này các bạn nhớ nhé, không phải object nào giống object nào, scrrun.dll nào giống scrrun.dll nào và Win32 khác Win64 nhé. Phải "rờ em" nó nhẹ nhàng thì nó mới phọt ra cho :)
 
Lần chỉnh sửa cuối:
Upvote 0
Bạn tiếp tục với các dòng dưới
1 biến kiểu object được xem là Is Nothing khi ruột nó toàn 00. ObjPtr return 0.
1 biến kiểu string được xem là vbNullString khi ruột nó toàn 00. StrPtr return 0.
Trên VBA, chỉ có 2 biến kiểu pointer thôi là string (cả động và fix lengh) và object.
 
Lần chỉnh sửa cuối:
Upvote 0
Web KT
Back
Top Bottom