Tạo COM Add-In bằng Visual C++ (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
127
Được thích
114
Hiện nay để phát triển phần bổ trợ (add-in) cho Excel riêng và các ứng dụng Office khác nói chung, Microsoft cung cấp cho lập trình viên những công cụ sau đây:
  • VSTO: Được xây dựng trên nền tảng .NET Framework, cung cấp một bộ thư viện đồ sộ giúp lập trình viên có thể dễ dàng xây dựng một add-in bằng Visual Basic hoặc Visual C#. Nhược điểm: Do phải truy cập vào mô hình đối tượng của ứng dụng thông qua lớp biên dịch (translation) com interop, cho nên hiệu năng mang lại không cao so với những giải pháp khác. Ngoài ra, giải pháp này chỉ có thể chạy trên Windows.
  • Office Javascript API: Cho phép lập trình viên có thể viết add-in bằng Javascript hoặc Typescript chạy đa nền tảng, đa thiết bị, là giải pháp đang được Microsoft khuyến khích nhất hiện nay. Nhược điểm: Triển khai khá phức tạp, yêu cầu kiến thức chuyên sâu về lập trình web.
  • COM Add-In: Giải pháp cổ điển, trong đó yêu cầu lập trình viên viết một COM DLL có triển khai giao diện (interface) IDTExtensibility2 và IRibbonExtensibility để tương tác với ứng dụng chủ (host) và làm việc với giao diện ribbon. Có thể viết bằng bất cứ ngôn ngữ nào cho phép làm việc với COM, từ C/C++, VB6, Delphi cho đến ngôn ngữ nền .NET Framework (Visual Basic và Visual C#) thông qua com interop. Nhược điểm: Triển khai rất phức tạp, yêu cầu kiến thức sâu rộng về COM.
Bài viết này trình bày các bước viết một Excel COM Add-In bằng C++.
Tạo COM DLL:
ATL Project là một giải pháp gồm một bộ các lớp (class) tiêu bản (template) và macro do Microsoft phát triển giúp đơn giản hoá quá trình phát triển một COM DLL và ActiveX control. Để sử dụng, người dùng có thể sử dụng bản Visual Studio mới nhất hoặc những phiên bản khác có hỗ trợ ATL Project.

1751784602911.png

Lưu ý: Tính đến thời điểm hiện tại (6-7-2025) Visual Studio đang tồn tại một số lỗi (bug) nghiêm trọng khiến cho việc phát triển bằng ATL Project trở nên rất khó khăn, cho nên trong bài viết này sử dụng phiên bản Visual Studio 2019 để trình bày code.
Dưới đây là một trong những lỗi đã được Microsoft ghi nhận:
"Bad file path: 'project_name.idl'" error when creating ATL Simple Objects in an ATL Project created using VS2022 Version 17.13.7
Để tạo ATL Project, người dùng khởi chạy Visual Studio, sau đó từ màn hình chính của Visual Studio, chọn tiếp Create a new project và chọn ATL Project.

1751784623148.png

Tiến hành đặt tên cho dự án và thiết lập một vài tham số khác (nếu có). Trong bài viết này đặt tên dự án là ExcelAddInDemo.

1751784630098.png

Triển khai giao diện IDTExtensibility2 và IRibbonExtensibility:
Lúc này dự án đang trống rỗng, cho nên để add-in thực sự hoạt động được, người dùng cần triển khai giao diện IDTExtensibility2 và IRibbonExtensibility. IDTExtensibility2 gồm năm phương thức (method) OnConnection, OnDisconnection, OnStartupComplete, OnAddInsUpdate và OnBeginShutdown, giao diện này đóng vai trò định hình COM DLL trở thành add-in mà Excel có thể tương tác được. Còn IRibbonExtensibility chỉ có một phương thức duy nhất, GetCustomUI, đóng vai trò trình bày giao diện ribbon tuỳ chỉnh theo mong muốn của người dùng.
Để bắt đầu, trước tiên người dùng cần nhập (import) các thư viện cần thiết trong tập tin pch.h.

C++:
// pch.h: This is a precompiled header file.
// Files listed below are compiled only once, improving build performance for future builds.
// This also affects IntelliSense performance, including code completion and many code browsing features.
// However, files listed here are ALL re-compiled if any one of them is updated between builds.
// Do not add files here that you will be updating frequently as this negates the performance advantage.

#ifndef PCH_H
#define PCH_H

// add headers that you want to pre-compile here
#include "framework.h"
#import "libid:AC0714F2-3D04-11D1-AE7D-00A0C90F26F4" raw_interfaces_only, raw_native_types, no_namespace, named_guids, auto_search
#import "libid:2DF8D04C-5BFA-101B-BDE5-00AA0044DE52" auto_rename auto_search raw_interfaces_only rename_namespace("Office")
#import "libid:00020813-0000-0000-C000-000000000046" auto_rename auto_search rename("RGB", "ExcelRGB") rename("DocumentProperties", "ExcelDocumentProperties")
using namespace Office;
using namespace Excel;
#endif //PCH_H

Tiếp theo, tạo một ATL Simple Object bằng cách, từ cửa sổ Solution Explorer bên tay phải màn hình, nhấp chuột phải vào tên dự án (ở đây là ExcelAddInDemo), sau đó chọn Add – New Item. Cửa sổ Add New Item hiện ra, người dùng chọn tiếp mục ATL trong phần Visual C++ xổ ra và chọn ATL Simple Object, trong bài viết đặt tên là ThisAddIn.

1751784696862.png

Lưu ý thông tin ở ô ProgID, đây chính là căn cứ để đặt tên khoá registry giúp Excel nhận diện được add-in.

1751784706052.png

Một class mới được tạo ra, người dùng có thể triển khai hai giao diện quan trọng ở trên.

C++:
// ThisAddIn.h : Declaration of the CThisAddIn

#pragma once
#include "resource.h"       // main symbols



#include "ExcelAddInDemo_i.h"



#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;

typedef IDispatchImpl <IRibbonExtensibility, &__uuidof(IRibbonExtensibility), &__uuidof(__Office), 2, 5> RibbonImpl;
typedef IDispatchImpl<_IDTExtensibility2, &__uuidof(_IDTExtensibility2), &LIBID_AddInDesignerObjects, /* wMajor = */ 1, /* wMinor = */ 0> IDTImpl;
// CThisAddIn

class ATL_NO_VTABLE CThisAddIn :
    public CComObjectRootEx<CComSingleThreadModel>,
    public CComCoClass<CThisAddIn, &CLSID_ThisAddIn>,
    public IDispatchImpl<IThisAddIn, &IID_IThisAddIn, &LIBID_ExcelAddInDemoLib, /*wMajor =*/ 1, /*wMinor =*/ 0>,
    public RibbonImpl,
    public IDTImpl
{
public:
    CThisAddIn()
    {
    }

DECLARE_REGISTRY_RESOURCEID(106)


BEGIN_COM_MAP(CThisAddIn)
    COM_INTERFACE_ENTRY(IThisAddIn)
    COM_INTERFACE_ENTRY2(IDispatch, IThisAddIn)
    COM_INTERFACE_ENTRY(_IDTExtensibility2)
    COM_INTERFACE_ENTRY(IRibbonExtensibility)
END_COM_MAP()



    DECLARE_PROTECT_FINAL_CONSTRUCT()

    HRESULT FinalConstruct()
    {
        return S_OK;
    }

    void FinalRelease()
    {
    }

public:
    STDMETHOD(OnConnection)(IDispatch* Application, ext_ConnectMode ConnectMode, IDispatch* AddInInst, SAFEARRAY** custom);
    STDMETHOD(OnDisconnection)(ext_DisconnectMode RemoveMode, SAFEARRAY** custom);
    STDMETHOD(OnAddInsUpdate)(SAFEARRAY** custom);
    STDMETHOD(OnStartupComplete)(SAFEARRAY** custom);
    STDMETHOD(OnBeginShutdown)(SAFEARRAY** custom);
    STDMETHOD(GetCustomUI)(BSTR RibbonID, BSTR* RibbonXml);


};

OBJECT_ENTRY_AUTO(__uuidof(ThisAddIn), CThisAddIn)

C++:
// ThisAddIn.cpp : Implementation of CThisAddIn

#include "pch.h"
#include "ThisAddIn.h"


// CThisAddIn

STDMETHODIMP CThisAddIn::OnConnection(IDispatch* Application, ext_ConnectMode ConnectMode, IDispatch* AddInInst, SAFEARRAY** custom) {
    if (!Application) return E_POINTER;
    MessageBox(NULL, L"Add-in loaded", L"Add-in Event", MB_OK | MB_ICONINFORMATION);
    return S_OK;
}

STDMETHODIMP CThisAddIn::OnDisconnection(ext_DisconnectMode RemoveMode, SAFEARRAY** custom) {
    return S_OK;
}

STDMETHODIMP CThisAddIn::OnAddInsUpdate(SAFEARRAY** custom) {
    return S_OK;
}

STDMETHODIMP CThisAddIn::OnStartupComplete(SAFEARRAY** custom) {
    return S_OK;
}

STDMETHODIMP CThisAddIn::OnBeginShutdown(SAFEARRAY** custom) {
    return S_OK;
}

STDMETHODIMP CThisAddIn::GetCustomUI(BSTR RibbonID, BSTR* RibbonXml) {
    if (!RibbonXml) return E_POINTER;
    *RibbonXml = SysAllocString(LR"(<?xml version="1.0" encoding="UTF-8"?>
                                    <customUI xmlns="http://schemas.microsoft.com/office/2009/07/customui" onLoad="OnRibbonLoaded">
                                      <ribbon>
                                        <tabs>
                                          <tab id="customTab" label="Excel AddIn">
                                            <group id="customGroup" label="Group">
                                              <button id="customButton"
                                                      label="Click Me"
                                                      getImage="GetImage"
                                                      size="large"
                                                      onAction="ButtonClicked" />
                                            </group>
                                          </tab>
                                        </tabs>
                                      </ribbon>
                                    </customUI>)");
    return *RibbonXml ? S_OK : E_OUTOFMEMORY;
}

Đoạn code này nhìn chung khá đơn giản, trong phương thức OnConnection chứa một câu lệnh nhỏ có chức năng hiển thị một hộp thoại, còn phương thức GetCustomUI có tác dụng nạp bố cục (schema) của ribbon theo định dạng XML để Excel có thể trình bày. Để đơn giản, bài viết này chèn trực tiếp schema của ribbon vào code, còn khi viết code thực tế người dùng nên nạp schema vào DLL thông qua mục Resource Files, sau đó lần lượt sử dụng các hàm FindResource, LoadResource và LockResource để đọc nội dung và đưa vào phương thức GetCustomUI sẽ thuận tiện hơn.
Ngoài ra một tập tin MIDL cũng sẽ được tạo ra. Trong bài viết này không sử dụng tập tin này, tuy nhiên nó đóng vai trò quan trọng khi người dùng muốn định nghĩa các hàm gọi lại (callback function) từ các control trên ribbon.

C++:
// ExcelAddInDemo.idl : IDL source for ExcelAddInDemo
//

// This file will be processed by the MIDL tool to
// produce the type library (ExcelAddInDemo.tlb) and marshalling code.

import "oaidl.idl";
import "ocidl.idl";

[
    object,
    uuid(ded9f6b8-b802-4547-97fe-0dabdcd4d205),
    dual,
    nonextensible,
    pointer_default(unique)
]
interface IThisAddIn : IDispatch
{
};
[
    uuid(bd1b5ce2-9b75-49f5-b9a3-c9eb192b1be1),
    version(1.0),
]
library ExcelAddInDemoLib
{
    importlib("stdole2.tlb");
    [
        uuid(2762b776-5488-4564-8cbd-fb71834332ca)
    ]
    coclass ThisAddIn
    {
        [default] interface IThisAddIn;
    };
};

import "shobjidl.idl";

Tiếp theo, người dùng mở tập tin ThisAddIn.rgs trong mục Resource Files trong cửa sổ Solution Explorer và chèn vào đó đoạn mã dưới đây, lưu ý mã ProgID ExcelAddInDemo.ThisAddIn mà người dùng đã thiết lập khi tạo ATL Simple Object:
Mã:
HKCR
{
    ExcelAddInDemo.ThisAddIn.1 = s 'ThisAddIn class'
    {
        CLSID = s '{2762b776-5488-4564-8cbd-fb71834332ca}'
    }
    ExcelAddInDemo.ThisAddIn = s 'ThisAddIn class'
    {    
        CurVer = s 'ExcelAddInDemo.ThisAddIn.1'
    }
    NoRemove CLSID
    {
        ForceRemove {2762b776-5488-4564-8cbd-fb71834332ca} = s 'ThisAddIn class'
        {
            ProgID = s 'ExcelAddInDemo.ThisAddIn.1'
            VersionIndependentProgID = s 'ExcelAddInDemo.ThisAddIn'
            ForceRemove Programmable
            InprocServer32 = s '%MODULE%'
            {
                val ThreadingModel = s 'Apartment'
            }
            TypeLib = s '{bd1b5ce2-9b75-49f5-b9a3-c9eb192b1be1}'
            Version = s '1.0'
        }
    }
}

HKCU
{
    NoRemove Software
    {
        NoRemove Microsoft
        {
            NoRemove Office
            {
                NoRemove Excel
                {
                    NoRemove Addins
                    {
                        ExcelAddInDemo.ThisAddIn
                        {
                            val Description = s 'Sample Addin'
                            val FriendlyName = s 'Sample Addin'
                            val LoadBehavior = d 3
                        }
                    }
                }
            }
        }
    }
}
Cuối cùng, tiến hành biên dịch (build) ra DLL hoàn chỉnh, tuỳ thuộc vào phiên bản Excel cài đặt trên máy tính mà người dùng có thể biên dịch ra DLL phiên bản 32bit hoặc 64bit, sau đó cho Excel nạp add-in.
Kết quả:

1751785125655.png

Bố cục ribbon tuỳ chỉnh.

1751785149545.png
Người dùng có thể tìm thấy code đính kèm theo bài viết này.
 

File đính kèm

Lần chỉnh sửa cuối:
Web KT

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

Back
Top Bottom