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:
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

  • test_rtcTypeName.xlsm
    16 KB · Đọc: 10
  • 1.png
    1.png
    113.1 KB · Đọc: 30
  • 1.png
    1.png
    137.3 KB · Đọc: 23
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

  • test_BStrVar.xlsm
    18.5 KB · Đọc: 13
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

  • test_BStrVar.xlsm
    21.3 KB · Đọc: 13
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
Web KT
Back
Top Bottom