从 C# 客户端调用 COM 服务器

问题描述 投票:0回答:1

如何从 C# 程序中调用进程外 COM 服务器的方法? COM 服务器是在 C++/MFC 程序中实现的,当调用 C# 代码时该程序已经在运行。抱歉,如果这是一个非常基本的问题,也许我没有使用最好的搜索关键字。

我可以在 PowerShell 中执行此操作:

$obj = New-Object -ComObject MyComServer
$res = ""
$obj.RunScript('script_commands', [ref]$res);
[System.Runtime.InteropServices.Marshal]::ReleaseComobject($obj)

其中 RunScript 在 *.idl 文件中声明为

HRESULT RunScript(BSTR bsScript, [out] BSTR* pbsResult);

现在我想在 C# 中做同样的事情。我试过了

var obj_type = Type.GetTypeFromProgID("MyComServer");
var obj = Activator.CreateInstance(obj_type);
var res = obj_type.InvokeMember("RunScript", BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static, null, obj, new object[] {"script_commands"});

obj_type 被赋值后,它的内容是一个 System.__ComObject,这可能是预料之中的。 CreateInstance 调用等待了很长时间,最后抛出一个异常,并显示文本“由于以下错误,检索具有 CLSID {9BBEF959-EA98-11D3-8BC3-00A0C9CA04BF} 的组件的 COM 类工厂失败:80080005 服务器执行失败(异常)来自 HRESULT:0x80080005 (CO_E_SERVER_EXEC_FAILURE))。”此消息中的 GUID 与 *.idl 文件中指定的 GUID 匹配。

我假设 CreateInstance 尝试启动包含 COM 服务器的程序的第二个实例。这会失败,因为程序阻止同时执行两个实例。看起来 Activator.CreateInstance 在这种情况下是一个糟糕的选择?

谢谢您的帮助

汉斯

c# com
1个回答
0
投票

这不一定是答案,但应该有帮助。 [Microsoft.Office.Interop.Excel][1] 和 [Microsoft.Office.Interop.Word][2] 都使用 [组件对象模型 (COM)][3]。由于您尚未为 C++ 应用程序提供任何代码,因此我将展示如何与使用 COM 的 Microsoft Excel 进行交互。


[解决PowerShell模块程序集依赖冲突][4]:

一般来说,PowerShell 5.1及以下版本在.NET Framework上运行,而 PowerShell 6 及更高版本在 .NET Core 上运行。

如果安装了 Microsoft Excel 和 Windows PowerShell 版本 5.1,则可以通过执行以下操作与现有(即:打开)的 Excel 实例进行交互:

打开 Microsoft Excel 并创建一个空白工作簿

打开 Windows PowerShell(版本 5.1)

注意:以下操作会将 Excel 工作表从 Sheet1 重命名为 Test,并将字符串“Hello World”放入单元格 A1中。

获取 PowerShell 版本:

Get-Host | Select Version;

检查 Excel 是否正在运行:

Get-Process | Where-Object {$_.ProcessName -eq "EXCEL" };

获取正在运行的 Excel 实例:

$objExcel = [System.Runtime.InteropServices.Marshal]::GetActiveObject("Excel.Application")

获取 Excel 属性和方法:

$objExcel | Get-Member;

激活工作簿:

$objWorkbook = $objExcel.ActiveWorkbook

获取工作表

$objWorksheet = $objWorkbook.Sheets(1)

激活工作表

$objWorksheet.Activate()

更改工作表名称

$objWorksheet.Name = "Test"

设置单元格 A1 的值:

$objWorksheet.Cells(1,1) = "Hello World"

释放COM:

[System.Runtime.InteropServices.Marshal]::ReleaseComObject($objExcel)

下面展示了如何在 C# 中与现有(即:打开)的 Excel 实例进行交互。

创建一个新的 Windows 窗体应用程序 (.NET Framework)(名称:ExcelInteropExistingInstanceTest)。提示时, 选择 .NET Framework 4.8

添加 COM 参考:

  • 在 Visual Studio 菜单中,单击 项目
  • 选择添加参考...
  • 点击左侧的COM
  • 检查 Microsoft Excel xx.x 对象库(例如:Microsoft Excel 16.0 对象库)
  • 点击确定

打开解决方案资源管理器

  • 在 Visual Studio 菜单中,单击 查看
  • 选择解决方案资源管理器

打开属性窗口

  • 在 Visual Studio 菜单中,单击 查看
  • 选择属性窗口

在解决方案资源管理器中,右键单击 Form1.cs* 并选择 视图设计器

打开工具箱

  • 在 Visual Studio 菜单中,单击 查看
  • 选择工具箱

向 Form1 添加按钮

  • 在工具箱中,展开所有 Windows 窗体
  • Button 拖到 Form1
  • 在属性窗口中,设置按钮(名称)btnUpdate和文本:Update
  • 在表单设计器中,双击按钮以添加 Click 事件处理程序。

在解决方案资源管理器中,右键单击 Form1.cs 并选择 查看代码

Form1.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Excel = Microsoft.Office.Interop.Excel;
using System.Runtime.InteropServices;
using System.Diagnostics;

namespace ExcelInteropExistingInstanceTest
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void UpdateExcel(string valueA1, string fileMonikerName)
        {
            Excel.Application excelApp = null;
            Excel.Workbook desiredWb = null;
            Excel.Worksheet ws = null;

            try
            {
                //registry keys
                //HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Excel.Application\CLSID
                //64-bit: HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\{00024500-0000-0000-C000-000000000046}
                //32-bit: HKEY_LOCAL_MACHINE\SOFTWARE\Classes\WOW6432Node\CLSID\{00024500-0000-0000-C000-000000000046}

                //get running processes that have the name "EXCEL"
                Process[] processes = Process.GetProcessesByName("EXCEL");

                if (processes == null || processes.Length == 0)
                {
                    Debug.WriteLine("Info: No Excel processes found.");
                    return;
                }

                foreach (Process p in processes)
                {
                    Debug.WriteLine($"{DateTime.Now.ToString("HH:mm:ss.fff")} - p.Id: {p.Id} p.MainWindowHandle: {p.MainWindowHandle.ToString("X8")}");

                    if (p.MainWindowHandle == IntPtr.Zero)
                    {
                        //an exception in this method may cause Excel to remain running in
                        //the background even after both this program and Excel have been closed
                        //if the Excel process is valid p.MainWindowHandle <> IntPtr.Zero
                        //however, if the Excel process has been orphaned, then p.MainWindowHandle = IntPtr.Zero
                        //and the 'Microsoft Excel' process can be observed in Task Manager under 'Background processes'
                        //kill orphaned Excel process
                        p.Kill();

                        Debug.WriteLine("Info: An orphaned Excel process was terminated, but no active Excel instances were found. Open desired Excel workbook and try again.");
                        return;
                    }
                }

                //get existing Excel instance
                excelApp = (Excel.Application)Marshal.GetActiveObject("Excel.Application");

                //prevent promptying to save
                excelApp.DisplayAlerts = false;

                //ensure Excel is visible
                excelApp.Visible = true;

                //get desired workbook
                foreach (Excel.Workbook workbook in excelApp.Workbooks)
                {
                    Debug.WriteLine($"               wb FullName: '{workbook.FullName}' Name: '{workbook.Name}'");

                    if (fileMonikerName == workbook.FullName)
                    {
                        //set value
                        desiredWb = workbook;

                        foreach (Excel.Worksheet worksheet in desiredWb.Sheets)
                        {
                            Debug.WriteLine($"               ws Name: '{worksheet.Name}'");

                            //ToDo: select desired worksheet
                            //set value
                            ws = worksheet;

                            //activate worksheet
                            ws.Activate();
                        }
                    }
                }

                if (desiredWb == null)
                {
                    throw new Exception($"Error: Desired workbook not found ('{fileMonikerName}').");
                }

                if (ws == null)
                {
                    throw new Exception("Error: Worksheet not found.");
                }

                //change worksheet name 
                ws.Name = "Test";

                //set value - for A1 also known as 1,1
                ws.Cells[1, 1] = valueA1;

                //save
                desiredWb.SaveAs(fileMonikerName, System.Reflection.Missing.Value, System.Reflection.Missing.Value, System.Reflection.Missing.Value, System.Reflection.Missing.Value, System.Reflection.Missing.Value, Excel.XlSaveAsAccessMode.xlNoChange, System.Reflection.Missing.Value, System.Reflection.Missing.Value, System.Reflection.Missing.Value, System.Reflection.Missing.Value, System.Reflection.Missing.Value);
            }
            finally
            {
                //release COM objects

                if (ws != null)
                {
                    Marshal.ReleaseComObject(ws);
                }
                    
                if (desiredWb != null)
                {
                    Marshal.ReleaseComObject(desiredWb);
                }
                    
                if (excelApp != null)
                {
                    Marshal.ReleaseComObject(excelApp);
                }

                //call garbage collector
                GC.Collect();
            }
        }

        private void btnUpdate_Click(object sender, EventArgs e)
        {
            //a new, unsaved, Excel workbook has a moniker name of 'Book1', 'Book2, 'Book3', etc...
            UpdateExcel("Hello World", "Book1");

            /*
            //for an Excel workbook that has been saved, the file moniker name is the fully-qualified filename
            //ex: C:\Temp\Workbook1.xlsx

            using (OpenFileDialog ofd = new OpenFileDialog())
            {
                ofd.Filter = "Excel File (*.xlsx)|*.xlsx";

                if (ofd.ShowDialog() == DialogResult.OK)
                {
                    UpdateExcel("Hello World", ofd.FileName);
                }
            }
            */
        }
    }
}

注意:对于 VB.NET,请参阅此[帖子][5]。


其他资源

  • [Powershell, Excel 中移动光标][6]


  [1]: https://learn.microsoft.com/en-us/dotnet/api/microsoft.office.interop.excel?view=excel-pia
  [2]: https://learn.microsoft.com/en-us/dotnet/api/microsoft.office.interop.word?view=word-pia
  [3]: https://learn.microsoft.com/en-us/windows/win32/com/component-object-model--com--portal
  [4]: https://learn.microsoft.com/en-us/powershell/scripting/dev-cross-plat/resolving-dependency-conflicts?view=powershell-7.4#powershell-and-net
  [5]: https://stackoverflow.com/a/78208187/10024425
  [6]: https://superuser.com/questions/1669324/powershell-move-cursor-in-excel
© www.soinside.com 2019 - 2024. All rights reserved.