Chương 3: Để sử dụng Win API từ VBA

vbano1

SMod
Thành viên BQT
3.1
3.2
3.3
3.4
3.5
3.6
3.7
3.8
 

Euler

Administrator
Thành viên BQT
3.1 Những điểm kỳ vọng khi sử dụng hàm Win API
Khi sử dụng hàm API, đây là các điểm mà chúng ta kỳ vọng.
Ⓐ Chúng ta muốn thêm vào chương trình VBA những tính năng mà các hàm cơ bản của VBA không có.
Ⓑ Chúng ta muốn tăng tốc độ xử lý của chương trình.
Ⓒ Muốn dùng ngôn ngữ cấp thấp.

Tuy nhiên Ⓑ và Ⓒ là các trường hợp rất đặc thù. Có lẽ chúng ta thường gặp trường hợp Ⓐ nhiều hơn.

Về trường hợp Ⓑ, có những trường hợp, việc gọi hàm API sẽ cho tốc độ nhanh hơn lệnh VBA. Tuy nhiên, hiệu quả có được như kỳ vọng hay không, thật ra là một điều khó đoán biết. Tại sao lại như vậy là vì, đối với VBA, chúng ta gọi các tính năng của Excel, tốc độ xử lý sẽ tương đồng với Excel.
Trong thực tế, những người tin tưởng tuyệt đối vào VBA sẽ cho rằng, VBA không chậm đâu, chẳng qua là code của bạn chậm thôi. Chẳng hạn nếu chúng ta không sử dụng phương thức Sort (sắp xếp) của Excel, thay vào đó ta sử dụng thuật toán nổi bọt để sắp xếp, hoặc chúng ta không sử dụng hàm MATCH của Excel để tìm kiếm, chúng ta sử dụng vòng lặp để tìm kiếm... tốc độ quả nhiên là có sự khác nhau. Cho nên có lý do để nói chương trình chậm là do cách code.

Tuy nhiên, VBA có những điểm hạn chế khi so với tốc độ thực thi của file EXE, quả nhiên tốc độ của VBA là chậm, trong quá trình xử lý, nếu nó phải tham chiếu tới thư viện biểu thức chính quy. Hay các thủ đoạn để giải quyết vấn đề của VBA có nhiều hạn chế, và có giới hạn. Lúc này, có lẽ chúng ta phải nghĩ tới việc thay thế phương án giải quyết.

Về điểm Ⓒ, đây là trường hợp rất đặc thù. Từ chương 4 trở đi, chúng ta nhìn vào các đoạn code mẫu, lúc đó các bạn sẽ hiểu nhưng tôi có thể nói rằng, hàm API là ngôn ngữ cấp thấp. Tuy nhiên, tôi không cho rằng đây là lý do chính để chúng ta sử dụng các hàm API.

Ta hãy xem ví dụ sau:
Mã:
Sub test1()
    MsgBox "Day la loi", vbCritical
End Sub
Kết quả:
Bạn cần đăng nhập để thấy đính kèm

Rõ ràng với code VBA như ở trên, các bạn thấy câu lệnh hết sức đơn giản. Tuy nhiên thực tế là lệnh VBA đã ngầm gọi hàm API để hiển thị DialogBox, kết quả các bạn nhìn thấy là hộp thoại cảnh báo hiện ra. Tuy nhiên sâu xa bên trong là các tính năng phức tạp mà code VBA đã giản tiện đi chỉ bằng một câu lệnh rất ngắn gọn. Vì vậy, có thể nói VBA là ngôn ngữ cao cấp (dễ hiểu), API là ngôn ngữ cấp thấp.

Ở đây, tôi muốn nói với các bạn rằng, API cũng giống như là rượu. Chúng ta không nên uống nhiều rươu, tức là không nên dùng nhiều hàm API. Đây là điểm mà chúng ta nên lưu ý. Đầu tiên, hãy tinh thông VBA. Hàm API nên là thủ đoạn cuối cùng nếu các bạn không còn cách nào khác. Thay vì dùng các hàm API, trong khả năng có thể, các bạn nên sử dụng càng nhiều hàm VBA cơ bản thì càng tốt.

Trong bản dịch trên, tôi đã lược dịch lấy các điểm chính-Lời người dịch.
 

giaiphapvba

Administrator
Thành viên BQT
3.2 Cách khai báo hàm API bằng lệnh Declare.
Ở các phần trước, chúng ta đã biết rằng hàm API được cung cấp trong các file DLL. Tuy nhiên, để gọi hàm API thì các điều dưới đây, VBA không hiểu.
Ⓐ Hàm API mà chúng ta muốn gọi nằm trong thư viện nào?
Ⓑ Ta phải truyền tham số như thế nào cho hàm API thì tốt?

Vì vậy, để truy cập vào DLL, ở VBA ta sử dụng lệnh Declare để khai báo hàm API.
Thông qua ví dụ về khai báo hàm lấy chỉ dẫn Windows Directory là GetWindowsDirectory, chúng ta sẽ cùng nhau học cách sử dụng lệnh Declare.
Đầu tiên hãy nhìn cấu trúc hàm GetWindowsDirectory được viết bằng ngôn ngữ C:
C++:
UNIT GetWindowsDirectoryA(
  LPSTR lpBuffer, //address of buffer for Windows directory
  UINT  uSize //size of directory buffer
);
Sau đây ta sẽ chuyển thành khai báo phù hợp để có thể sử dụng được trong VBA.
Đầu tiên hãy xem định dạng Win64 API.
Bạn cần đăng nhập để thấy đính kèm

❶ Khai báo của 64bit
Khi gọi Win64 API, đầu tiên chúng ta sử dụng từ khóa PtrSafe, nó sẽ biên dịch cho VBA 64bit.
❷ Chỉ định tên hàm
Tên hàm ở đây sẽ dùng giống như tên hàm được khai báo bằng ngôn ngữ C. Do đó chúng ta giữ nguyên tên hàm là GetWindowsDirectory.

Chú ý: Cũng có những hàm API không có giá trị trả về. Khi đó chúng ta không dùng Function, chúng ta dùng từ khóa Sub. (Lời người dịch: Hãy nhớ lại khái niệm thủ tục và hàm)

❸ Chỉ định thư viện
Hàm API này đang được cất trong DLL nào, chúng ta phải khai báo. Nếu không khai báo thì VBA sẽ không hiểu địa chỉ của hàm API. Như tôi đã nói, chúng ta giới hạn trong khuôn khổ 3 DLL phổ biến hay dùng nhất, đó là Kernel32.dll, User32.dll, Gdi32.dll , chúng ta có thể giản lược bỏ đuôi file là ".dll" và có thể viết là [Lib "kernel32"] nhưng khi bạn gọi hàm API trong thư viện khác thì phải viết rõ ràng cả đuôi file ".dll", ví dụ [Lib "advapi32.dll"] .
❹ Chỉ định tên hàm chính thức được công khai ở DLL
Ở mục 5 chương 3 (3.5) tôi sẽ giải thích về Win API sử dụng chuỗi ký tự nhưng cũng có trường hợp API được công khai bằng cái tên khác trong DLL. Trong trường hợp đó, ta sẽ khai báo tên hàm được công khai bằng Alias.
Tóm lại, hàm GetWindowsDirectory được công khai trong DLL là hai tên hàm: GetWindowsDirectoryAGetWindowsDirectoryW. Tên hàm GetWindowsDirectory không tồn tại trong thực tế.
Trong trường hợp ví dụ này, mặc dù có hai cái tên, nhưng chúng ta đã chọn khai báo lấy tên là GetWindowsDirectoryA.

Chú ý: Ở cuối tên hàm có sự khác biệt chữ cái [A] và [W], về điều này tôi sẽ thuyết minh trong mục Win API sử dụng chuỗi ký tự.

Ngoài ra, tên hàm API có phân biệt chữ in hoa và chữ thường. Trường hợp khai báo như dưới đây sẽ sinh ra lỗi.
Bạn cần đăng nhập để thấy đính kèm


Tổng kết: Thực tế là, nếu bạn chỉ định tên hàm API đúng ở phần Alias, thì đằng sau từ khóa Function, bạn có thể viết tên hàm tùy ý mà không phải là [GetWindowsDirectory]. Ví dụ, trường hợp khai báo như dưới đây cũng đúng.
Bạn cần đăng nhập để thấy đính kèm


Tuy nhiên, chúng ta không nên đặt tên tùy tiện như vậy, đó không phải là thói quen tốt. Hãy đặt tên giống như Microsoft.

❺ Chỉ định tham số
Trong C, tham số lpBuffer có kiểu dữ liệu được khai báo là [LPTSTR] nhưng trong VBA không có kiểu dữ liệu như vậy. Ở đây, ta chuyển thành khai báo kiểu String.
Tương tự như thế, kiểu dữ liệu [UNIT] chúng ta chuyển thành kiểu Long. Ở phần tiếp theo, tôi sẽ giới thiệu quy tắc chuyển đổi kiểu dữ liệu từ ngôn ngữ C sang kiểu dữ liệu của VBA.

❻ Giá trị trả về
Trong ngôn ngữ C, giá trị trả về của hàm GetWindowsDirectory là kiểu dữ liệu UNIT. Trong VBA, ta chuyển kiểu dữ liệu trả về của [Function GetWindowsDirectory] là kiểu LongPtr.
(Còn nữa)
 

Euler

Administrator
Thành viên BQT
3.2 Cách khai báo hàm API bằng lệnh Declare.
(tiếp theo)
Ghi chú: Long và LongPtr
LongPtr không phải là kiểu dữ liệu cố định, trên 32bit thì nó là Long, trên 64bit thì nó là LongLong.
Chỉ cần nhớ điều này, chúng ta sẽ không khó khăn khi phải khai báo hàm API trên 32bit và 64bit.
  • Win64 API thì dùng LongPtr
  • Win32 API thì dùng Long
Ngoài ra trong trường hợp Win64 API, [UNIT] ở ❺ là kiểu Long, [UNIT] ở ❻ là LongPtr, nhìn qua thì chẳng ăn khớp gì với nhau, nhưng OS 32 bit từ Windows 95 trở đi, đối với hàm API, nếu tham số các bạn chỉ định là Long, thì kiểu dữ liệu trả về của hàm API dù ghi là LongPtr thì cũng không sao đâu.
Tại sao lại vậy thì ở chương 4 trở đi, khi các bạn nhìn vào các đoạn code mẫu các bạn sẽ hiểu. Đây là cách viết code vắn tắt khi chúng ta cần hoán đổi nhau giữa WIN32 API và WIN64 API.
Tất nhiên, tôi nghĩ cũng có ý kiến phản đối cách suy nghĩ này, vậy thì các bạn cứ viết thống nhất kiểu dữ liệu của tham số truyền vào và kiểu dữ liệu trả về của hàm API cũng được, hãy cứ làm như vậy đi.
Vậy, bây giờ chúng ta hãy nhìn vào cách khai báo hàm GetWindowsDirectory cho Win32 API.
Bạn cần đăng nhập để thấy đính kèm

❶ Không chỉ định từ khóa PtrSafe
Đối với trường hợp Win32 API, từ khóa PtrSafe là không cần thiết, cho nên không cần ghi từ khóa này khi khai báo.
❷ Chỉ định kiểu dữ liệu cho giá trị trả về của hàm
Win32 API không thể sử dụng LongPtr, do đó giá trị trả về của [Function GetWindowsDirectory] ta chuyển thành kiểu Long trên VBA. Có thể nói rằng giá trị trả về của Win32 API là 32bit (4 byte) do đó để kiểu Long là đương nhiên.

Ghi chú: Một số lưu ý khi sử dụng Declare
  1. Với lệnh Declare, ta cần viết khai báo ở trên đầu Module.
  2. Khi khai báo bằng Declare thì hàm (thủ tục) trong DLL sẽ được mặc định là Public. Tức là ở các Module khác trong VBA có thể gọi hàm API đã được khai báo này. Để các Module khác không thể gọi được nó, hãy dùng từ khóa Private.

Ngoài ra, nếu hàm API được khai báo không phải trên Module tiêu chuẩn (chẳng hạn trên UserForm) thì dù có sử dụng từ khóa Public, thì nó vẫn được nhìn nhận là Private.
 

tuhocvba

Administrator
Thành viên BQT
3.3 Kiểu dữ liệu của Win API và kiểu dữ liệu của VBA
Đương nhiên kiểu dữ liệu của ngôn ngữ C và VBA sẽ có sự khác nhau. Lý tưởng là chúng ta học và nắm được ngôn ngữ C, tuy nhiên tôi sẽ hệ thống những kiểu dữ liệu hay gặp nhất, và xây dựng thành bảng tương đối giữa kiểu dữ liệu Win API và VBA tương ứng.
Trong đó, kiểu dữ liệu này các bạn không cần quan tâm tới Win 64 API, chỉ cần cho Win 32 API là đủ rồi.
Có chú ý là, với kiểu dữ liệu BOOL của ngôn ngữ C, khi chuyển sang VBA sẽ là Long, các bạn lưu ý nhé.
Ngôn ngữ CNgôn ngữ VBA
ATOMInteger
BOOLLong
BYTEByte
CHARByte
COLORREFLong
DWORDLong
HWND, HDC, HMENU, Windows handle...Long
INT, UNITLong
LPARAMLong
LPDWORDLong
LPINT, LPUNITLong
LPRECTKiểu dữ liệu người dùng tự định nghĩa
LPSTR, LPCSTRString
LPVOIDAny
LPWORDInteger
LRESULTLong
NULLAny hoặc long
SHORTInteger
WORDInteger
WPARAMLong
LongLong
LPVOIDAny
 
3.4 Gọi hàm (thủ tục) API
Hàm (và thủ tục) API được khai báo bằng câu lệnh Declare, thông thường có thể được gọi từ chương trình VBA để chúng thực thi.
Ở phần trước chúng ta đã được xem một ví dụ thực tế về khai báo hàm GetWindowsDirectory. Bây giờ chúng ta sẽ cùng xem xét nó được gọi ra như thế nào.

Đầu tiên, hãy xem trường hợp Win64 API.
Mã:
Declare PtrSafe Function GetWindowsDirectory Lib "kernel32" _
        Alias "GetWindowsDirectoryA" _
        (ByVal lpBuffer As String, _
        ByVal nSize As Long) As LongPtr
        
Public Const MAX_PATH = 260 'Do dai lon nhat cua PATH

Sub GetWindowsDirectory_Sample()
    'Windows Derectory Path Name
    Dim strBuffer   As String * MAX_PATH
    Dim rc          As LongPtr
    
    Dim strPrompt   As String
    
    'Lay WindowsDirectory Path Name
    rc = GetWindowsDirectory(strBuffer, Len(strBuffer))
    
    'Trich xuat lay phan ky tu can thiet tu data lay duoc
    strPrompt = Left(strBuffer, InStr(strBuffer, vbNullChar) - 1)
    
    MsgBox "Windows directory Path Name: " & strPrompt
End Sub
Hãy để ý vào các chỗ khai báo kiểu LongPtr trong toàn bộ chương trình trên.

Kết quả khi chạy thủ tục GetWindowsDirectory_Sample ta sẽ được thông báo có nội dung sau:
Mã:
Windows directory Path Name: C:\Windows
Đối với Win32 API, ta sửa từ khóa LongPtr thành Long. Từ khóa PtrSafe không cần thiết có thể bỏ đi (giữ lại cũng không sao).
Mã:
Declare Function GetWindowsDirectory Lib "kernel32" _
        Alias "GetWindowsDirectoryA" _
        (ByVal lpBuffer As String, _
        ByVal nSize As Long) As Long
        
Public Const MAX_PATH = 260 'Do dai lon nhat cua PATH

Sub GetWindowsDirectory_Sample()
    'Windows Derectory Path Name
    Dim strBuffer   As String * MAX_PATH
    Dim rc          As Long
    
    Dim strPrompt   As String
    
    'Lay WindowsDirectory Path Name
    rc = GetWindowsDirectory(strBuffer, Len(strBuffer))
    
    'Trich xuat lay phan ky tu can thiet tu data lay duoc
    strPrompt = Left(strBuffer, InStr(strBuffer, vbNullChar) - 1)
    
    Msgbox "Windows directory Path Name: " & strPrompt
End Sub
Chú ý, từ nay khi chúng tôi viết code, chúng tôi sẽ sử dụng tên biến là rc có ý nghĩa nó là từ giản lược của từ return code, dùng để lưu giá trị trả về của hàm API.

Ghi chú: VBA không kiểm tra code của hàm API có thể hoạt động hay không khi gọi hàm API.
Trong môi trường VBA, khi làm chương trình, có chức năng kiểm tra code đúng hay sai. Dù chỉ là truyền kiểu dữ liệu sai cho tham số thì quá trình thực thi macro sẽ thất bại và kết thúc ngay.
Tuy nhiên trong trường hợp VBA gọi hàm API, môi trường VBA không hỗ trợ xác nhận code hàm API đúng sai hay không.
Nói cách khác, nếu bạn chỉ định kiểu dữ liệu sai cho tham số của hàm API, khi thực thi có trường hợp hệ thống sẽ xảy ra lỗi, hoặc hàm API hoàn hoàn toàn không hoạt động.
 

giaiphapvba

Administrator
Thành viên BQT
3.5 Hàm API sử dụng tham số là kiểu chuỗi ký tự
VBA chuyển UNICODE thành ANSI và truyền cho API

Đối với hàm Win API chứa chuỗi ký tự thì có hai hình thức là ANSI và DBCS với Unicode.
Thực tế ở bài học trước, khi nói tới hàm GetWindowsDirectory chúng ta có tên hai hàm số sau được công khai là GetWindowsDirectoryAGetWindowsDirectoryW.
Chú ý: ANSI là kiểu ký tự ASCII, độ dài là 1 byte. Unicode là kiểu ký tự bao gồm các ký tự ASCII và các ký tự khác, có độ dài là 2 byte.

Như vậy, ở đây tồn tại một vấn đề. Ngôn ngữ VBA và hàm Win API của Windows có sự khác nhau về thiết định ký tự được sử dụng.
Môi trườngThiết định ký tự được sử dụng
VBAUnicode
Windows 95/98/ Me APIANSI
Windows NT 4.0/2000/XP/VISTA/7/8/10 APIANSI & Unicode

Nhìn vào biểu đồ trên, ta thấy VBA là Unicode. Tuy nhiên ký tự được thiết định của hàm API Windows 95/98/Me là ANSI. Câu chuyện ở đây có một chút phức tạp, nhưng chúng ta cũng không cần hiểu sâu hơn. Chỉ cần hiểu là, khi VBA gọi API thì tham số nếu là chuỗi ký tự thì chúng chuyển đổi thành ANSI rồi mới truyền cho API.

Ngoài ra, Windows NT 4.9/2000/XP/VISTA/7/8/10 API là Unicode nhưng hàm API khi tiếp nhận tham số từ VBA, đầu tiên VBA không quan tâm tới Unicode mà sẽ chuyển ký tự tham số đó thành ANSI.
Bạn cần đăng nhập để thấy đính kèm

(Còn nữa)
 
3.5 Hàm API sử dụng tham số là kiểu chuỗi ký tự
(tiếp theo)
Tóm lại, chúng ta không cần phải quan tâm tới chuỗi ký tự truyền vào API của OS như thế nào. Nói đúng hơn là VBA sau khi tự ý chuyển đổi thành ANSI. Thông thường sẽ phải sử dụng tên hàm có chứa chữ A ở cuối tên hàm.
Lại nói về giá trị trả vè của hàm API là chuỗi ký tự, khi giá trị này được trả về trong môi trường VBA, nó sẽ nhanh chóng được chuyển đổi lại thành Unicode. Đây là điểm cần chú ý.
Tóm lại, khi xử lý chuỗi ký tự thu được từ API các hàm xử lý chuỗi ký tự bằng đơn vị Byte như LenB, LeftB, RightB, MidB không thể dùng được. Thông thường sẽ sử dụng các hàm Len, Left, Right, Mid.
Các hàm được công khai trong ANSI version, Unicode version chỉ là các hàm xử lý chuỗi ký tự. Do vậy, khi khai báo các hàm API sử dụng chuỗi ký tự, nhất định chúng ta phải dùng Alias để chỉ định tên hàm chính xác có chứa chữ [A] ở cuối tên hàm.

Hàm API sẽ ghi đè giá trị lên tham số chuỗi ký tự được truyền cho nó

Trong hàm API, chuỗi ký tự với tư cách là tham số được truyền vào có thể bị thay đổi. Trong trường hợp sử dụng hàm API như vậy, trong chương trình VBA phải đảm bảo độ dài cần thiết để truyền tham số chuỗi ký tự cho hàm API.
Mã:
Public Const MAX_PATH =260
Dim strBuffer As String * MAX_PATH 'Qui dinh do dai chuoi ky tu la 260
Chuỗi ký tự mà hàm API thay đổi có thể có kích thước dài hơn chuỗi ký tự tham số ban đầu được nhận vào, nếu vượt quá độ dài của biến số chuỗi ký tự, việc ghi đè dữ liệu có thể gây nên những lỗi, phá hủy hay làm hỏng các dữ liệu khác.
Bạn cần đăng nhập để thấy hình ảnh

(Còn nữa)
 

vbano1

SMod
Thành viên BQT
3.5 Hàm API sử dụng tham số là kiểu chuỗi ký tự
(tiếp theo)
Ở bài trên ta cần giải thích kỹ hơn về số 260. Tại sao ta lại phải quy định chuỗi ký tự chỉ có độ dài tối da tới 260 ký tự. Điều này là vì nó đã được định nghĩa trong ngôn ngữ C, cụ thể là ở file winder.h, chuỗi ký tự mà ta thường dùng trong VBA có độ dài rất lớn và không thể truyền cho hàm API được. Vì hàm API được viết bằng ngôn ngữ C. Do đó khi truyền chuỗi ký tự từ VBA sang ngôn ngữ C ta cần lấp đầy bằng data Null (mã ký tự là 0) để truyền cho API.
Trong trường hợp này, ta đã cố định độ dài chuỗi ký tự từ trước (260), do đó nó sẽ tự động được lấp đầy bởi Null.
Từ nay về sau, trong các ví dụ tôi đưa ra thì sẽ tuân thủ quy tắc này, sẽ cố định độ dài chuỗi ký tự ngay từ đầu.
Tuy nhiên, nếu một lúc nào đó, bạn dùng chuỗi ký tự thông thường, thì hãy nhớ là phải lấp đầy nó bằng Null như ví dụ dưới đây.
Mã:
Sub macro1()
    Dim strBuffer   As String 'Bien so co do dai lon
    '
    '
    '
    strBuffer = String(260, vbNullChar)
    '
    '
    '
End Sub
Hoặc:
Mã:
strBuffer = String(260, 0)
Bạn cần đăng nhập để thấy đính kèm

(Còn nữa)
 

Euler

Administrator
Thành viên BQT
3.5 Hàm API sử dụng tham số là kiểu chuỗi ký tự
(tiếp theo)
Lấy chuỗi ký tự mà API đã tiếp nhận
Hàm API tiếp nhận chuỗi ký tự đã được lấp đầy bởi các ký tự Null, và thay đổi chuỗi ký tự đó, trả về cho hàm VBA.
Đối với hàm GetWindowsDirectory đã nói ở các bài viết trước, nó sẽ ghi dữ liệu vào chuỗi ký tự và trả về cho VBA là [C\Windows], thế nhưng, nếu giữ nguyên giá trị đó để sử dụng trong VBA thì không thể được.
Nhìn vào thì thấy là [C\Windows] nhưng thực tế ở phía sau đó là một đống ký tự Null.
Vì vậy, đối với tham số mà hàm API dùng để tiếp nhận chuỗi ký tự, nếu chúng ta lấy về chuỗi ký tự đó dùng trong VBA thì cần phải xử lý như dưới đây:
Mã:
strPrompt = Left(strBuffer, Instr(strBuffer, vbNullChar)-1)
Trong chuỗi ký tự strBuffer, ta cần tìm vị trí của ký tự Null đầu tiên ở bên trái. Từ vị trí này lùi 1 vị trí về bên trái là phần ý tự mà ta cần lấy. Vì vậy ta có thể dùng hàm Left để thực hiện công việc này.
Bạn cần đăng nhập để thấy đính kèm

Tuy nhiên cũng có người viết code như sau:
Mã:
rc = GetWindowsDirectory(strBuffer, Len(strBuffer))
strPrompt = Left(strBuffer, rc)
Thực tế câu lệnh này sẽ hoạt động bình thường trong VBA. Tuy nhiên nói một cách nghiêm khắc thì cách viết như thế này là sai.
Giá trị trả về rc là kích thước số lượng Byte của kiểu dữ liệu ANSI của strBuffer (đã được loại bỏ ký tự Null).
Giả sử như giá trị trả về của hàm GetWindowsDirectory là [C:\ウィンドウ] mà các ký tự đều là kiểu zenkaku. Trong trường hợp này thì với định dạng ANSI, chuỗi ký tự này sẽ được biểu diễn bằng 13 bytes. Như vậy rc sẽ là 13.
Câu lệnh trên trở thành:
Mã:
strPrompt = Left(strBuffer, 13)
Tuy nhiên với định dạng Unicode của VBA, thì hankaku/zenkaku sẽ được biểu diễn 1 ký tự là 2 bytes.
Với chuỗi ký tự strBuffer có 8 ký tự : [C:\ウィンドウ]. Như vậy là phải xử lý với tư cách là 16 bytes (mỗi ký tự là 2 byte).
Tức là để xử lý chính xác với chuỗi ký tự [C:\ウィンドウ] thì câu lệnh chính xác sẽ là:
Mã:
strPrompt = Left(strBuffer, 8)
Như thuyết minh ở trên, ta đã hiểu tại sao không thể dùng rc để thực thi xử lý đúng. Tại sao chúng ta nên xử lý bằng hàm Instr để xác định ký tự Null đầu tiên.
(Còn nữa)
 

giaiphapvba

Administrator
Thành viên BQT
3.5 Hàm API sử dụng tham số là kiểu chuỗi ký tự
(tiếp theo)
Khi truyền tham số chuỗi ký tự cho hàm API, chúng ta dùng Byval
Chương này thật là nhiều rắc rối đau đầu, nhưng không sao đâu. Khi đã sử dụng quen các hàm API, bạn sẽ thấy những điều này là bình thường.
Nào, hãy xem lại khai báo hàm API trong VBA.
Bạn cần đăng nhập để thấy đính kèm

Các khai báo chuỗi ký tự ở trên đều bắt đầu bằng ByVal.

Đối với môi trường VBA thì khi sử dụng để truyền tham số thì biến số vốn có ấy sẽ không bị ảnh hưởng.
Ví dụ: Hãy xem đoạn code sau:
Byval-VBA:
Sub main()
    Dim i As Integer
    i = 1
    S_change i
    MsgBox i 'Hien thi la 1
End Sub
Sub S_change(ByVal myno)
    myno = 2
End Sub
Mặc dù S_change có phép gán myno = 2 nhưng biến số myno chỉ là bản copy của biến số i trong thủ tục main.
Vì vậy ở thủ tục main, giá trị i không bị thay đổi, nó vẫn là 1.

Hãy thay đổi ví dụ trên:
Byref-VBA:
Sub main()
    Dim i As Integer
    i = 1
    S_change i
    MsgBox i 'Hien thi la 2
End Sub
Sub S_change(myno)
    myno = 2
End Sub
Khai báo thủ tục S_change ở trên tương đương với:
Mã:
Sub S_change(ByRef myno)
Trên đây là câu chuyện của môi trường VBA.
Tuy nhiên môi trường ngôn ngữ C thì không sử dụng như vậy.
Khi truyền tham số, biến số được trao cho hàm API sẽ là bản copy của biến số đó.
Biến số có hai thành phần quan trọng. Một là giá trị của biến số đó. Hai là địa chỉ bộ nhớ lưu trữ biến số đó.
Bạn cần đăng nhập để thấy đính kèm

Trong ngôn ngữ C, giá trị của biến số sẽ được gọi thông qua con trỏ chỉ tới địa chỉ của biến số. Trong VBA thì chúng ta không có khái niệm con trỏ.
Như ở hình minh họa trên, nếu ta không trao cho ngôn ngữ C địa chỉ con trỏ (1000) thì hàm API không thể thay đổi được giá trị chuỗi ký tự mà nó tiếp nhận.
Vậy làm thế nào để trao con trỏ cho hàm API? Ở phía VBA, ta cần khai báo bằng từ khóa ByVal. Khi đó ngôn ngữ C (API) sẽ tiếp nhận được con trỏ của biến số đó.
(Còn nữa)
 

vbano1

SMod
Thành viên BQT
3.5 Hàm API sử dụng tham số là kiểu chuỗi ký tự
(tiếp theo)
Truyền tham số string cho hàm DLL
Việc truyền tham số string cho hàm API là chủ đề đau đầu cho bất cứ ai.
Dưới đây là nội dung của Visual Basic 5.0 lấy từ .NET:
Thông thường khi truyền tham số string cho hàm API chúng ta sẽ sử dụng ByVal.
Ở Visual Basic sẽ sử dụng kiểu dữ liệu được gọi là BSTR, nó có tư cách như là kiểu dữ liệu String.
BSTR được cấu thành từ chính thể chuỗi ký tự với Header chứa thông tin chuỗi ký tự. Chuỗi ký tự cũng có thể được lấp đầy bởi các ký tự Null.
BSTR được truyền đi với tư cách như là con trỏ, hàm DLL có thể thay đổi chuỗi ký tự.
Con trỏ là biến số lưu trữ vị trí vùng nhớ của biến số khác, nó không phải là kiểu dữ liệu có trong thực tế.
BSTR là Unicode cho nên mỗi ký tự cần 2bytes để biểu diễn. Thông thường ký tự cuối cùng của BSTR là ký tự Null được biểu diễn bởi 2 bytes.
Bạn cần đăng nhập để thấy đính kèm


Hàm trong nhiều file DLL và tất cả các hàm của Windows API đều nhận thức LPSTR.

LPSTR là con trỏ, trỏ tới ký tự Null cuối cùng trong ngôn ngữ C (là kiểu ký tự ASCIIZ).
Ở đây chuỗi ký tự không có Header.
Ở hình minh họa dưới đây minh họa LPSTR trỏ tới ký tự ASCIIZ.
Bạn cần đăng nhập để thấy đính kèm


Trường hợp hàm DLL yêu cầu tham số là LPSTR (con trỏ trỏ tới ký tự Null cuối cùng), ta sẽ trao giá trị BSTR. Con trỏ trỏ tới BSTR là con trỏ trỏ tới byte đầu tiên của ký tự Null là ký tự kết thúc của của data string, ta có thể sử dụng giống như với LPSTR.

Lời người dịch: Mình đọc xong mà đầu óc cũng trở nên ù ù, chưa hiểu gì lắm.
Tóm lại, cho tới đây, các bạn chỉ cần ghi nhớ, đối với các hàm API có tham số là string, thì chúng ta sẽ khai báo bằng
ByVal.
 

NhanSu

SMod
Thành viên BQT
Mình xin bổ sung bài của bạn @vbano1 . Khi ta khai báo một biến x bằng lệnh Dim thì địa chỉ của biến x (xác định bằng hàm VarPtr(x)) không đổi trong suốt quá trình chương trình chạy, chỉ có giá trị tại địa chỉ đó thay đổi được.
1. Nếu x = 1 là kiểu số Long (4 byte) chẳng hạn thì giá trị của 4 byte tại địa chỉ của biến x chính là giá trị của x.
Mã:
Public Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" ( _
    ByRef Destination As Any, _
    ByRef Source As Any, _
    ByVal Length As Long)
Sub test()
    Dim X As Long, Y As Long
    Dim PtrX As LongPtr
    X = 1000
    PtrX = VarPtr(X)  'Address of X
    CopyMemory Y, ByVal PtrX, 4           '(1)
    'CopyMemory Y, X, 4                         '(2)
    Debug.Print PtrX, VarPtr(PtrX), Y
End Sub
Kết quả khi chạy: 49801168 49801160 1000 (kết quả này có thể khác nhau đối với từng lần chạy hay trên máy khác). Hàm API CopyMemory sẽ copy Length bytes từ vùng nhớ có địa chỉ Source đến vùng nhớ có địa chỉ Destination. Ở đây ta chú ý thêm mặc dù CopyMemory được khai báo Byref nhưng khi lại được gọi bằng ByVal vì PtrX đang chứa địa chỉ của biến X (49801168), nếu ta gọi bằng ByRef thì địa chỉ của PtrX (49801160) được truyền cho hàm và ra kết quả sai; ta cũng có thể thay lệnh (1) thành (2), khi đó tham số X được truyền bằng ByRef nên địa chỉ của X (chính là PtrX=49801168) được truyền cho CopyMemory.
2. Nếu x = "abcde" là kiểu String thì giá trị của vùng nhớ tại địa chỉ của x không phải là nội dung chuỗi mà chứa địa chỉ của chuỗi đó, VBA cũng có hàm StrPtr để lấy địa chỉ của chuỗi này. VBA lưu chuỗi dưới dạng BSTR, đó là cấu trúc gồm 4 byte chứa độ dài của chuỗi tính theo byte (không tính ký tự null), theo sau là chuỗi unicode, mỗi ký tự chiếm 2 byte, cuối cùng là 2 byte ký tự null. VBA thường không quan tâm đến ký tự null này và ta có thể thay đổi nội dung 4 byte đầu để thay đổi độ dài chuỗi. Ví dụ chuỗi x="abcde" thì VBA sẽ BSTR sẽ lưu dưới dạng 4 byte đầu tạo thành số Long có giá trị = 10, tiếp đến 10 byte chuỗi abcde dạng unicode 16, cuối cùng là 2 byte null. Hàm StrPtr(x) sẽ trả về địa chỉ của nội dung chuỗi unicode (không phải địa chỉ bắt đầu của BSTR)
Mã:
Option Explicit
#If VBA7 Then
    Public Const PTR_LENGTH As Long = 8
#Else
    Public Const PTR_LENGTH As Long = 4
#End If
Public Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" ( _
    ByRef Destination As Any, _
    ByRef Source As Any, _
    ByVal Length As Long)
Sub test2()
    Dim X As String, i As Long
    Dim PtrX As LongPtr, PtrS As LongPtr
    Dim arr(1 To 10) As Byte             
    X = "abcde"
    PtrX = VarPtr(X)  'Address of X
    'CopyMemory PtrS, ByVal PtrX, PTR_LENGTH     'Address of "abcde"
    'CopyMemory arr(1), ByVal PtrS, 10
    CopyMemory arr(1), ByVal StrPtr(X), 10
    For i = 1 To 10
        Debug.Print arr(i)
    Next
End Sub[code]
Chạy sub Test2 ta được nội dung của chuỗi.
3. Qua các ví dụ trên, ta cũng thấy khi truyền tham số kiểu String cho API thì ta cần truyền dưới dạng ByVal vì nếu dùng Byref thì địa chỉ của địa chỉ của chuỗi sẽ được chuyển cho hàm.
 

Euler

Administrator
Thành viên BQT
3.6 Giá trị số mà Win API sử dụng cho tham số
Chúng ta cùng phán đoán cách truyền tham số xem hàm API có ghi đè giá trị lên số được truyền cho nó hay không


Tham số truyền cho hàm API thì không chỉ có ký tự, đương nhiên có cả trường hợp tham số truyền cho hàm API là số.
Trường hợp là chuỗi ký tự, như chúng ta đã biết, hàm API sẽ ghi đè giá trị cho chuỗi ký tự bằng việc ở môi trường VBA, chúng ta khai báo bằng từ khóa ByVal.
Trường hợp là số, giá trị số đó sẽ được giữ nguyên, nói cách khác, hàm API không thể ghi đè thay đổi giá trị nếu biến số là số, tham số được truyền sẽ được khai báo bằng từ khóa ByVal.
Ở ví dụ trước đây khi chúng ta sử dụng hàm GetWindowsDirectory, hàm này tiếp nhận số 260 và không thể thay đổi giá trị này. Và tham số khi truyền cho hàm API được khai báo là [Byval nSize As Long].

Tuy nhiên, ở các phần sau, các bạn sẽ thấy hàm GetExitCodeProcess có thể thay đổi giá trị số mà nó tiếp nhận và trả về cho VBA, tham số được truyền khi khai báo không sử dụng từ khóa ByVal.
(Còn nữa)
 

tuhocvba

Administrator
Thành viên BQT
3.6 Giá trị số mà Win API sử dụng cho tham số
(tiếp theo)
Số nguyên có dấu và số nguyên không dấu
Hàm API viết bằng ngôn ngữ C có thể dùng số nguyên không dấu, chúng được gọi là unsigned long. Tuy nhiên VBA không hỗ trợ số nguyên không dấu, dù có dấu hay không dấu thì nó đều dùng 32 bit (4bytes) và được gọi là kiểu Long. Vì thế, chúng ta dễ nhầm lẫn về mặt nhận thức rằng unsigned long = Long.
Ngôn ngữKiểu dữ liệuPhạm vi
Ngôn ngữ CSố nguyên không dấu0~4.294.967.295
VBASố nguyên có dấu-2.147.483.648~2.147.483.647
Kiểu Log trong VBA không thể vượt quá 2.147.483.647, tuy nhiên đối với hàm API, giá trị trả về của nó có trường hợp vượt quá 2.147.483.647, như vậy trong trường hợp này thì phía VBA phải làm thế nào?
Trong môi trường VBA, nếu chúng ta nạp vào biến số kiểu Long giá trị là 2.147.483.648, khi đó sẽ xảy ra lỗi tràn số, thế nhưng trường hợp này lại không xảy ra lỗi. Câu trả lời là, biến số kiểu Long nhận giá trị 2.147.483.648 sẽ tiếp nhận giá trị là -2.147.483.648 .

Chúng ta hãy thử suy nghĩ vấn đề này đối với số có giá trị nhỏ hơn một chút, chúng ta xét một số có 8 bits (1byte). Trường hợp số nguyên không dấu, 1 byte tức là 8 bits, số sẽ có phạm vi từ 0~255.
Bạn cần đăng nhập để thấy đính kèm

Tuy nhiên, nếu là số nguyên có dấu, cùng là 8 bits, nhưng phạm vi của số lúc này chỉ nằm trong khoảng -128~127. Tại sao lại vậy?
Là bởi vì chúng ta phải dùng một bit để biểu diễn dấu (số dương hay âm). Như vậy chúng ta chỉ còn có 7 bits để biểu diễn giá trị.
Bạn cần đăng nhập để thấy đính kèm

Trong môi trường VBA, thì chúng ta không thể cộng thêm 1 vào [01111111 (=127)] nhưng nếu như có thể thêm được, thì khi đó toàn bộ 7 bits còn lại sẽ bị làm tròn khi thực hiện phép cộng, và như vậy ta chỉ còn duy nhất bit ngoài cùng mang giá trị 1.
Bạn cần đăng nhập để thấy đính kèm

Bạn hãy nhớ kiến thức cơ bản này của máy tính.
Bit đầu tiên bên trái của số [10000000] chỉ đơn thuần mang ý nghĩa dấu âm. Tuy nhiên nếu nói như vậy, sẽ có một nhầm lẫn rất lớn nếu cho rằng 7 bits còn lại đều là 0, cho nên [10000000] chẳng phải là [-0] hay sao? Điều này là một điều nhầm lẫn.
Khi bit đầu tiên bên trái là 1, nó mang giá trị âm, khi đó giá trị của số là âm của giá trị tuyệt đối của số [10000000] tức là -128. Như vậy khi tăng dần nó bằng cách cộng thêm 1 vào nó ta có [10000001],[10000010],... có nghĩa là -127 và -126...
Nếu cứ tăng như vậy, tới một thời điểm ta sẽ được một dãy bit gồm toàn các số 1: [11111111].
Bạn cần đăng nhập để thấy đính kèm

Qua câu chuyện trên, các bạn đã hiểu tại sao giới hạn của số nguyên có dấu lại là -2.147.483.648~2.147.483.647 .
Vậy thì thực tế nếu hàm API trả về giá trị vượt quá 2.147.483.647 thì sẽ nên xử lý như thế nào đây? Trong trường hợp này, bit ngoài cùng bên trái nhất định sẽ là 1. Khi đó VBA sẽ phán đoán giá trị đó là số âm. Như vậy mặc dù giá trị trả về thực tế là số dương kiểu Long, nhưng hiển thị ra lại là số âm. Ta chỉ cần lấy giá trị âm này cộng với 4.294.967.296 (2^32) ta sẽ thu được số dương mà chúng ta cần.
 

Euler

Administrator
Thành viên BQT
3.7 Kiểu dữ liệu do người dùng tự định nghĩa
Khai báo kiểu biến tự định nghĩa

Trong VBA, chúng ta có thể tự định nghĩa kiểu dữ liệu mà bên trong gồm có kiểu integer, kiểu String,... Đó gọi là kiểu dữ liệu do người dùng tự định nghĩa. Mặc dù điều này ít thấy và không phổ biến mấy trong VBA, tuy nhiên việc tự định nghĩa kiểu dữ liệu thì lại gặp rất nhiều trong các hàm API.
Ví dụ ta có kiểu dữ liệu được định nghĩa như sau:
Mã:
Ten    String
Tuoi    Integer
NgaySinh    Date
Kiểu dữ liệu như thế này, thông thường trong VBA mọi người sẽ nghĩ ngay là sử dụng mảng.
Mã:
Dim thvbaData(1 to 3) As Variant
thvbaData(1) ="Nguyen Van A"
thvbaData(2) = 30
thvbaData(3) = #19/6/2000#

Msgbox "Ten la: " & thvbaData(1)
Msgbox "Tuoi la: " & thvbaData(2)
Msgbox "Ngay sinh la: " & thvbaData(3)
Bây giờ, ta sẽ viết code VBA có chức năng tương tự nhưng dùng kiểu dữ liệu tự định nghĩa như sau:
Mã:
Type thongtincanhan
    PName   As String   'Ten
    PAge    As Integer 'Tuoi
    PNs     As Date 'Ngay sinh
End Type
Sub main()
    Dim udtPdata    As thongtincanhan '(1)
    udtPdata.PName = "Nguyen Van A" '(2)
    udtPdata.PAge = 30 '(2)
    udtPdata.PNs = #6/19/2000# '(2)
    
    MsgBox "Ten la: " & udtPdata.PName
    MsgBox "Tuoi la: " & udtPdata.PAge
    MsgBox "Ngay sinh la: " & udtPdata.PNs
    
End Sub
Ở (1) ta đã khai báo biến số có kiểu dữ liệu là do chúng ta tự định nghĩa.
Ở (2), chúng ta đã sử dụng các thuộc tính của kiểu dữ liệu mà chúng ta đã định nghĩa.

Trong ngôn ngữ C, kiểu dữ liệu người dùng tự định nghĩa được gọi là biến cấu trúc. Các thuộc tính bên trong được gọi là thành viên (member).

Trong VBA, để sử dụng kiểu biến tự định nghĩa ở các Module khác, chúng ta nên khai báo bằng Public. Nếu không muốn các Module khác sử dụng kiểu biến tự định nghĩa, chúng ta nên sử dụng từ khóa Private.
(Còn nữa)
 

tuhocvba

Administrator
Thành viên BQT
3.7 Kiểu dữ liệu do người dùng tự định nghĩa
Truyền tham số cho hàm API kiểu dữ liệu tự định nghĩa bằng ByRef

Kiểu dữ liệu tự định nghĩa trong VBA thì trong C được gọi là kiểu biến cấu trúc, được sử dụng rất nhiều.
Khi truyền tham số cho hàm API mà hàm này sử dụng biến cấu trúc thì kiểu biến cấu trúc này cần được định nghĩa trong VBA dưới dạng kiểu dữ liệu tự định nghĩa, và chúng ta phải sử dụng ByRef. Tại sao lại như vậy thì thú thực tôi cũng không hiểu được ý nghĩa. Có lẽ vì VBA được thiết kế mà theo đó chỉ có thể truyền dữ liệu có cấu trúc bằng ByRef.
Điều này không chỉ trong hàm API, đối với hàm Function của VBA khi được gọi, thì đây cũng là quy tắc.
Nếu như việc truyền tham số sai thì đương nhiên sẽ xảy ra lỗi, nhưng việc này sẽ được phát hiện ngay khi biên dịch chương trình.
Ví dụ ta có biến cấu trúc được khai báo trong VBA như sau:
Mã:
    Private Type POINTAPI
           X As Long
           Y As Long
    End Type
Và đối với khai báo hàm API:
Khai báo sai:
Private Declare PtrSafe Function GetCursorPos Lib "user32" ( _
                                                            ByVal lpPoint As POINTAPI) As LongPtr
Khai báo đúng:
Private Declare PtrSafe Function GetCursorPos Lib "user32" ( _
                                                            ByRef lpPoint As POINTAPI) As LongPtr
 
3.8 Các điểm chính để sử dụng Win API

Point1:
Mảng số
Win API về cơ bả không thể tiếp nhận toàn bộ mảng từ VBA. Do đó thông thường chúng ta phải dùng For ~Next hoặc một vòng lặp khác để truyền tuần tự từng phần tử mảng cho hàm API.
Tuy nhiên đối với mảng số, VBA có thể truyền toàn bộ mảng cho hàm API. Khi đó ta dùng ByRef để truyền phần tử đầu tiên của mảng cho hàm API.

Point2: Con trỏ NULL
Trong Win API, ta có thể chỉ định con trỏ NULL cho tham số có kiểu dữ liệu là chuỗi ký tự. Chỉ định con trỏ NULL nói một cách đơn giản, đó là ta không truyền dữ liệu cho tham số đó.
Sau đây ta hãy xem ví dụ hàm API FindWindow dưới đây:
Mã:
#If Win64 Then
    Declare PtrSafe Function FindWindow Lib "user32" _
        Alias "FindWindowA" _
        (ByVal lpClassName As String, _
        ByVal lpWindowName As String) As LongPtr
#Else
     Declare PtrSafe Function FindWindow Lib "user32" _
        Alias "FindWindowA" _
        (ByVal lpClassName As String, _
        ByVal lpWindowName As String) As Long
#End If
Hàm này sẽ tìm tên class (lpClassName ) và tên cửa sổ (lpWindowName ) và trả về handler nhưng đối với tham số thứ nhất (tên class) và tham số thứ hai (tên cửa sổ) thì ta có thể truyền giá trị là con trỏ Null cho nó.
Tuy nhiên cần chú ý rằng con trỏ NULL trong API và từ khóa NULL trong VBA có ý nghĩa hoàn toàn khác nhau. Hãy xem câu lệnh sai dưới đây:
Mã:
rc = FindWindow(Null, "MicroSoft Excel - Book1")
Các bạn sẽ thấy là xuất hiện lỗi 94.
Bạn cần đăng nhập để thấy hình ảnh

Từ hóa Null là một biến số kiểu Variant thể hiện rằng biến số này đang không lưu trữ giá trị nào.
Trong ngôn ngữ C, con trỏ NULL tất nhiên không phải là từ khóa Null trong VBA, nó cũng không phải là chuỗi ký tự có độ dài là 0, tức là không phải là "". Nó là giá trị được thể hiện bằng [0].
Vì hàm API đang yêu cầu tham số có kiểu dữ liệu String, cho nên ta thử thay đổi code trên. Quả nhiên là nó cũng hoạt động không chính xác.
Mã:
rc = FindWindow(0&, "MicroSoft Excel - Book1")
Note:Nếu thay kiểu dữ liệu của hàm API từ kiểu String thành kiểu Long thì câu lệnh có thể được thực hiện.​

Đối với VBA, ký tự thể hiện giá trị 0 chính là vbNullString. Để truyền con trỏ NULL cho hàm API, ta dùng câu lệnh như dưới đây là chính xác:
Mã:
rc = FindWindow(vbNullString, "MicroSoft Excel - Book1")
(Còn nữa)
 
3.8 Các điểm chính để sử dụng Win API
(tiếp theo)

Point3: As Any
Trong Win API, bằng cách khai báo As Any, thì tham số được khai báo có thể được nhận bất kỳ kiểu dữ liệu nào. Nói chính xác thì ở đây tắt chức năng kiểm tra kiểu dữ liệu cho tham số đó. Vì vậy khi sử dụng, các bạn phải hết sức lưu ý.
Khi khai báo As Any trong hàm API ta dùng ByRef, nếu truyền giá trị cho tham số của hàm API qua hàm VBA thì tham số của hàm API chúng ta phải dùng ByVal để khai báo.
(Đính chính bởi admin tuhocvba)

Point4: TRUE của Win API với TRUE của VBA
TRUE trong hàm API và TRUE trong VBA có sự khác biệt về giá trị. Trong ngôn ngữ C, TRUE là 1, TRUE trong VBA là -1. Thế nhưng FALSE trong ngôn ngữ C và FALSE trong VBA đều có giá trị là 0.
Khi lấy giá trị trả về của hàm API là TRUE hay FALSE để sử dụng trong VBA, câu lệnh như dưới đây là không thể sử dụng được.
Mã:
If (GiaTriTraVe = True) then
Ở ví dụ trên tại sao không thể sử dụng được là vì GiaTriTraVe = 1 trong khi đó True = -1.
Câu lệnh trên cần sửa lại là:
Mã:
If (GiaTriTraVe <> False) then
(Còn nữa)
 

NhanSu

SMod
Thành viên BQT
@vanthanhVBA về tham số có kiểu As Any như mình đã ví dụ ở bài 13 với hàm CopyMemory thì hàm WinAPI yêu cầu địa chỉ của vùng nhớ chứ không phải giá trị, tức là API sẽ nhận tham số kiểu LongPtr, do đó cần khai báo ByRef.
Tuy vậy có bạn sẽ thắc mắc vì sao khi gọi hàm cần truyền tham số là chuỗi thì ta lại dùng ByVal, đó là do giá trị tại địa chỉ của biến String không phải là nội dung chuỗi mà chính là địa chỉ (con trỏ) của chuỗi. Code ví dụ mình đã viết ở bài 13.
 
Top