c# - 如何从 VBE 加载项中获取正确的活动应用程序?
问题描述
我正在为访问的 VBE 编写一个 COM 加载项,并且我想在单击命令栏按钮后从 C# 中执行一个 vba 函数。
所以我使用以下代码:
const string ApplicationObjectName = "Access.Application";
Microsoft.Office.Interop.Access.Application app = (Microsoft.Office.Interop.Access.Application)Marshal.GetActiveObject(ApplicationObjectName);
app.Run(functionName);
如果只有一个 ms-access-db 打开,这可以正常工作。但是如果有两个打开的数据库,'GetActiveObject' 会得到错误的应用程序,并在另一个数据库中调用该函数。不在命令栏按钮所属的那个中。
那么,如何获得正确的应用程序对象(= 单击按钮的对象)?
解决方案
目前我使用这里的片段(仅限德语):
https://dotnet-snippets.de/snippet/laufende-com-objekte-abfragen/526
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
namespace Rainbird.Tools.COMInterop
{
public class RunningObjectTable
{
private RunningObjectTable() { }
[DllImport("ole32.dll")]
private static extern int GetRunningObjectTable(uint reserved, out IRunningObjectTable pprot);
[DllImport("ole32.dll")]
private static extern int CreateBindCtx(uint reserved, out IBindCtx pctx);
public static object GetRunningCOMObjectByName(string objectDisplayName)
{
IRunningObjectTable runningObjectTable = null;
IEnumMoniker monikerList = null;
try
{
if (GetRunningObjectTable(0, out runningObjectTable) != 0 || runningObjectTable == null) return null;
runningObjectTable.EnumRunning(out monikerList);
monikerList.Reset();
IMoniker[] monikerContainer = new IMoniker[1];
IntPtr pointerFetchedMonikers = IntPtr.Zero;
while (monikerList.Next(1, monikerContainer, pointerFetchedMonikers) == 0)
{
IBindCtx bindInfo;
string displayName;
CreateBindCtx(0, out bindInfo);
monikerContainer[0].GetDisplayName(bindInfo, null, out displayName);
Marshal.ReleaseComObject(bindInfo);
if (displayName.IndexOf(objectDisplayName) != -1)
{
object comInstance;
runningObjectTable.GetObject(monikerContainer[0], out comInstance);
return comInstance;
}
}
}
catch
{
return null;
}
finally
{
if (runningObjectTable != null) Marshal.ReleaseComObject(runningObjectTable);
if (monikerList != null) Marshal.ReleaseComObject(monikerList);
}
return null;
}
public static IList<string> GetRunningCOMObjectNames()
{
IList<string> result = new List<string>();
IRunningObjectTable runningObjectTable = null;
IEnumMoniker monikerList = null;
try
{
if (GetRunningObjectTable(0, out runningObjectTable) != 0 || runningObjectTable == null) return null;
runningObjectTable.EnumRunning(out monikerList);
monikerList.Reset();
IMoniker[] monikerContainer = new IMoniker[1];
IntPtr pointerFetchedMonikers = IntPtr.Zero;
while (monikerList.Next(1, monikerContainer, pointerFetchedMonikers) == 0)
{
IBindCtx bindInfo;
string displayName;
CreateBindCtx(0, out bindInfo);
monikerContainer[0].GetDisplayName(bindInfo, null, out displayName);
Marshal.ReleaseComObject(bindInfo);
result.Add(displayName);
}
return result;
}
catch
{
return null;
}
finally
{
if (runningObjectTable != null) Marshal.ReleaseComObject(runningObjectTable);
if (monikerList != null) Marshal.ReleaseComObject(monikerList);
}
}
}
}
使用此代码(仅适用于 ms-access):
var activeProject = m_VBE.ActiveVBProject;
Microsoft.Office.Interop.Access.Application app = (Microsoft.Office.Interop.Access.Application)RunningObjectTable.GetRunningCOMObjectByName(activeProject.FileName);
app.Run(functionName);
但必须有更好的解决方案来解决这个问题。
此处也讨论了类似的问题:如何获取 *actual* 主机应用程序实例?在这里:如何使用 Marshal.getActiveObject() 来获取一个正在运行的进程的 2 个实例,该进程有两个打开的进程