如何从 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 在这种情况下是一个糟糕的选择?
谢谢您的帮助
汉斯
这不一定是答案,但应该有帮助。 [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 参考:
打开解决方案资源管理器
打开属性窗口
在解决方案资源管理器中,右键单击 Form1.cs* 并选择 视图设计器。
打开工具箱
向 Form1 添加按钮
在解决方案资源管理器中,右键单击 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]。
其他资源:
[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