Tạo, sử dụng COM DLL viết bằng Visual C# .NET trong VBA

Liên hệ QC
Tôi tuân thủ nội quy khi đăng bài

nguyendang95

Thành viên chính thức
Tham gia
25/5/22
Bài viết
66
Được thích
74
Trong quá trình viết macro cho Excel nói chung và bộ phần mềm Office nói riêng, chắc hẳn không ít người dùng cảm thấy những tính năng mà VBA mang lại chưa đáp ứng được nhu cầu lập trình. Thông thường, VBA có thể sử dụng lớp (class) từ COM DLL được viết bằng nhiều ngôn ngữ lập trình khác nhau, và đa phần những COM DLL thường đóng vai trò là phần gói gọn (wrapper) chức năng hoặc tính năng nào đó mà VBA còn thiếu, ví dụ như gói gọn lớp ClientWebsocket của nền tảng .NET kèm theo một số phương thức để kết nối, nhận gửi và ngắt kết nối với máy chủ Websocket chẳng hạn.
Để tạo COM DLL, người dùng cần khởi chạy Visual Studio bằng quyền quản trị viên (Run as Administrator, cái này rất quan trọng vì khi Build, Visual Studio sẽ đăng ký COM DLL với tiện ích Regasm để tiện gỡ lỗi khi chạy trong VBA), sau đó tạo một dự án (project) mới là Class Library (.NET Framework).
1729138016334.png
Để VBA có thể thấy được COM DLL trong hộp thoại Tools - References trong trình soạn thảo code VBE, từ Visual Studio người dùng chọn Project - <tên_dự_án> properties. Tiếp theo chọn tab Application - Assembly Information - nhấp chọn Make assembly COM-visible.
1729138371507.png
Tiếp theo, cũng trong cửa sổ <tên_dự_án> properties, người dùng chọn tab Build - nhấp chọn Register for COM interop, sau đó nhấn tổ hợp Ctrl+S để lưu lại mọi thay đổi.
Sau khi đã thiết lập xong các bước cần thiết, tiến hành viết code.
Ví dụ trong bài viết này là một COM DLL gói gọn đơn giản với chức năng kết nối đến máy chủ websocket có địa chỉ là ws://websockets.chilkat.io/wsChilkatEcho.ashx, máy chủ này nhận thông điệp (message) dạng chuỗi và phản hồi (echo) về người dùng đúng như những gì người dùng đã gửi. Máy chủ websocket dạng này thường chỉ được dùng để kiểm tra liệu code của người dùng đã viết đúng quy cách hay chưa.
Đầu tiên, tạo một giao diện (interface) mới chứa các phương thức (method), thuộc tính (property), sự kiện (event) cần dùng và gắn các thuộc tính (attribute) cần thiết. Đây chính là cơ sở để trình Object Browser và intellisense của VBE có thể thấy được những class trong COM DLL chứa những phương thức, thuộc tính và sự kiện nào. Với event, người dùng cần tạo riêng một interface chứa các phương thức tương ứng với các event cần dùng cho class.
C#:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace Websocket
{
    [Guid("952E3490-2EEA-4923-ADFC-E31887551375")]
    [ComVisible(true)]
    [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
    public interface IWebsocketEchoClient
    {
        Task Send(string message);
        Task Connect();
        Task Disconnect();
    }
    //Sự kiện (Events)
    [ComVisible(true)]
    [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
    [Guid("45944D28-C95F-4EB3-AA25-CDEC402CF76D")]
    public interface IEvents
    {
        void ConnectionOpened();
        void ConnectionClosed();
        void ErrorOccurred(string message);
        void DataReceived(string data);
    }
}
Cuối cùng, tạo một class mới, thừa hưởng và cho triển khai interface, sau đó thiết lập các attribute cần thiết. Riêng với event, người dùng cần chỉ định thêm attribute là [ComSourceInterfaces(typeof(interface_dùng_cho_event))] (ví dụ: [ComSourceInterfaces(typeof(IEvents))]).
Lưu ý: Khi khai báo event trong class, người dùng thay vì sử dụng delegate theo khuyến cáo của Microsoft là EventHandler hoặc EventHandler<T> thì nên khai báo delegate có chữ ký (signature) trùng với signature của phương thức đã được định nghĩa trong interface, đồng thời tên của event cần phải trùng với tên của phương thức có signature trùng với delegate dùng cho event.
Vd: public delegate void ErrorOccurredDel(string message) là delegate có signature trùng với signature của phương thức void ErrorOccurred(string message), và public event ErrorOccurredDel ErrorOccurred là event trùng tên với phương thức ErrorOccurred.
C#:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.WebSockets;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Websocket
{
    [Guid("8C87E804-372B-44F0-ABC6-DE8541DA0407")]
    [ComVisible(true)]
    [ClassInterface(ClassInterfaceType.None)]
    [ComSourceInterfaces(typeof(IEvents))]
    public class WebsocketEchoClient : IWebsocketEchoClient
    {
        private ClientWebSocket Client;
        public delegate void ErrorOccurredDel(string message);
        public event ErrorOccurredDel ErrorOccurred;
        public delegate void ConnectionOpenedDel();
        public event ConnectionOpenedDel ConnectionOpened;
        public delegate void ConnectionClosedDel();
        public event ConnectionClosedDel ConnectionClosed;
        public delegate void DataReceivedDel(string data);
        public event DataReceivedDel DataReceived;
        public async Task Connect()
        {
            Client = new ClientWebSocket();
            ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls13;
            try
            {
                await Client.ConnectAsync(new Uri("ws://websockets.chilkat.io/wsChilkatEcho.ashx"), CancellationToken.None);
                if (Client.State == WebSocketState.Open)
                {
                    ConnectionOpened?.Invoke();
                    await Task.Run(async () => await Receive());
                }
            }
            catch (WebSocketException ex)
            {
                ErrorOccurred?.Invoke(ex.Message);
            }
        }
        public async Task Disconnect()
        {
            try
            {
                if (Client.State == WebSocketState.Open)
                {
                    await Client.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None);
                    ConnectionClosed?.Invoke();
                }
            }
            catch (WebSocketException Ex)
            {
                ErrorOccurred?.Invoke(Ex.Message);
            }
        }
        public async Task Send(string message)
        {
            try
            {
                if (Client.State == WebSocketState.Open)
                {
                    byte[] Buffer = Encoding.UTF8.GetBytes(message);
                    ArraySegment<byte> AS = new ArraySegment<byte>(Buffer);
                    await Client.SendAsync(AS, WebSocketMessageType.Text, true, CancellationToken.None);
                }
            }
            catch (WebSocketException Ex)
            {
                ErrorOccurred?.Invoke(Ex.Message);
            }
        }
        private async Task Receive()
        {
            try
            {
                while (Client.State == WebSocketState.Open)
                {
                    using (MemoryStream Ms = new MemoryStream())
                    {
                        byte[] Buffer = new byte[1024];
                        ArraySegment<byte> AS = new ArraySegment<byte>(Buffer);
                        WebSocketReceiveResult Result;
                        do
                        {
                            Result = await Client.ReceiveAsync(AS, CancellationToken.None);
                            if (Result.MessageType == WebSocketMessageType.Binary)
                            {
                                ErrorOccurred?.Invoke("Unexpected data type from the server.");
                                return;
                            }
                            if (Result.MessageType == WebSocketMessageType.Close)
                            {
                                await Client.CloseAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None);
                                return;
                            }
                            if (Result.Count > 0)
                            {
                                await Ms.WriteAsync(AS.Array, AS.Offset, Result.Count);
                            }
                        }
                        while (!Result.EndOfMessage);
                        Ms.Seek(0, SeekOrigin.Begin);
                        using (StreamReader Sr = new StreamReader(Ms, Encoding.UTF8))
                        {
                            string Response = Sr.ReadToEnd();
                            DataReceived?.Invoke(Response);
                        }
                    }
                }
            }
            catch (WebSocketException Ex)
            {
                ErrorOccurred?.Invoke(Ex.Message);
            }
        }
    }
}
Tiến hành Build ra DLL và sử dụng trong VBA. Lưu ý: Để sử dụng trên các máy tính khác, cần phải đăng ký với tiện ích Regasm trên chính máy tính ấy (yêu cần quyền quản trị viên).
1729142004572.png
1729142064752.png
1729142668325.png
 

File đính kèm

  • WebsocketWrapper_VBA.zip
    80 KB · Đọc: 7
Lần chỉnh sửa cuối:
Viết được tới đó là trạm vào cửa Viết Web Server máy chủ máy khách rồi đó

Tiếp theo áp dụng cho vận chuyển dữ liệu đến và đi qua Web Server nữa + các kiểu vvv...............

Vậy là Excel thừa hưởng cái máy chủ máy khách với các sự kiện trên Excel

Chờ xem sao ??!!!!!!!!!!!! hay dừng ở Hello world
 
Web KT

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

Back
Top Bottom