Tạo, sử dụng thư viện liên kết động DLL (Standard DLL) trong VBA (1 người xem)

Liên hệ QC

Người dùng đang xem chủ đề này

Tôi tuân thủ nội quy khi đăng bài
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.

View attachment 307698

Mã:
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ị.

View attachment 307699

C++:
#include "pch.h"
#include "comutil.h"
#include "comdef.h"

extern "C" __declspec(dllexport) VARIANT WINAPI GetDemoArray() {
    VARIANT varResult;
    HRESULT hr;
    VariantInit(&varResult);
    SAFEARRAYBOUND sab[1]{};
    sab[0].cElements = 2;
    sab[0].lLbound = 0;
    SAFEARRAY* psa = SafeArrayCreate(VT_BSTR, 1, sab);
    if (!psa) {
        varResult.vt = VT_EMPTY;
        return varResult;
    }
    for (long i = 0; i < 2; i++) {
        BSTR bstrVal = SysAllocString(L"Đây là phẩn tử của mảng SAFEARRAY");
        if (!bstrVal) {
            varResult.vt = VT_EMPTY;
            SafeArrayDestroy(psa);
            return varResult;
        }
        hr = SafeArrayPutElement(psa, &i, (LPVOID)bstrVal);
        if (FAILED(hr)) {
            varResult.vt = VT_EMPTY;
            SafeArrayDestroy(psa);
            SysFreeString(bstrVal);
            return varResult;
        }
        SysFreeString(bstrVal);
    }
    varResult.vt = VT_ARRAY | VT_BSTR;
    varResult.parray = psa;
    return varResult;
}

VBA khai báo và sử dụng hàm trong DLL:
Để khai báo hàm DLL, ta sử dụng cấu trúc như sau:
Mã:
[ Public | Private ] Declare PtrSafe Function name Lib "đường_dẫn_dll"  [ ( [ tham_số_hàm_yêu_cầu] ) ] [ As type ]

View attachment 307700

Mã:
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
mạo muội xin các bác chỉ giáo rằng việc tạo .dll như kia tác dụng là gì trong khi mình có thể viết thẳng function trong file excel ạ !?
 
Đã 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.

1743647722909.png

Tham khảo thêm: Comparing Memory Allocation Methods
Bài đã được tự động gộp:

mạo muội xin các bác chỉ giáo rằng việc tạo .dll như kia tác dụng là gì trong khi mình có thể viết thẳng function trong file excel ạ !?
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.
Vậy bạn đề xuất xem nên sửa lại thế nào để nó có thể chạy trên cả hai bản 32 bit và 64bit, chỉ tính từ bản Office 2010 trở đi?
 
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
 
Web KT

Bài viết mới nhất

Back
Top Bottom