Tạo đối tượng dạng tập hợp (Collection) tương tự như VBA bằng Visual C++ ATL Project (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
    163
    Được thích
    154
    Khi lập trình VBA, chắc hẳn người dùng đều đã quen thuộc với đối tượng Collection của VBA, đây là đối tượng có chức năng lưu trữ giá trị với nhiều kiểu dữ liệu khác nhau từ chuỗi, số cho đến đối tượng, v.v. Kiểu đối tượng này thực chất là đối tượng có triển khai interface IEnumVARIANT gồm bốn phương thức dưới đây:
    • Clone
    • Skip
    • Next
    • Reset
    Với VBA, nó chỉ quan tâm phương thức Next khi cần duyệt qua đối tượng dạng tập hợp bằng khối lệnh For Each. Ngoài ra để chuẩn bị cho khối lệnh For Each, VBA cũng cần đối tượng dạng tập hợp đó phải có thuộc tính _NewEnum và có mã dispid là DISPID_NEWENUM.
    Dưới đây là code mẫu viết bằng Visual C++ mô tả một đối tượng dạng tập hợp tương thích với khối lệnh For Each và hỗ trợ những phương thức, thuộc tính cơ bản của Collection trong VBA như Item, Remove và Add.
    Trong tệp .idl, khai báo các phương thức, thuộc tính cần thiết mà đối tượng hỗ trợ, lưu ý từ khóa restricted dùng cho thuộc tính _NewEnum nhằm báo hiệu rằng thuộc tính này chỉ VBA biết và người dùng sẽ không thể sử dụng nó, ngoài ra còn thiết lập từ khóa id(DISPID_VALUE) cho thuộc tính Item nhằm xác định đây là thuộc tính mặc định của tập hợp, tức là thay vì viết obj.Item người dùng có thể viết gọn là obj.
    C++:
    [
        object,
        uuid(43d3183c-6ed2-4631-9342-34b55eeae317),
        dual,
        nonextensible,
        pointer_default(unique)
    ]
    interface IList : IDispatch
    {
        [propget] HRESULT Count([out, retval] long* pVal);
        [propget, restricted, id(DISPID_NEWENUM)] HRESULT _NewEnum([out, retval]IUnknown** ppEnum);
        [id(DISPID_VALUE), propget] HRESULT Item([in] long Index, [out, retval] VARIANT* pVal);
        HRESULT Add([in]VARIANT val);
        HRESULT Remove([in]long Index);
    };

    Toàn bộ logic của đối tượng dạng tập hợp đều ở dưới đây:
    List.h
    C++:
    // List.h : Declaration of the CList
    
    #pragma once
    #include "resource.h"       // main symbols
    
    
    
    #include "VBAEnumeratorSimulation_i.h"
    #include <vector>
    
    
    
    #if defined(_WIN32_WCE) && !defined(_CE_DCOM) && !defined(_CE_ALLOW_SINGLE_THREADED_OBJECTS_IN_MTA)
    #error "Single-threaded COM objects are not properly supported on Windows CE platform, such as the Windows Mobile platforms that do not include full DCOM support. Define _CE_ALLOW_SINGLE_THREADED_OBJECTS_IN_MTA to force ATL to support creating single-thread COM object's and allow use of it's single-threaded COM object implementations. The threading model in your rgs file was set to 'Free' as that is the only threading model supported in non DCOM Windows CE platforms."
    #endif
    
    using namespace ATL;
    
    
    // CList
    
    class ATL_NO_VTABLE CList :
        public CComObjectRootEx<CComSingleThreadModel>,
        public CComCoClass<CList, &CLSID_List>,
        public IDispatchImpl<IList, &IID_IList, &LIBID_VBAEnumeratorSimulationLib, /*wMajor =*/ 1, /*wMinor =*/ 0>,
        public IEnumVARIANT
    {
    public:
        CList()
        {
        }
    
    DECLARE_REGISTRY_RESOURCEID(106)
    
    
    BEGIN_COM_MAP(CList)
        COM_INTERFACE_ENTRY(IList)
        COM_INTERFACE_ENTRY(IDispatch)
        COM_INTERFACE_ENTRY(IEnumVARIANT)
    END_COM_MAP()
    
    
    
        DECLARE_PROTECT_FINAL_CONSTRUCT()
    
        HRESULT FinalConstruct()
        {
            return S_OK;
        }
    
        void FinalRelease()
        {
            for (auto& item : varItems)
                VariantClear(&item);
            varItems.clear();
        }
    
    public:
        STDMETHOD(Next)(ULONG celt, VARIANT* rgVar, ULONG* pCeltFetched) override;
        STDMETHOD(Reset)() override;
        STDMETHOD(Skip)(ULONG celt) override;
        STDMETHOD(Clone)(IEnumVARIANT** ppEnum) override;
        STDMETHOD(Initialize)(const std::vector<VARIANT>& items, ULONG index);
        STDMETHOD(get_Count)(long* pVal);
        STDMETHOD(get__NewEnum)(IUnknown** ppEnum);
        STDMETHOD(Add)(VARIANT val);
        STDMETHOD(get_Item)(long Index, VARIANT* pVal);
        STDMETHOD(Remove)(long Index);
    private:
        std::vector<VARIANT> varItems{};
        ULONG ulCurrentIndex = 0;
    
    };
    
    OBJECT_ENTRY_AUTO(__uuidof(List), CList)

    C++:
    #include "pch.h"
    #include "List.h"
    
    //Tạo bản sao của đối tượng
    STDMETHODIMP CList::Clone(IEnumVARIANT** ppEnum) {
        //Kiểm tra điều kiện đầu vào
        if (!ppEnum)
            return E_POINTER;
        CComObject<CList>* pClone = NULL;
        //Khởi tạo đối tượng
        HRESULT hr = CComObject<CList>::CreateInstance(&pClone);
        if (FAILED(hr))
            return hr;
        //Sao chép các giá trị hiện tại của đối tượng được tạo bản sao
        pClone->Initialize(varItems, ulCurrentIndex);
        *ppEnum = pClone;
        (*ppEnum)->AddRef();
        return S_OK;
    }
    
    //Hàm này khởi tạo các giá trị khi tạo đối tượng CList
    STDMETHODIMP CList::Initialize(const std::vector<VARIANT>& items, ULONG index) {
        varItems = items;
        ulCurrentIndex = index;
        return S_OK;
    }
    
    //Bỏ qua số lượng phần tử có trong tập hợp
    STDMETHODIMP CList::Skip(ULONG celt) {
        //Xác định vị trí hiện tại khi bỏ qua số lượng phần tử
        ulCurrentIndex += celt;
        //Xác định số lượng phần tử trong tập hợp
        /*
            Nếu vị trí hiện tại sau khi tính toán xong vượt quá số lượng phần tử trong tập hợp
            Thì lấy phần tử cuối cùng làm vị trí hiện tại
        */
        ULONG ulCount = static_cast<long>(varItems.size());
        if (ulCurrentIndex > ulCount) {
            ulCurrentIndex = ulCount;
            return S_FALSE;
        }
        return S_OK;
    }
    
    //Thiết lập lại vị trí hiện tại là phần tử đầu tiên
    STDMETHODIMP CList::Reset() {
        ulCurrentIndex = 0;
        return S_OK;
    }
    
    /*
        VBA gọi phương thức này khi cần duyệt qua tập hợp
        Bằng khối lệnh For Each
    */
    STDMETHODIMP CList::Next(ULONG celt, VARIANT* rgVar, ULONG* pCeltFetched) {
        //Kiểm tra điều kiện đầu vào
        if (!rgVar) return E_POINTER;
        ULONG fetched = 0;
        HRESULT hr = S_OK;
        /*
            Duyệt qua tập hợp và trả về kết quả căn cứ theo số lượng celt
            Trả kết quả về rgVar
            Nếu VBA yêu cầu báo cáo số lượng kết quả thì trả về cho biến pCeltFetched
        */
        while (fetched < celt && ulCurrentIndex < varItems.size()) {
            hr = VariantCopy(&rgVar[fetched], &varItems[ulCurrentIndex]);
            if (FAILED(hr)) return hr;
            ++ulCurrentIndex;
            ++fetched;
        }
        if (pCeltFetched) *pCeltFetched = fetched;
        return (fetched == celt) ? S_OK : S_FALSE;
    }
    
    //Đếm số lượng phần tử hiện có trong tập hợp
    STDMETHODIMP CList::get_Count(long* pVal) {
        if (!pVal) return E_POINTER;
        *pVal = static_cast<long>(varItems.size());
        return S_OK;
    }
    
    //VBA gọi phương thức này để chuẩn bị cho khối lệnh For Each
    STDMETHODIMP CList::get__NewEnum(IUnknown** ppEnum) {
        //Kiểm tra điều kiện đầu vào
        if (!ppEnum) return E_POINTER;
        CComObject<CList>* pEnum = NULL;
        //Khởi tạo đối tượng
        HRESULT hr = CComObject<CList>::CreateInstance(&pEnum);
        if (FAILED(hr)) return hr;
        pEnum->AddRef();
        pEnum->Initialize(varItems, ulCurrentIndex);
        //Trả về interface IUnknown mà VBA yêu cầu
        hr = pEnum->QueryInterface(IID_IUnknown, reinterpret_cast<LPVOID*>(ppEnum));
        if (FAILED(hr)) pEnum->Release();
        return hr;
    }
    
    //Thêm giá trị vào tập hợp
    STDMETHODIMP CList::Add(VARIANT val) {
        VARIANT varResult = {};
        VariantInit(&varResult);
        //Sao chép giá trị cần đưa vào tập hợp
        HRESULT hr = VariantCopy(&varResult, &val);
        if (FAILED(hr)) return hr;
        varItems.push_back(val);
        return S_OK;
    }
    
    //Lấy giá trị từ tập hợp
    STDMETHODIMP CList::get_Item(long Index, VARIANT* pVal) {
        if (varItems.empty() || Index >= varItems.size() || Index < 0) return E_FAIL;
        return VariantCopy(pVal, &varItems[Index]);
    }
    
    //Xóa phần tử khỏi tập hợp
    STDMETHODIMP CList::Remove(long Index) {
        /*
            Kiểm tra điều kiện đầu vào
            Tập hợp không rỗng, chỉ số không âm và không vượt quá kích thước của tập hợp
        */
        if (varItems.empty() || Index >= varItems.size() || Index < 0) return E_FAIL;
        //Xóa bỏ giá trị, dọn dẹp bộ nhớ
        VariantClear(&varItems[Index]);
        //Xóa phần tử khỏi tập hợp
        varItems.erase(varItems.begin() + Index);
        //Nếu phần tử cần xóa là phần tử cuối cùng trong tập hợp thì cần xác định lại vị trí hiện tại cho chính xác
        if (ulCurrentIndex = (ULONG)varItems.size()) ulCurrentIndex--;
        return S_OK;
    }

    Tiến hành biên dịch ra DLL và cho chạy thử. Trong chế độ gỡ lỗi (debugging), khi bước vào khối lệnh For Each của VBA, người dùng có thể thấy thuộc tính _NewEnum sẽ được gọi.

    1759378665895.png

    Khi trong khối lệnh For Each, VBA sẽ gọi phương thức Next để lấy giá trị của từng phần tử trong tập hợp.

    1759378748760.png

    Để tìm hiểu thêm về interface IEnumVARIANT, tham khảo bài viết IEnumVARIANT interface (oaidl.h).
    Cách triển khai interface IEnumVARIANT: Implementing the IEnumVARIANT IInterface
    Cách triển khai thuộc tính _NewEnum: Implementing the _NewEnum Property.
     

    File đính kèm

    Lần chỉnh sửa cuối:
    hảo huynh đệ có cách nào xáo trộn mã, mã hóa... code c# excel dna hiệu quả k?. Viết trực tiếp xll c++ tất cả các tác vụ vẫn khó khăn
     

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

    Back
    Top Bottom