Tạo, sử dụng COM DLL viết bằng Visual C# .NET 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

nguyendang95

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

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
 
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
Tôi thường sử dụng kết nối WebSocket để trao đổi dữ liệu theo thời gian thực khi làm việc với một số nhà cung cấp dịch vụ có hỗ trợ loại kết nối này.
Một ví dụ dễ hiểu là viết một script trò chuyện với con bot Microsoft Copilot, chẳng hạn.
1729338936309.png
 
xem xét nếu rảnh tôi sẻ viết WebSocket trên Delphi sử dụng vận chuyển dữ liệu đến và đi qua Internet

nếu viết để trò chuyện vài câu Hello world qua lại thì tôi ít quan tâm nó

Nhưng có nhược điểm cùa WebSocket là nhiều kết nối máy chủ - máy khách duy trì kết nối liên tục quá tải là văng ..

còn xử lý sao thì tôi chưa hình dung ra ... vì năm ngoái có vọc nó rồi vì thấy code bài 1 nên tôi mới lôi ra xem lại chơi cho vui

Cơ bản nó chỉ khác Web Server là duy trì kết nối hai chiều liên tục nhưng nhiều kết nối là sập có lẻ do tôi chưa biết cách xử lý đa luồng cho nó
 
xem xét nếu rảnh tôi sẻ viết WebSocket trên Delphi sử dụng vận chuyển dữ liệu đến và đi qua Internet

nếu viết để trò chuyện vài câu Hello world qua lại thì tôi ít quan tâm nó

Nhưng có nhược điểm cùa WebSocket là nhiều kết nối máy chủ - máy khách duy trì kết nối liên tục quá tải là văng ..

còn xử lý sao thì tôi chưa hình dung ra ... vì năm ngoái có vọc nó rồi vì thấy code bài 1 nên tôi mới lôi ra xem lại chơi cho vui

Cơ bản nó chỉ khác Web Server là duy trì kết nối hai chiều liên tục nhưng nhiều kết nối là sập có lẻ do tôi chưa biết cách xử lý đa luồng cho nó
Bên .NET phải tự viết lấy server, khởi tạo và lắng nghe kết nối đến qua TCP bằng class Socket hoặc TcpClient (dạng gói gọn của Socket nhưng nhỏ gọn hơn và dễ dùng hơn, tất nhiên là khả năng tùy biến cũng hạn chế), sau đó xem tài liệu về RFC6455 rồi viết theo thôi. Mà vấn đề này với tôi thật sự rất khó, tốt nhất là cứ nhờ người có chuyên môn viết cho là tốt nhất, tối ưu về hiệu năng lẫn bảo mật.

nhiều kết nối là sập có lẻ do tôi chưa biết cách xử lý đa luồng cho nó: Cái này nhiều anh em cao thủ trên Stack Overflow khuyên là đừng gán mỗi thread cho mỗi kết nối, sẽ rất dễ khiến cho server bị đổ vỡ (crash) do hết sạch thread từ ThreadPool, tốt nhất là cứ lập trình bất đồng bộ.
 
Bên .NET phải tự viết lấy server, khởi tạo và lắng nghe kết nối đến qua TCP bằng class Socket hoặc TcpClient (dạng gói gọn của Socket nhưng nhỏ gọn hơn và dễ dùng hơn, tất nhiên là khả năng tùy biến cũng hạn chế), sau đó xem tài liệu về RFC6455 rồi viết theo thôi. Mà vấn đề này với tôi thật sự rất khó, tốt nhất là cứ nhờ người có chuyên môn viết cho là tốt nhất, tối ưu về hiệu năng lẫn bảo mật.

nhiều kết nối là sập có lẻ do tôi chưa biết cách xử lý đa luồng cho nó: Cái này nhiều anh em cao thủ trên Stack Overflow khuyên là đừng gán mỗi thread cho mỗi kết nối, sẽ rất dễ khiến cho server bị đổ vỡ (crash) do hết sạch thread từ ThreadPool, tốt nhất là cứ lập trình bất đồng bộ.
Trên Delphi nó cũng không hổ trợ WebSocket mà đo người dùng tự định nghĩa nhưng nó có thư viện Indy đa nền tảng quá tốt

1/ Nếu tự viết lấy thì sử dụng Indy hoặc cái khác mà tôi chưa biết

2/ Nếu không viết được mua hay sử dụng nguồn mở

Tôi dò trên github.com và Google nguồn mở thì nó cũng sử dụng Indy xong tái chế và viết thêm hàm tuỳ chỉnh lướt qua một lượt xong chốt lại

Tôi + Em chatGPT sử dụng Indy thuần Delphi vẫn viết cũng ok ở mức đơn giản định hình khung WebSocket
 
Trên Delphi nó cũng không hổ trợ WebSocket mà đo người dùng tự định nghĩa nhưng nó có thư viện Indy đa nền tảng quá tốt

1/ Nếu tự viết lấy thì sử dụng Indy hoặc cái khác mà tôi chưa biết

2/ Nếu không viết được mua hay sử dụng nguồn mở

Tôi dò trên github.com và Google nguồn mở thì nó cũng sử dụng Indy xong tái chế và viết thêm hàm tuỳ chỉnh lướt qua một lượt xong chốt lại

Tôi + Em chatGPT sử dụng Indy thuần Delphi vẫn viết cũng ok ở mức đơn giản định hình khung WebSocket
Nói chung là cái vấn đề đó phức tạp lắm, không có kiến thức về mạng máy tính thì khó mà tự mình làm được.
Cho nên là mình cứ tập trung vào chuyên môn thế mạnh của mình, cho nhẹ đầu.
Như bên tôi hay làm mấy cái VSTO add-in cho mấy anh em chơi tiền điện tử chẳng hạn.
ezgif-3-524cd3cbda.gif
 
Rảnh dò thử xem. trong Windows ẩn chứa nhiều điều thú vị không công bố hoặc nếu có chỉ hình thức

View attachment 304929



Có nhiều hàm cách đây 20 năm vẫn sử dụng tốt
Viết được cái này bằng thuần C++ thì đầu phải cực kỳ nhiều sạn.
 
Lúc trước khi Tôi dò lấy dữ liệu qua Internet nó bắt đầu từ VB6

xong dò hoài trên Delphi mãi không ra tìm google nó toàn dẫn vào link sau


làm theo chỉ dẫn của nó diết mãi mà không thành công vì nguồn khi tải về sử dụng có dòng lỗi trên các bản Delphi khác nhau

xem nguồn của nó như lạc vào rừng già amozon xong tôi đọc nát web của nó của không làm được . .. nhưng bắt đầu hình dung ra nó


Khi Em ChatGPT ra đời thì tôi dò từ VB6 qua Delphi chỉ mất khoãng 5 ngày chi đó hình thành khung WebServer - Client

ban đầu vẫn giữ nguyên mọi cái từ VB6 qua Delphi là sử dụng ADODB + WinHttp của Ms khai báo COM

sau đó lại bỏ khai báo COM mà sử dụng ADODB do Delphi khai báo + unit WinHttp_TLB; nhưng khi sử dụng trong một số trường hợp nó vẫn lỗi

Tôi đoán đo COM + WinHttp tạo ra.... ghét tôi bỏ luôn

Viết mới sử dụng FireDAC + Indy thì loại bỏ các lỗi trên mà không biết do đâu mà ra

....

qua các bước trên Tôi mới thấy là ... code két quan trọng nhất là dò ra hướng sử dụng của nó xong từ đó kế thừa viết ra

hay tạm keo là khai hoang và mở đường ... nhiều code sau khi thành công nhìn lại sao thấy nó quá đơn giản vậy mà phải mất nhiều năm mới dò ra ???!!! :p_)()(-Nếu tôi công bố mã nguồn ai đó sẻ ồ lên tưởng gì tôi cũng viết được và viết tốt hơn ...

hãy chờ đấy và tự thân thử sức là biết ta là ai và có khả năng gì
 
Rảnh dò thử xem. trong Windows ẩn chứa nhiều điều thú vị không công bố hoặc nếu có chỉ hình thức

View attachment 304929



Có nhiều hàm cách đây 20 năm vẫn sử dụng tốt
À quên, Microsoft khuyến nghị nên sử dụng winhttp.dll thay vì dùng thư viện này, hèn gì code mẫu toàn là dùng winhttp.dll là phải. Trên GitHub có repo của một thanh niên cố gắng chuyển code mẫu C++ của Microsoft sang VBA, mà vướng ở chỗ nếu máy chủ không phản hồi lại thì bị treo vô hạn luôn.
1729519181722.png
 
À quên, Microsoft khuyến nghị nên sử dụng winhttp.dll thay vì dùng thư viện này, hèn gì code mẫu toàn là dùng winhttp.dll là phải. Trên GitHub có repo của một thanh niên cố gắng chuyển code mẫu C++ của Microsoft sang VBA, mà vướng ở chỗ nếu máy chủ không phản hồi lại thì bị treo vô hạn luôn.
View attachment 304939
Cách đây vài năm chi đó Tôi có thấy trên GitHub họ khai báo API cho WebServer + WebSocket luôn khi tải về sử dụng toàn lỗi xong bỏ

đang xem link sau cho Delphi thấy sử dụng Tốt ... ai đam mê dựa vào đó mà viết

Delphi WebSocket Server

Đối tượng dữ liệu Json khá hay cho ai quan tâm nó​

Đối tượng dữ liệu Json
 
WebSocket hai năm trước tôi đã từng dò rồi xong lỗi như mô tả các bài trước

Nay có nền tảng trên cở sở viết thành công WebServer đa luồng đa dịch vụ thuần Delphi rồi thì chuyển qua WebSocket cũng nhanh thôi

không quá khó chỉ điều chỉnh lại tiêu đề nhận và gửi Server và Client = tạm xong

Trò chơi này bỏ quên nay lôi ra cũng thú vị ...

mỗi ngày chơi chút trên Exe xem tình hình sao xong xuất hàm API vậy là từ Excel sẻ có WebSocket truy xuất dữ liệu từ xa thôi --=0--=0--=0


1729559971653.png
 
Rốt cuộc thì các quý vị đang tranh nghề kiến thức hay trao đổi với các người khác trên diển đàn về "Sử Dụng COM DLL..." như đề bài?
Tôi đang tính hỏi thêm chi tiết về "Sử dụng..." mà bi giờ thấy rắc rối quá, hết hứng. Hỏi vài câu biết đâu có người nhảy đổng lên cười vỡ bụng chỉ cái dốt của mình.

Gợi ý chủ thớt:
Đã muốn chia sẻ thì tập trung vào giao diện người dùng,. Đừng vì bị khích bác mà lôi hết kỹ thuật này kỹ năng khác ra. Chúng chỉ làm người ta sợ khong dám mó vào thôi.
 
Lần chỉnh sửa cuối:
Nhưng xét về lâu dài thì nó sẽ có lỗi, phải tìm cách bồi đắp và cuối cùng là cuốn chiếu :fish::fish::fish:
 
Nhưng xét về lâu dài thì nó sẽ có lỗi, phải tìm cách bồi đắp và cuối cùng là cuốn chiếu :fish::fish::fish:
Có vài điều 3 thực tế phơi bày ra trên 20 năm nay nó vẫn còn hoạt động tốt..

cho dù bạn có thừa nhận hay phủ nhận nó thì thực tế hiển nhiên phơi bày ra đó và cộng đồng lập trình quốc tế thừa nhận nó ...

còn bạn là gì trong cái cộng đồng đó ??!!!

1/ VB6 Ms đã bỏ trên 20 năm nay nó vẫn hoạt động tốt trên Windows11 32 và 64 bit

2/ VBA Ms cũng bỏ trên 20 năm nay không có hổ trợ gì mới cho VBE nó vẫn hoạt động tốt mặc dù VBA còn cùi hơn VB6 nhiều thứ

3/ Delphi7 là bản khá chuẩn cũng đã trên 20 năm nhưng giờ vẫn sử dụng viết hàm API tốt ... có điều không hổ trợ Unicode nếu muốn người dùng tự định nghĩa như trên VBA để sử dụng Unicode


Chốt lại:

Viết trên bất cứ ngôn ngữ nào không còn quan trọng nữa ..
mà quan trọng khả năng tự thân viết được gì cho mình sử dụng xong hãy nghĩ đến người khác sử dụng
 
Web KT

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

Back
Top Bottom