C# 和 VBA 之间的通信

Posted

技术标签:

【中文标题】C# 和 VBA 之间的通信【英文标题】:Communicating between C# and VBA 【发布时间】:2012-11-17 23:54:42 【问题描述】:

应我老板的要求,我创建了一组脚本,用于定期监控某些设备和进程的状态。该信息随后使用相对复杂的 VBA 模块进行处理,该模块收集所有信息、应用公式、设置范围和生成图表等。

但是有两个问题: 我是一个业余程序员,所以我的 VBA 程序效率很低。这对我来说不是什么大问题(我只是在它运行的时候起床喝杯咖啡),但对于其他用户来说,这可能会很麻烦,因为那时不知道为什么要花这么长时间。我想要进度的图形表示。 应用程序的配置是通过文本文件完成的。我想提供一个不错的 GUI 来设置配置。

为了实现这一点,我正在寻找一种方法让我的 C# WinForms 应用程序与我的 VBA 应用程序进行通信。我知道如何从我的 C# 应用程序运行 VBA 例程,但我不知道如何让它们实时通信。

以下是我特别想要实现的两件事:

我已经有一个日志文件保存在 VBA 例程的末尾。 但是,我想将日志/调试消息发送到 我的 C# 应用程序实时(不只是在结束时 应用程序),因此消息可以显示在我的 GUI 应用程序中,因为它们 由 VBA 应用程序生成。 我还希望 VBA 应用程序发送信息 关于我的 GUI 应用程序的实时进度,所以我可以创建一个图形 我的 GUI 应用中的进度条

我已经考虑过通过标准输出进行通信。我知道如何使用 C#/.Net 从标准输出读取数据,但我不确定如何使用 VBA 写入 StdOut 流。

我敢肯定很多人会指出我想要实现的目标是愚蠢和过时的(或完全没有必要),但作为一个业余程序员,这对我来说似乎是一个非常具有挑战性和有趣的项目,可以教我有一两件事。

【问题讨论】:

我建议您花时间改进 VBA 应用程序。此外,没有理由你不能做进度条和登录 VBA。如果不需要,为什么还要引入额外的复杂性? 嗯,首先,我认为我无法创建更快或更高效的应用程序,因为我已经使用了我所有的 VBA 知识。正如我所说,我是一个绝对的业余爱好者。第二:我希望“Excel 层”在工作簿完全生成之前是不可见的。第三:我在 VBA 中创建表单的经验也绝对为零,而我在 C# 中确实有一些经验。第四:正如我所说,这似乎是一件非常非常有趣/有趣的事情。 我不明白你是怎么走到这样一个境地的,你学会了 C# 中的表单编程,但是 VBA 中的“其他”编程?让所有这些在一个平台或另一个平台上运行不是更有意义吗?我建议让它全部在 C# 中运行,因为它是较新的技术。但是,如果它只是需要构建一个完整的 VBA 项目的表单,那么 VBA 中的表单确实非常简单。当您遇到困难时,请尝试提问。这比尝试在 VBA 之间引发事件并将它们放入 .Net 中要简单得多 @ElectricLlama 我用 VBA 所做的几乎所有事情最终都链接到一个会定期执行的脚本。我从来没有做过任何需要大量用户交互的事情。也就是说,正如我在最初的问题中指出的那样,我完全理解您对我的问题的担忧。 你是说VBA真的只是另一个脚本的外壳吗?如果是这种情况,您应该从 C# 调用脚本。或者你为什么不改变你的 C# 前端以定期轮询日志文件并以这种方式显示状态栏。 【参考方案1】:

创建一个 C# COM 可见的类非常简单,而且(正如您在 cmets 中所说)这很有趣。这是一个小样本。

在一个新的 C# 库项目中,添加:

using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace CSharpCom

    [ComVisible(true)]
    [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
    //The 3 GUIDs in this file need to be unique for your COM object.
    //Generate new ones using Tools->Create GUID in VS2010
    [Guid("18C66A75-5CA4-4555-991D-7115DB857F7A")] 
    public interface ICSharpCom
    
        string Format(string FormatString, [Optional]object arg0, [Optional]object arg1, [Optional]object arg2, [Optional]object arg3);
        void ShowMessageBox(string SomeText);
    

    //TODO: Change me!
    [Guid("5D338F6F-A028-41CA-9054-18752D14B1BB")] //Change this 
    [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
    interface ICSharpComEvents
    
        //Add event definitions here. Add [DispId(1..n)] attributes
        //before each event declaration.
    

    [ComVisible(true)]
    [ClassInterface(ClassInterfaceType.None)]
    [ComSourceInterfaces(typeof(ICSharpComEvents))]
    //TODO: Change me!
    [Guid("C17C5EAD-AA14-464E-AD32-E9521AC17134")]
    public sealed class CSharpCom : ICSharpCom
    
        public string Format(string FormatString, [Optional]object arg0, [Optional]object arg1, [Optional]object arg2, [Optional]object arg3)
        
            return string.Format(FormatString, arg0, arg1, arg2, arg3);   
        

        public void ShowMessageBox(string SomeText)
        
            MessageBox.Show(SomeText);
        
    

您将需要进入您的项目属性,进入“签名”选项卡,选中复选框以对您的程序集进行签名,然后创建一个新的“强名称密钥文件”。这将有助于防止您注册的 DLL 出现版本控制问题。

编译它,并在 Visual Studio 命令提示符下使用 regasm 注册 DLL。您将使用 32 位或 64 位 regasm,具体取决于您使用的 Office 版本...您会在 C:\windows\Microsoft.NET\FrameworkC:\windows\Microsoft.NET\Framework64(分别为 32 位和 64 位)中找到 RegAsm:

C:\windows\Microsoft.NET\Framework\v4.0.30319\RegAsm.exe /codebase /tlb CSharpCom.dll

这会将您的 DLL 注册到 Windows,因此现在在 VBA 编辑器中,您应该能够转到 Tools->References 并找到您的 COM 命名空间“CSharpCom”。选中该框,现在您应该可以在 VBA 中创建 COM 对象了:

Sub TestCom()
    Dim c As CSharpCom.CSharpCom
    Set c = New CSharpCom.CSharpCom
    c.ShowMessageBox c.Format("0 1!", "Hello", "World")
End Sub

您可以将表单添加到您的 COM 对象,并在 VBA 调用特定方法时打开它们;您应该能够使用它来创建带有进度条的表单。不过,要考虑的一件事是 VBA 是单线程的。这意味着在您的代码运行时,其他所有内容都会被冻结,因此事情可能会变得有些棘手。

在我的脑海中,你可以在你的 COM 可见类中创建 3 个方法:一个打开表单,一个更新进度(在你的 VBA 调用 update() 方法后立即调用 VBA 的 DoEvents() 方法您的 COM 对象,以允许 Office 处理屏幕更新),并关闭它。您应该在表单上调用 Dispose();这可以在“关闭”方法中完成,但我认为如果你的 VBA 崩溃,它可能会导致内存泄漏/问题,并且你的关闭方法永远不会被调用——只是需要考虑的其他事情。

【讨论】:

在我看来,他试图在 C# 程序中捕获 VB 中引发的事件,而不是相反。 是的,这正是它的作用...... COM 可见的 C# 对象是在 C# 中编写并在 VBA 中实例化(或任何地方,真的——这就是他们的目的)。反过来就不行了。 这确实符合我的要求,尽管它是一种非常先进的技术。我在修补这些不同的解决方案时获得了很多乐趣,但最终我(可以预见地)得出结论,对我的“应用程序”进行彻底的重新思考(如果这样的名称确实可以应用于我在这里看到的内容) ) 是必须的。感谢您的意见,我确实在这里学到了很多东西! 我需要这样的东西已经有一段时间了,因为 Access 在长时间运行时会冻结。很高兴向用户展示某种进展。您可以在 Access 中显示进度,但我发现这样做有点笨拙,而且仍有一些地方您无法对冻结做任何事情。我借此机会创造了一些东西,很高兴与您分享:Google Docs link。阅读包含的 README.txt 文件以了解详细信息。 另外 - 要考虑的另一件事是,使用类似的东西,.dll 需要在您的 VBA 应用程序使用的每台机器上注册。可以在不注册的情况下使用 .NET DLL,但这需要在 VBA 中进行一些额外的编码。有关更多详细信息,请参阅 this:这种技术是我将如何分发进度条的方式。【参考方案2】:

您可以在 VB6 项目中使用 .NET DLL,并创建您在程序集中定义的任何对象的实例。这是通过创建类型库文件 (.tlb) 为 COM 互操作(在 VB6 中使用)注册 .NET 程序集 (.dll) 来完成的。 .tlb 文件包含额外信息,以便您的 VB6 项目可以使用您的 .dll。请注意,您的 VB6 项目不会引用 .dll,而是引用相应的 .tlb 文件。您可以使用 Regasm.exe 实用程序来生成和注册类型库并注册托管程序集的位置。然后只需创建一个 .NET 对象的实例,无论它是 WinForm 还是其他一些很酷的类。 =)。

Dim MyDotNetObject As MyDotNetClass

Set MyDotNetObject = New MyDotNetClass
MyDotNetObject.SomeMethod(value1, value2)

''end of execution
Set MyDotNetObject = Nothing

见:http://support.microsoft.com/default.aspx?scid=kb;en-us;817248

【讨论】:

在我看来,他试图在 C# 程序中捕获 VB 中引发的事件,而不是相反。【参考方案3】:

我相信此链接中的代码大致符合您的要求:

C# 代码创建一个 COM 对象(在示例中是 IE,但根据您的描述,您已经设法创建了 VBA 例程的实例)

它将 .Net 事件处理程序(使用 +=)附加到 COM 对象引发的事件(当标题更改时)

它定义了示例事件处理代码

http://msdn.microsoft.com/en-us/library/66ahbe6y.aspx

我不自称理解这段代码,我的目标是展示这个解决方案的复杂性!你真的应该在一个解决方案中构建它。一个完整的 VBA 解决方案将更快地开发(您只需要学习很容易的 VBA 形式),但技术较旧。一个完整的 C# 解决方案的开发速度会较慢,但随后您将开始学习 C#。

性能实际上是一个问题。如果它现在效率低下,当您有 5 倍的记录要处理时会发生什么?您应该获得那么多记录之前解决性能问题。

【讨论】:

【参考方案4】:

此问题的其他答案涉及调用外部方法的 VBA 宏。但是有一个更简单的方法。您的 GUI 应用可以附加到工作簿SheetSelectionChange 事件或工作表Change 事件,当任何值更改时都会触发。

因为您可能不想减慢所有速度,我建议您添加一个额外的工作表,添加命名范围来描述每个值的含义,然后挂钩工作表更改事件以在这些值更改时进行拦截并更新您的 GUI .

【讨论】:

以上是关于C# 和 VBA 之间的通信的主要内容,如果未能解决你的问题,请参考以下文章

Metro 应用程序中 C++ 和 C# 之间的通信

如何使用标准输入和标准输出在 C# 和 ruby​​ 进程之间进行通信

C#和Python之间的进程间通信

C#中两个插件之间的通信

C# 服务器和 C++ 客户端之间的命名管道通信

python服务器和C#客户端之间的通信(Unity)