Tạo, sử dụng thư viện liên kết động DLL (Standard DLL) trong VBA (4 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

    nguyendang95

    Thành viên hoạt động
    Tham gia
    25/5/22
    Bài viết
    106
    Được thích
    94
    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.

    1743477290646.png

    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ị.

    1743477808479.png

    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 ]

    1743478015362.png

    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
     
    Lần chỉnh sửa cuối:
    TwinBasic có bản free và bản đó có tạo được DLL dạng này không bạn? Nếu có thì dùng nó cho vba hợp với mọi người chỉ biết vba.
     
    TwinBasic có bản free và bản đó có tạo được DLL dạng này không bạn? Nếu có thì dùng nó cho vba hợp với mọi người chỉ biết vba.
    Tạo được nhé, hạn chế là vẫn đang ở giai đoạn beta, và khi biên dịch ra DLL dùng cho Office 64bit thì sẽ hiện ra ảnh banner tầm 5 giây khá khó chịu.
    Bạn tải ở đây: twinBASIC BETA .
     
    Tạo được nhé, hạn chế là vẫn đang ở giai đoạn beta, và khi biên dịch ra DLL dùng cho Office 64bit thì sẽ hiện ra ảnh banner tầm 5 giây khá khó chịu.
    Bạn tải ở đây: twinBASIC BETA .

    Hay quá. Tui có vài dự án chết ngỏm vì VB6 không hỗ trợ 64 bit, nếu thằng này làm tốt cho x64 thì mua cũng đáng.
     
    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.
     
    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.
    Vậy bạn hãy trình bày thêm ở đây cho mọi người cùng biết, bài viết này chỉ hướng đến người đã hiểu rõ dll là gì và làm cách nào sử dụng nó mà thôi.
     
    Vấn đề này căng, kiến thức lập trình không đủ, khó đi theo hướng này. Để hướng dẫn đầy đủ chắc mất nhiều trang bài viết. Rồi phải nhồi nhét kiến thức chuyên sâu về lập trình API. Chắc phải tự nỗ lực học tập từ từ thôi. Chứ đến đây, nó là cấp độ lập trình khác hoàn toàn so với những cơ bản của VBA. Dễ thì ai cũng làm được rồi.
    Chưa kể phải biết xử lý mã để trình quét virus xem là an toàn.

    Thật ra vấn đề này không biết được bao nhiêu người đủ khả năng tiếp thu, chắc là 1 hoặc 2 và 3.

    Tôi chỉ có thể đề xuất các bước học được kiến thức này:
    1. Thông thuộc các API xử lý bộ nhớ, xử lý chuỗi, xử lý mảng, ...
    2. Hiểu về giao diện lớp như: QueryInterface, AddRef, Release, GetTypeInfoCount, GetTypeInfo, GetIDsOfNames, Invoke
    3. Hiểu về con trỏ bộ nhớ.
    4. Hiểu về kiến trúc 32 và 64 bit cho lập trình API.
    5. Sử dụng các ứng dụng bổ trợ như COMView, TypeLib Browser để xem thông tin của một DLL.
     
    Mình nghĩ trên đây chỉ hướng dẫn cơ bản thôi chứ còn nâng cao thì vô bờ bến và dành cho người chuyên nghiệp hoặc chủ để nâng cao khác.
     
    Thư viện dll này có cần phải đăng ký với hệ thống không ? hay chỉ cần trỏ tới là được vậy bạn @nguyendang95 ?
     
    Thư viện dll này có cần phải đăng ký với hệ thống không ? hay chỉ cần trỏ tới là được vậy bạn @nguyendang95 ?
    Không cần đăng ký nhé, chỉ OCX và ActiveX DLL mới cần đăng ký, trình đăng ký như regsvr32 và regasm sẽ gọi hàm DllRegisterServer có trong OCX và ActiveX DLL để ghi thông tin cần thiết vào registry của hệ thống.
    Bài đã được tự động gộp:

    Vấn đề này căng, kiến thức lập trình không đủ, khó đi theo hướng này. Để hướng dẫn đầy đủ chắc mất nhiều trang bài viết. Rồi phải nhồi nhét kiến thức chuyên sâu về lập trình API. Chắc phải tự nỗ lực học tập từ từ thôi. Chứ đến đây, nó là cấp độ lập trình khác hoàn toàn so với những cơ bản của VBA. Dễ thì ai cũng làm được rồi.
    Chưa kể phải biết xử lý mã để trình quét virus xem là an toàn.

    Thật ra vấn đề này không biết được bao nhiêu người đủ khả năng tiếp thu, chắc là 1 hoặc 2 và 3.

    Tôi chỉ có thể đề xuất các bước học được kiến thức này:
    1. Thông thuộc các API xử lý bộ nhớ, xử lý chuỗi, xử lý mảng, ...
    2. Hiểu về giao diện lớp như: QueryInterface, AddRef, Release, GetTypeInfoCount, GetTypeInfo, GetIDsOfNames, Invoke
    3. Hiểu về con trỏ bộ nhớ.
    4. Hiểu về kiến trúc 32 và 64 bit cho lập trình API.
    5. Sử dụng các ứng dụng bổ trợ như COMView, TypeLib Browser để xem thông tin của một DLL.
    1 và 3: Cứ C/C++ mà học thôi.
    2. Cái này liên quan đến COM rồi, nếu cần tạo ActiveX DLL thì cần nắm vững kiến thức về COM chứ bài viết này chỉ bàn về Standard DLL.
    Nói chung là nắm được cách tạo standard dll thì có thể làm được nhiều thứ, một số phần mềm cho phép bổ sung tính năng thông qua plugin hoặc add-in dưới dạng dll như thế này, ví dụ như add-in xll của Excel và data plugin dùng cho Amibroker mà tôi hay viết cho một số người chơi chứng khoán và tiền mã hóa.
     
    Lần chỉnh sửa cuối:
    Tôi chỉ đề xuất các hướng để đạt được cách gọi hàm DLL từ VBA, không phải COM gì ở đây.

    Bạn sang đây xem một cách triển khai Standard DLL, mà không phụ thuộc tham chiếu API
    https://www.giaiphapexcel.com/diendan/threads/166377/#post-1140435

    Tôi không thích Amibroker cho lắm, giao diện tệ hại, và nó là có phí. Thời hiện đại rồi, viết tận dụng giao diện Web hiện đại hoặc WebView2, hoặc ElectronJs mà viết.
    TradingView là một ví dụ điển hình.
     
    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ách tạo mảng SAFEARRAY trong Delphi có thể làm giống như C/C++ và cũng có cách để Delphi tự làm như sau
    //----------------------------------------------------
    function GetDemoArray1D(): OleVariant; stdcall
    var
    n: Integer;
    arr: array of string;
    begin
    SetLength(arr, 2);
    for n := 0 to 1 do
    arr[n] := 'Đây là phần tử thứ ' + n.ToString;
    Result := arr;// Delphi auto convert dynamic array 1D to safearray.
    end;
    //----------------------------------------------------
    exports
    GetDemoArray1D;
    //----------------------------------------------------


    Delphi có hai cách lập trình vừa cho phép người lập trình tự tay viết từng lệnh xử lý chi tiết bên trong giống C/C++ và cũng vừa cho phép người lập trình làm hết sức đơn giản và để Delphi tự làm giúp các kỹ thuật cần thiết đằng sau đó, việc này giống như VB/VBA. Nên Delphi và VBA rất giống nhau nếu lập trình ở mức cơ bản, còn lập trình nâng cao thì Delphi cũng có các kỹ thuật khá giống với C/C++. Nếu ai muốn thêm một lựa chọn ngôn ngữ lập trình Delphi để tạo Standard DLL hay các kiểu thì xem chủ đề này mình chia sẻ trên GPE lâu rồi.

    Nếu ai không muốn học thêm một ngôn ngữ mới như C/C++, Delphi để tạo DLL hoặc không muốn lập trình can thiệp sâu vào hệ thống thì lựa chọn TwinBasic có lẽ là phù hợp nhất hiện nay.

    TwinBasic hình như chưa có Remote Debug và chưa Debug DLL trên Host App như Delphi và các IDE của .NET thì phải.
     
    Cách tạo mảng SAFEARRAY trong Delphi có thể làm giống như C/C++ và cũng có cách để Delphi tự làm như sau
    //----------------------------------------------------
    function GetDemoArray1D(): OleVariant; stdcall
    var
    n: Integer;
    arr: array of string;
    begin
    SetLength(arr, 2);
    for n := 0 to 1 do
    arr[n] := 'Đây là phần tử thứ ' + n.ToString;
    Result := arr;// Delphi auto convert dynamic array 1D to safearray.
    end;
    //----------------------------------------------------
    exports
    GetDemoArray1D;
    //----------------------------------------------------


    Delphi có hai cách lập trình vừa cho phép người lập trình tự tay viết từng lệnh xử lý chi tiết bên trong giống C/C++ và cũng vừa cho phép người lập trình làm hết sức đơn giản và để Delphi tự làm giúp các kỹ thuật cần thiết đằng sau đó, việc này giống như VB/VBA. Nên Delphi và VBA rất giống nhau nếu lập trình ở mức cơ bản, còn lập trình nâng cao thì Delphi cũng có các kỹ thuật khá giống với C/C++. Nếu ai muốn thêm một lựa chọn ngôn ngữ lập trình Delphi để tạo Standard DLL hay các kiểu thì xem chủ đề này mình chia sẻ trên GPE lâu rồi.

    Nếu ai không muốn học thêm một ngôn ngữ mới như C/C++, Delphi để tạo DLL hoặc không muốn lập trình can thiệp sâu vào hệ thống thì lựa chọn TwinBasic có lẽ là phù hợp nhất hiện nay.

    TwinBasic hình như chưa có Remote Debug và chưa Debug DLL trên Host App như Delphi và các IDE của .NET thì phải.
    Trong Delphi chắc là gói gọn các hàm trong comdef.h, comutil.h sao cho tương thích với kiểu dữ liệu của Delphi hơn.
    Nói về cách tạo mảng SAFEARRAY, sử dụng mấy hàm C như SafeArrayCreate, SafeArrayGetElement, SafeArrayAccessData, SafeArrayDestroy, v.v., khá rườm rà, khiến cho code trở nên dài dòng và phức tạp nên mấy năm trước Microsoft tạo ra lớp ATL gói gọn là Simplify Safe Array Programming in C++ with CComSafeArray để dùng cho tiện.
    TwinBasic vẫn đang ở dạng beta thôi, giờ đang lo sửa mấy cái lỗi bug lặt vặt liên quan đến form chưa xong, làm xong mấy tính năng debug vẫn còn xa lắm.
    Bài đã được tự động gộp:

    Tôi chỉ đề xuất các hướng để đạt được cách gọi hàm DLL từ VBA, không phải COM gì ở đây.

    Bạn sang đây xem một cách triển khai Standard DLL, mà không phụ thuộc tham chiếu API
    https://www.giaiphapexcel.com/diendan/threads/166377/#post-1140435

    Tôi không thích Amibroker cho lắm, giao diện tệ hại, và nó là có phí. Thời hiện đại rồi, viết tận dụng giao diện Web hiện đại hoặc WebView2, hoặc ElectronJs mà viết.
    TradingView là một ví dụ điển hình.
    Tôi thấy Amibroker cũng ok mà, giao diện tuy hơi lạc hậu so với thời đại hiện nay, nhưng hiệu năng chạy rất tốt, tôi từng viết data plugin lấy dữ liệu thời gian thực bằng websocket và Amibroker chạy cực kỳ mượt, treo 50 mã vẫn mượt, dung lượng RAM tiêu tốn cũng ít.
     
    Trong Delphi chắc là gói gọn các hàm trong comdef.h, comutil.h sao cho tương thích với kiểu dữ liệu của Delphi hơn.
    Nói về cách tạo mảng SAFEARRAY, sử dụng mấy hàm C như SafeArrayCreate, SafeArrayGetElement, SafeArrayAccessData, SafeArrayDestroy, v.v., khá rườm rà, khiến cho code trở nên dài dòng và phức tạp nên mấy năm trước Microsoft tạo ra lớp ATL gói gọn là Simplify Safe Array Programming in C++ with CComSafeArray để dùng cho tiện.

    Delphi có các thư viện rất mạnh họ đã viết lại và wrap đến các hàm API của Windows, ví dụ làm việc với kiểu tagVARIANT (đây là kiểu dữ liệu phổ biết cho lập trình COM/OLE mà lập trình VBA chúng ta hay đụng đến như kiểu Variant) họ tạo ra kiểu Variant, OleVariant giao tiếp với các hàm API và thực hiện gán và nhận dễ dàng giống như trong VB/VBA. Các hàm SafeArrayCreate, SafeArrayGetElement, SafeArrayAccessData, SafeArrayDestroy,... có nhiệm vụ để làm việc với mảng SAFEARRAY mà Windows quy định, chúng nằm trong DLL OleAut32.dll, các ngôn ngữ lập trình như C/C++, Delphi, VB/VBA, C#,... chỉ là wrap đến chúng mà thôi. Bản chất các trình biên dịch chỉ hỗ trợ các tiện ích, thư viện để đơn giản hóa còn bản chất đằng sau đó chúng đều làm những gì mà Windows quy định. Ở level cao, trong hoàn cảnh nào đó vẫn phải moi móc thủ công với các hàm API dù rườm rà, nếu cứ dùng những thứ tự động không phải lúc nào cũng đạt được đúng mục đích.
     
    Lần chỉnh sửa cuối:
    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ị.

    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); //<== Lỗi ở đây
        }
        varResult.vt = VT_ARRAY | VT_BSTR;
        varResult.parray = psa;
        return varResult;
    }

    Tôi xem lại ví dụ viết bằng C của bạn có một lỗi nghiêm trọng đó là dòng lệnh giải phóng bộ nhớ SysFreeString(bstrVal); sau khi đã SafeArrayPutElement thành công. Việc này là đã tự xóa vùng nhớ của con trỏ BSTR trong phần tử mảng đó. Chạy trên một hoàn cảnh nào đó có thể không thấy lỗi nhưng nó luôn tiềm ẩn lỗi bất cứ lúc nào trong quá trình cấp phát vùng nhớ để lưu chuỗi, dấu hiệu là giá trị lấy ra từ mảng là dữ liệu trắng hoặc rác. Lỗi này trình biên dịch không phát hiện được mà nó nằm ở cấp phát bộ nhớ lưu dữ liệu.

    Thực tế tạo hàm trong DLL để export dùng bên ngoài không hề dễ nếu liên quan đến mảng và string. Đặc biệt với C/C++ người lập trình phải tự làm rất nhiều, vì phải tự làm nên nếu không cẩn thận nhầm lẫn hoặc không đúng nguyên lý thì ứng dụng sẽ tai nạn bất cứ lúc nào.
     
    Lần chỉnh sửa cuối:
    Tôi xem lại ví dụ viết bằng C của bạn có một lỗi nghiêm trọng đó là dòng lệnh giải phóng bộ nhớ SysFreeString(bstrVal); sau khi đã SafeArrayPutElement thành công. Việc này là đã tự xóa vùng nhớ của con trỏ BSTR trong phần tử mảng đó. Chạy trên một hoàn cảnh nào đó có thể không thấy lỗi nhưng nó luôn tiềm ẩn lỗi bất cứ lúc nào trong quá trình cấp phát vùng nhớ để lưu chuỗi, dấu hiệu là giá trị lấy ra từ mảng là dữ liệu trắng hoặc rác. Lỗi này trình biên dịch không phát hiện được mà nó nằm ở cấp phát bộ nhớ lưu dữ liệu.

    Thực tế tạo hàm trong DLL để export dùng bên ngoài không hề dễ nếu liên quan đến mảng và string. Đặc biệt với C/C++ người lập trình phải tự làm rất nhiều, vì phải tự làm nên nếu không cẩn thận nhầm lẫn hoặc không đúng nguyên lý thì ứng dụng sẽ tai nạn bất cứ lúc nào.
    Vậy tại sao macro VBA trong bài viết vẫn cho ra kết quả bình thường mà đáng lẽ theo suy luận của bạn thì chương trình phải bị crash ngay khi VBA truy cập phần tử bất kỳ của mảng, hoặc khi gỡ lỗi (debug) bằng trình soạn thảo code thì hiện thông báo lỗi access violation (đại loại là truy cập đến địa chỉ vùng nhớ không tồn tại)? Câu trả lời đơn giản thôi, hàm SafeArrayPutElement chỉ sao chép giá trị mà con trỏ trỏ đến thôi. Bạn xem phần Lưu ý (Remarks) dưới đây thì sẽ hiểu. Hàm sau khi được gọi dù thành công (SUCCEEDED(hr) == TRUE) hay không thì phải giải phóng bộ nhớ đã cấp phát cho biến bstrVal, nếu không chương trình sẽ bị rò rỉ bộ nhớ (memory leak) theo thời gian.
    SafeArrayPutElement function (oleauto.h)
    Allocating and Releasing Memory for a BSTR
    1743584627278.png
    Tham khảo thêm một số câu trả lời trên Stack Overflow:
    COM: Create a VT_ARRAY with VT_BSTR values
    Regarding SafeArrayPutElement
     
    Lần chỉnh sửa cuối:
    Vậy tại sao macro VBA trong bài viết vẫn cho ra kết quả bình thường mà đáng lẽ theo suy luận của bạn thì chương trình phải bị crash ngay khi VBA truy cập phần tử bất kỳ của mảng, hoặc khi gỡ lỗi (debug) bằng trình soạn thảo code thì hiện thông báo lỗi access violation (đại loại là truy cập đến địa chỉ vùng nhớ không tồn tại)? Câu trả lời đơn giản thôi, hàm SafeArrayPutElement chỉ sao chép giá trị mà con trỏ trỏ đến thôi. Bạn xem phần Lưu ý (Remarks) dưới đây thì sẽ hiểu. Hàm sau khi được gọi thành công (SUCCEEDED(hr) == TRUE) thì phải giải phóng bộ nhớ đã cấp phát cho biến bstrVal, nếu không chương trình sẽ bị rò rỉ bộ nhớ (memory leak) theo thời gian.
    SafeArrayPutElement function (oleauto.h)
    Allocating and Releasing Memory for a BSTR
    View attachment 307723
    Tham khảo thêm một số câu trả lời trên Stack Overflow:
    COM: Create a VT_ARRAY with VT_BSTR values
    Regarding SafeArrayPutElement

    Tôi từng gặp lỗi này và khi không dùng SysFreeString thì không bị nữa. Loại lỗi này như tôi nói nó biểu hiện là mất dữ liệu hoặc dữ liệu rác chứ không gây lỗi kiểu mất địa chỉ truy xuất dữ liệu như "access violation". Còn nếu hàm SafeArrayPutElement copy BSTR thì đương nhiên không có lỗi và sẽ phải Free nó sau khi copy thành công. MS không nói rõ lắm cơ chế Put data của hàm này với loại string.
     
    Tôi từng gặp lỗi này và khi không dùng SysFreeString thì không bị nữa. Loại lỗi này như tôi nói nó biểu hiện là mất dữ liệu hoặc dữ liệu rác chứ không gây lỗi kiểu mất địa chỉ truy xuất dữ liệu như "access violation". Còn nếu hàm SafeArrayPutElement copy BSTR thì đương nhiên không có lỗi và sẽ phải Free nó sau khi copy thành công. MS không nói rõ lắm cơ chế Put data của hàm này với loại string.
    Vấn đề này theo tôi nghĩ phải xem kỹ lại tài liệu của các hàm liên quan đến kiểu BSTR trong Delphi mới rõ được. Trong C/C++ với kiểu BSTR và VARIANT, lập trình viên có thể tự động cấp phát và giải phóng bộ nhớ bằng cách sử dụng lớp gói gọn của hai kiểu dữ liệu này mà Microsoft cung cấp lần lượt là _bstr_t class_variant_t Class, ngoài ra chúng còn cung cấp một số phương thức giúp tương tác với hai kiểu dữ liệu này vừa chính xác lại vừa an toàn, ví dụ như có thể dễ dàng ghép hai chuỗi (concatenate) BSTR bằng cách gói gọn chúng trong lớp gói gọn _bstr_t và dùng toán tử "+"giữa hai lớp gói gọn là xong, không cần phải phiền phức gọi SysAllocString, SysAllocStringLen, SysAllocStringByteLen, SysAllocByteString, SysStringLen, SysStringByteLen rồi lại memcpy vừa phiền phức lại vừa dễ mắc sai lầm.
     
    Lần chỉnh sửa cuối:
    Vấn đề này theo tôi nghĩ phải xem kỹ lại tài liệu của các hàm liên quan đến kiểu BSTR trong Delphi mới rõ được. Trong C/C++ với kiểu BSTR và VARIANT, lập trình viên có thể tự động cấp phát và giải phóng bộ nhớ bằng cách sử dụng lớp gói gọn của hai kiểu dữ liệu này mà Microsoft cung cấp lần lượt là _bstr_t class_variant_t Class, ngoài ra chúng còn cung cấp một số phương thức giúp tương tác với hai kiểu dữ liệu này vừa chính xác lại vừa an toàn, ví dụ như có thể dễ dàng ghép hai chuỗi (concatenate) BSTR bằng cách gói gọn chúng trong lớp gói gọn _bstr_t và dùng toán tử "+"giữa hai lớp gói gọn là xong, không cần phải phiền phức gọi SysAllocString, SysAllocStringLen, SysAllocStringByteLen, SysAllocByteString, SysStringLen, SysStringByteLen rồi lại memcpy vừa phiền phức lại vừa dễ mắc sai lầm.

    Về String và Variant thì Delphi làm rất tốt, tiện dụng y như lập trình trong VB/VBA là bạn hiểu. Mọi thứ cấp phát và giải phóng bộ nhớ liên quan đến Variant, String Delphi tự làm cho hết (kiểu con trỏ thì khác). Chỉ khi muốn tự làm thì mới phải làm giống C/C++ thôi.
     
    Đã 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.
     
    Web KT

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

    Back
    Top Bottom