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