Trong quá trình viết macro VBA trên Excel, chắc hẳn nhiều người dùng nhận ra nhiều hạn chế nhất định của ngôn ngữ lập trình này. DLL, hay thư viện liên kết động, có thể được viết bằng nhiều ngôn ngữ lập trình khác nhau (C/C++, Delphi, TwinBasic, v.v...) để VBA có thể khai báo và sử dụng thông qua từ khóa Declare statement (VBA).
Dưới đây là một ví dụ về việc viết và sử dụng DLL trong VBA, trong ví dụ này sử dụng code mẫu viết bằng TwinBasic và C/C++. VD: Viết một DLL chứa hàm trả về mảng một chiều kiểu chuỗi gồm hai phần tử.
TwinBasic là một ngôn ngữ lập trình lấy cảm hứng từ VB6 và VB.NET nên cú pháp của nó khá tương đồng, giúp người dùng đã quen với VB có thể dễ dàng làm quen với ngôn ngữ lập trình này. Với thuận lợi này, người dùng có thể dễ dàng viết code một cách liền mạch, xuyên suốt.
Module MainModule
[DllExport]
Public Function GetDemoArray() As Variant
Dim arrResult(0 To 1) As String
For i As Long = LBound(arrResult) To UBound(arrResult)
arrResult(i) = "Đây là phần tử của mảng SAFEARRAY"
Next
Return arrResult
End Function
End Module
Với C/C++, mọi thứ sẽ phức tạp hơn so với TwinBasic do cần phải khai báo và sử dụng nhiều hàm cần thiết để cho ra kết quả cuối cùng. Với VBA, kiểu String thực chất là kiểu BSTR, mảng là SAFEARRAY và Variant tương đương với VARIANT. Khi hàm trả về giá trị, người dùng không cần phải gọi hàm dọn dẹp (trường hợp ở đây là hàm VariantClear), đây là trách nhiệm của VBA sau khi sử dụng xong giá trị.
Option Explicit
Private Declare PtrSafe Function GetDemoArray Lib "Y:\data\CPlusPlus\SimpleDll\x64\Release\SimpleDll.dll" () As Variant
'Private Declare PtrSafe Function GetDemoArray Lib "Y:\data\twinBASIC_IDE_BETA\projects\Build\SimpleDll_win64.dll" () As Variant
Private Sub TestDll()
Dim varResult As Variant
varResult = GetDemoArray()
Debug.Print varResult(1)
End Sub
Đã lập trình C/C++ thì tận dụng các hàm xử lý bộ nhớ và kernel mạnh mẽ nhất, để đạt được hiệu suất, tối ưu, hiệu quả quản lý bộ nhớ. Toán tử + buộc cấp phát bộ nhớ mới, đường nào mà nó không gọi đến các api SysAllocString,... khi xử lý chuỗi, nó chỉ nhanh khi + 1, 2 lần. memcpy nó là một phần của lập trình cấp thấp, nên lệnh này rất quan trọng, không thể bỏ qua được.
Trong VBA nếu bạn cần sử dụng bộ nhớ cho một số mục đích nào đó, vd: nối hàng chục chuỗi lại với nhau kiểu StringBuider, thì có thể sử dụng hàm CoTaskMemAlloc function (combaseapi.h) để xin cấp phát bộ nhớ động vùng heap.
Dùng DLL khi VBA thiếu một số chức năng mà bản thân nó không thể tự làm được. VD: Bạn có thể viết một hàm tính toán đa luồng, biên dịch thành DLL rồi sử dụng nó trong VBA nhằm giải quyết vấn đề VBA không hỗ trợ đa luồng.
Nối chuỗi hiệu suất phải sử dụng xử lý mảng byte, con trỏ bộ nhớ, cấp phát bộ nhớ.
Đoạn mã bạn đề xuất không phải nối chuỗi mà là đặt chuỗi vào bên trái của bộ nhớ đã cấp phát.
Và đoạn mã này viết lỗi và không tương thích 32 và 64 bit.
Nối chuỗi hiệu suất phải sử dụng xử lý mảng byte, con trỏ bộ nhớ, cấp phát bộ nhớ.
Đoạn mã bạn đề xuất không phải nối chuỗi mà là đặt chuỗi vào bên trái của bộ nhớ đã cấp phát.
Và đoạn mã này viết lỗi và không tương thích 32 và 64 bit.
Mã ở trên không cần thiết trong VBA, mặc dù sửa lại rất đơn giản.
Đây là một lớp StringBuilder cho VB, có thể cấp phát mới, có thể clone lại một StringBuilder, gộp, tái tạo
JavaScript:
Option Explicit
#If VBA7 Then
Private Declare PtrSafe Sub CopyMemory Lib "kernel32.dll" Alias "RtlMoveMemory" (ByVal dst As LongPtr, ByVal src As LongPtr, ByVal Length As LongPtr)
#Else
Private Declare Sub CopyMemory Lib "kernel32.dll" Alias "RtlMoveMemory" (ByVal dst As Long, ByVal src As Long, ByVal Length As Long)
#End If
Private clb&, cBuffer() As Byte, l&, lb&, ns&, U&, b() As Byte
Private Sub Class_Initialize()
Create
End Sub
Public Sub Create(Optional ByVal Content As String, Optional ByVal initialCapacity As Long = 255)
l = Len(Content): cBuffer = Content: clb = l * 2
If initialCapacity < l Then U = clb - 1 Else U = initialCapacity * 2 - 1: ReDim Preserve cBuffer(0 To U)
End Sub
Public Sub Append(ByVal value As String)
lb = LenB(value): If lb = 0 Then Exit Sub
ns = clb + lb
If ns > (U + 1) Then U = ns - 1: ReDim Preserve cBuffer(U)
CopyMemory VarPtr(cBuffer(clb)), StrPtr(value), lb: clb = ns
End Sub
Public Sub join(ByVal Builder As StringBuilder)
With Builder
lb = .LenBuffer: If lb = 0 Then Exit Sub
ns = clb + lb
If ns > (U + 1) Then U = ns - 1: ReDim Preserve cBuffer(U)
b = .buffer()
CopyMemory VarPtr(cBuffer(clb)), VarPtr(b(0)), lb: clb = ns
End With
End Sub
Public Sub clone(ByVal Builder As StringBuilder)
With Builder
clb = .LenBuffer: U = .Capacity: cBuffer = .buffer
End With
End Sub
Public Property Get buffer(Optional fixLength As Boolean) As Byte()
If fixLength And clb > 0 Then
b = cBuffer: ReDim Preserve b(clb - 1): buffer = b
Else
buffer = cBuffer:
End If
End Property
Public Property Get Length() As Long: Length = clb / 2: End Property
Public Property Get LenBuffer() As Long: LenBuffer = clb: End Property
Public Property Get Capacity() As Long: Capacity = U: End Property
Public Sub getString(refString$)
If clb > 0 Then b = cBuffer: ReDim Preserve b(clb - 1): refString = b Else refString = vbNullString
End Sub
Public Function ToString() As String
'add:Attribute ToString.VB_UserMemId = 0
If clb > 0 Then b = cBuffer: ReDim Preserve b(clb - 1): ToString = b
End Function
Sub reset()
clb = 0: ReDim cBuffer(0 To U)
End Sub
Mã ở trên không cần thiết trong VBA, mặc dù sửa lại rất đơn giản.
Đây là một lớp StringBuilder cho VB, có thể cấp phát mới, có thể clone lại một StringBuilder, gộp, tái tạo
JavaScript:
Option Explicit
#If VBA7 Then
Private Declare PtrSafe Sub CopyMemory Lib "kernel32.dll" Alias "RtlMoveMemory" (ByVal dst As LongPtr, ByVal src As LongPtr, ByVal Length As LongPtr)
#Else
Private Declare Sub CopyMemory Lib "kernel32.dll" Alias "RtlMoveMemory" (ByVal dst As Long, ByVal src As Long, ByVal Length As Long)
#End If
Private clb&, cBuffer() As Byte, l&, lb&, ns&, U&, b() As Byte
Private Sub Class_Initialize()
Create
End Sub
Public Sub Create(Optional ByVal Content As String, Optional ByVal initialCapacity As Long = 255)
l = Len(Content): cBuffer = Content: clb = l * 2
If initialCapacity < l Then U = clb - 1 Else U = initialCapacity * 2 - 1: ReDim Preserve cBuffer(0 To U)
End Sub
Public Sub Append(ByVal value As String)
lb = LenB(value): If lb = 0 Then Exit Sub
ns = clb + lb
If ns > (U + 1) Then U = ns - 1: ReDim Preserve cBuffer(U)
CopyMemory VarPtr(cBuffer(clb)), StrPtr(value), lb: clb = ns
End Sub
Public Sub join(ByVal Builder As StringBuilder)
With Builder
lb = .LenBuffer: If lb = 0 Then Exit Sub
ns = clb + lb
If ns > (U + 1) Then U = ns - 1: ReDim Preserve cBuffer(U)
b = .buffer()
CopyMemory VarPtr(cBuffer(clb)), VarPtr(b(0)), lb: clb = ns
End With
End Sub
Public Sub clone(ByVal Builder As StringBuilder)
With Builder
clb = .LenBuffer: U = .Capacity: cBuffer = .buffer
End With
End Sub
Public Property Get buffer(Optional fixLength As Boolean) As Byte()
If fixLength And clb > 0 Then
b = cBuffer: ReDim Preserve b(clb - 1): buffer = b
Else
buffer = cBuffer:
End If
End Property
Public Property Get Length() As Long: Length = clb / 2: End Property
Public Property Get LenBuffer() As Long: LenBuffer = clb: End Property
Public Property Get Capacity() As Long: Capacity = U: End Property
Public Sub getString(refString$)
If clb > 0 Then b = cBuffer: ReDim Preserve b(clb - 1): refString = b Else refString = vbNullString
End Sub
Public Function ToString() As String
'add:Attribute ToString.VB_UserMemId = 0
If clb > 0 Then b = cBuffer: ReDim Preserve b(clb - 1): ToString = b
End Function
Sub reset()
clb = 0: ReDim cBuffer(0 To U)
End Sub
Liệu hiệu năng có bị ảnh hưởng không khi trong code bạn sử dụng ReDim Preserve để mở rộng mảng (thực chất là tạo một mảng mới, sao chép nội dung từ mảng cũ sang) và số lượng chuỗi cần xử lý vừa nhiều lại vừa khá dài?
Bạn tham khảo bài viết này xem sao, nội dung khá hay, nói chung là có bắt chước, mô phỏng theo class System.Text.StringBuilder của .NET. [VB6] StringBuilder - Fast string concatenation
Đã lập trình C/C++ thì tận dụng các hàm xử lý bộ nhớ và kernel mạnh mẽ nhất, để đạt được hiệu suất, tối ưu, hiệu quả quản lý bộ nhớ. Toán tử + buộc cấp phát bộ nhớ mới, đường nào mà nó không gọi đến các api SysAllocString,... khi xử lý chuỗi, nó chỉ nhanh khi + 1, 2 lần. memcpy nó là một phần của lập trình cấp thấp, nên lệnh này rất quan trọng, không thể bỏ qua được.
Trong C/C++ làm gì có chuyện dễ dàng dùng mỗi toán tử "+" để nối hai chuỗi vậy bạn, tất cả đều phải làm việc ở dạng mảng, tùy thuộc vào chuỗi đang tương tác là kiểu char hay kiểu wchar_t thì mỗi ký tự chiếm 1 hoặc 2 byte bộ nhớ và tất cả đều phải đánh dấu kết thúc bằng ký tự '\0' (hay còn gọi là null-terminated string). Thư viện hàm C cung cấp nhiều hàm xử lý kiểu chuỗi khác nhau, hàm có tiền tố "str" (vd: strlen) dùng cho mảng char, còn "wcs" (vd: wcslen) dùng cho mảng wchar_t.
Liệu hiệu năng có bị ảnh hưởng không khi trong code bạn sử dụng ReDim Preserve để mở rộng mảng (thực chất là tạo một mảng mới, sao chép nội dung từ mảng cũ sang) và số lượng chuỗi cần xử lý vừa nhiều lại vừa khá dài?
Bạn tham khảo bài viết này xem sao, nội dung khá hay, nói chung là có bắt chước, mô phỏng theo class System.Text.StringBuilder của .NET. [VB6] StringBuilder - Fast string concatenation
Bài đã được tự động gộp:
Trong C/C++ làm gì có chuyện dễ dàng dùng mỗi toán tử "+" để nối hai chuỗi vậy bạn, tất cả đều phải làm việc ở dạng mảng, tùy thuộc vào chuỗi đang tương tác là kiểu char hay kiểu wchar_t thì mỗi ký tự chiếm 1 hoặc 2 byte bộ nhớ và tất cả đều phải đánh dấu kết thúc bằng ký tự '\0' (hay còn gọi là null-terminated string). Thư viện hàm C cung cấp nhiều hàm xử lý kiểu chuỗi khác nhau, hàm có tiền tố "str" (vd: strlen) dùng cho mảng char, còn "wcs" (vd: wcslen) dùng cho mảng wchar_t.
ReDim Preserve Là cấp phát bộ nhớ động, không phải sao chép mảng. Trong VBA lệnh này đồng thời cũng là lệnh cấp phát bộ nhớ nhanh hơn các lệnh API.
Trong lớp ở trên lệnh chỉ được gọi, chỉ xảy ra khi bộ nhớ ban đầu nhỏ hơn bộ nhớ đủ để chứa chuỗi.
Nếu đã xác định chuỗi ban đầu lớn chỉ cần gọi Create với bộ nhớ ban đầu lớn là được.
Trong C/C++ cũng có gì ghê gớm đâu, lớp StringBuilder này họ đã phát triển tối ưu, từ xửa từ xưa rồi, giờ chỉ cần kế thừa là xong rồi.
Thời gian mà bỏ ra để viết lại lớp này, rồi thời gian đâu để viết ra ứng dụng.
ReDim Preserve Là cấp phát bộ nhớ động, không phải sao chép mảng. Trong VBA lệnh này đồng thời cũng là lệnh cấp phát bộ nhớ nhanh hơn các lệnh API.
Trong lớp ở trên lệnh chỉ được gọi, chỉ xảy ra khi bộ nhớ ban đầu nhỏ hơn bộ nhớ đủ để chứa chuỗi.
Trong C/C++ cũng có gì ghê gớm đâu, lớp StringBuilder này họ đã phát triển tối ưu, từ xửa từ xưa rồi, giờ chỉ cần kế thừa là xong rồi.
Thời gian mà bỏ ra để viết lại lớp này, rồi thời gian đâu để viết ra ứng dụng.
Đã lập trình C/C++ thì tận dụng các hàm xử lý bộ nhớ và kernel mạnh mẽ nhất, để đạt được hiệu suất, tối ưu, hiệu quả quản lý bộ nhớ. Toán tử + buộc cấp phát bộ nhớ mới, đường nào mà nó không gọi đến các api SysAllocString,... khi xử lý chuỗi, nó chỉ nhanh khi + 1, 2 lần. memcpy nó là một phần của lập trình cấp thấp, nên lệnh này rất quan trọng, không thể bỏ qua được.
Làm thế nào mà lại hướng dẫn tạo tham chiếu đường dẫn tuyệt đối cho API kiểu này được bạn. "Y:\data\CPlusPlus\SimpleDll\x64\Release\SimpleDll.dll"
Rồi người ta tải về sử dụng bằng cách nào để có được đường dẫn này. Cách lập trình này chỉ dành cho chính lập trình viên sử dụng mà thôi.
Nên hướng dẫn theo hướng sử dụng các API là LoadLibrary và DispCallFunc.
Hướng dẫn sai lầm dễ làm con người ta u mê mãi không thoát ra được cái vòng luẩn quẩn của lập trình.
Tôi thấy hướng dẫn như vậy có sai lầm gì đâu nhỉ?
Để đường dẫn tuyệt đối cũng oke mà, chắc thiếu comment cho dòng đó thôi, vd như vậy càng dễ hiểu cho người chưa biết chứ nhỉ?
Bởi đây là bài viết ngắn, chủ yểu nói việc VBA có thể khai báo, sử dụng dll và giới thiệu 1 công cụ tạo dll.