Mat*_*don 22 c# com reflection ms-office vbe
我正在为VBE编写COM加载项,其中一个核心功能是在单击命令栏按钮时执行现有的VBA代码.
代码是用户在标准(.bas)模块中编写的单元测试代码,其模块如下所示:
Run Code Online (Sandbox Code Playgroud)Option Explicit Option Private Module '@TestModule Private Assert As New Rubberduck.AssertClass '@TestMethod Public Sub TestMethod1() 'TODO: Rename test On Error GoTo TestFail 'Arrange: 'Act: 'Assert: Assert.Inconclusive TestExit: Exit Sub TestFail: Assert.Fail "Test raised an error: #" & Err.Number & " - " & Err.Description End Sub
所以我有这个代码获取主机Application对象的当前实例:
protected HostApplicationBase(string applicationName)
{
Application = (TApplication)Marshal.GetActiveObject(applicationName + ".Application");
}
Run Code Online (Sandbox Code Playgroud)
这是ExcelApp班级:
public class ExcelApp : HostApplicationBase<Microsoft.Office.Interop.Excel.Application>
{
public ExcelApp() : base("Excel") { }
public override void Run(QualifiedMemberName qualifiedMemberName)
{
var call = GenerateMethodCall(qualifiedMemberName);
Application.Run(call);
}
protected virtual string GenerateMethodCall(QualifiedMemberName qualifiedMemberName)
{
return qualifiedMemberName.ToString();
}
}
Run Code Online (Sandbox Code Playgroud)
奇迹般有效.我有类似的代码WordApp,PowerPointApp和AccessApp也.
问题是Outlook的Application对象没有公开Run方法,所以我很好,卡住了.
如何从VB的COM加载项执行VBA代码,而不是Application.Run?
这个答案链接到MSDN上看起来很有前途的博客帖子,所以我尝试了这个:
public class OutlookApp : HostApplicationBase<Microsoft.Office.Interop.Outlook.Application>
{
public OutlookApp() : base("Outlook") { }
public override void Run(QualifiedMemberName qualifiedMemberName)
{
var app = Application.GetType();
app.InvokeMember(qualifiedMemberName.MemberName, BindingFlags.InvokeMethod, null, Application, null);
}
}
Run Code Online (Sandbox Code Playgroud)
但是,我得到的最好的是一个COMException说"未知名称",OUTLOOK.EXE进程退出代码-1073741819(0xc0000005)'访问违规' - 它也与Excel一样好用.
如果我放入TestMethod1内部,这个VBA代码可以工作ThisOutlookSession:
Outlook.Application.TestMethod1
Run Code Online (Sandbox Code Playgroud)
请注意,TestMethod1它未列为Outlook.ApplicationVBA IntelliSense中的成员..但不知何故,它恰好起作用.
问题是,我如何使用Reflection进行此工作?
更新3:
我在MSDN论坛上发现了这篇文章:从VSTO调用Outlook VBA sub.
显然它使用VSTO,我尝试将其转换为VBE AddIn,但在使用带有Register Class问题的x64 Windows时遇到了问题:
COMException(0x80040154):由于以下错误,检索具有CLSID {55F88893-7708-11D1-ACEB-006008961DA5}的组件的COM类工厂失败:80040154类未注册
无论如何,这是那些回答谁认为他得到它的工作:
MSDN论坛帖子的开始
我找到了一个方法!什么可以从VSTO和VBA触发?剪贴板!!
所以我使用剪贴板将消息从一个环境传递到另一个环境.这里有一些代码可以解释我的诀窍:
VSTO:
'p_Procedure is the procedure name to call in VBA within Outlook
'mObj_ou_UserProperty is to create a custom property to pass an argument to the VBA procedure
Private Sub p_Call_VBA(p_Procedure As String)
Dim mObj_of_CommandBars As Microsoft.Office.Core.CommandBars, mObj_ou_Explorer As Outlook.Explorer, mObj_ou_MailItem As Outlook.MailItem, mObj_ou_UserProperty As Outlook.UserProperty
mObj_ou_Explorer = Globals.Menu_AddIn.Application.ActiveExplorer
'I want this to run only when one item is selected
If mObj_ou_Explorer.Selection.Count = 1 Then
mObj_ou_MailItem = mObj_ou_Explorer.Selection(1)
mObj_ou_UserProperty = mObj_ou_MailItem.UserProperties.Add("COM AddIn-Azimuth", Outlook.OlUserPropertyType.olText)
mObj_ou_UserProperty.Value = p_Procedure
mObj_of_CommandBars = mObj_ou_Explorer.CommandBars
'Call the clipboard event Copy
mObj_of_CommandBars.ExecuteMso("Copy")
End If
End Sub
Run Code Online (Sandbox Code Playgroud)
VBA:
为Explorer事件创建一个类并捕获此事件:
Public WithEvents mpubObj_Explorer As Explorer
'Trap the clipboard event Copy
Private Sub mpubObj_Explorer_BeforeItemCopy(Cancel As Boolean)
Dim mObj_MI As MailItem, mObj_UserProperty As UserProperty
'Make sure only one item is selected and of type Mail
If mpubObj_Explorer.Selection.Count = 1 And mpubObj_Explorer.Selection(1).Class = olMail Then
Set mObj_MI = mpubObj_Explorer.Selection(1)
'Check to see if the custom property is present in the mail selected
For Each mObj_UserProperty In mObj_MI.UserProperties
If mObj_UserProperty.Name = "COM AddIn-Azimuth" Then
Select Case mObj_UserProperty.Value
Case "Example_Add_project"
'...
Case "Example_Modify_planning"
'...
End Select
'Remove the custom property, to keep things clean
mObj_UserProperty.Delete
'Cancel the Copy event. It makes the call transparent to the user
Cancel = True
Exit For
End If
Next
Set mObj_UserProperty = Nothing
Set mObj_MI = Nothing
End If
End Sub
Run Code Online (Sandbox Code Playgroud)
MSDN论坛帖子结束
因此,此代码的作者将一个UserProperty添加到邮件项目并以这种方式传递函数名称.再次,这将需要Outlook中的一些锅炉板代码和至少1个邮件项目.
更新3a:
我没有注册的80040154类是因为尽管我将代码从VSTO VB.Net转换为VBE C#而我的目标是x86平台,我实例化了项目,例如:
Microsoft.Office.Core.CommandBars mObj_of_CommandBars = new Microsoft.Office.Core.CommandBars();
Run Code Online (Sandbox Code Playgroud)
在浪费了几个小时后,我想出了这个代码!
VBE C#代码(从我的回答中得到一个VBE AddIn答案):
namespace VBEAddin
{
[ComVisible(true), Guid("3599862B-FF92-42DF-BB55-DBD37CC13565"), ProgId("VBEAddIn.Connect")]
public class Connect : IDTExtensibility2
{
private VBE _VBE;
private AddIn _AddIn;
#region "IDTExtensibility2 Members"
public void OnConnection(object application, ext_ConnectMode connectMode, object addInInst, ref Array custom)
{
try
{
_VBE = (VBE)application;
_AddIn = (AddIn)addInInst;
switch (connectMode)
{
case Extensibility.ext_ConnectMode.ext_cm_Startup:
break;
case Extensibility.ext_ConnectMode.ext_cm_AfterStartup:
InitializeAddIn();
break;
}
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
private void onReferenceItemAdded(Reference reference)
{
//TODO: Map types found in assembly using reference.
}
private void onReferenceItemRemoved(Reference reference)
{
//TODO: Remove types found in assembly using reference.
}
public void OnDisconnection(ext_DisconnectMode disconnectMode, ref Array custom)
{
}
public void OnAddInsUpdate(ref Array custom)
{
}
public void OnStartupComplete(ref Array custom)
{
InitializeAddIn();
}
private void InitializeAddIn()
{
MessageBox.Show(_AddIn.ProgId + " loaded in VBA editor version " + _VBE.Version);
Form1 frm = new Form1();
frm.Show(); //<-- HERE I AM INSTANTIATING A FORM WHEN THE ADDIN LOADS FROM THE VBE IDE!
}
public void OnBeginShutdown(ref Array custom)
{
}
#endregion
}
}
Run Code Online (Sandbox Code Playgroud)
我实例化并从VBE IDE InitializeAddIn()方法加载的Form1代码:
namespace VBEAddIn
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
Call_VBA("Test");
}
private void Call_VBA(string p_Procedure)
{
var olApp = new Microsoft.Office.Interop.Outlook.Application();
Microsoft.Office.Core.CommandBars mObj_of_CommandBars;
Microsoft.Office.Core.CommandBars mObj_of_CommandBars = new Microsoft.Office.Core.CommandBars();
Microsoft.Office.Interop.Outlook.Explorer mObj_ou_Explorer;
Microsoft.Office.Interop.Outlook.MailItem mObj_ou_MailItem;
Microsoft.Office.Interop.Outlook.UserProperty mObj_ou_UserProperty;
//mObj_ou_Explorer = Globals.Menu_AddIn.Application.ActiveExplorer
mObj_ou_Explorer = olApp.ActiveExplorer();
//I want this to run only when one item is selected
if (mObj_ou_Explorer.Selection.Count == 1)
{
mObj_ou_MailItem = mObj_ou_Explorer.Selection[1];
mObj_ou_UserProperty = mObj_ou_MailItem.UserProperties.Add("JT", Microsoft.Office.Interop.Outlook.OlUserPropertyType.olText);
mObj_ou_UserProperty.Value = p_Procedure;
mObj_of_CommandBars = mObj_ou_Explorer.CommandBars;
//Call the clipboard event Copy
mObj_of_CommandBars.ExecuteMso("Copy");
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
ThisOutlookSession代码:
Public WithEvents mpubObj_Explorer As Explorer
'Trap the clipboard event Copy
Private Sub mpubObj_Explorer_BeforeItemCopy(Cancel As Boolean)
Dim mObj_MI As MailItem, mObj_UserProperty As UserProperty
MsgBox ("The mpubObj_Explorer_BeforeItemCopy event worked!")
'Make sure only one item is selected and of type Mail
If mpubObj_Explorer.Selection.Count = 1 And mpubObj_Explorer.Selection(1).Class = olMail Then
Set mObj_MI = mpubObj_Explorer.Selection(1)
'Check to see if the custom property is present in the mail selected
For Each mObj_UserProperty In mObj_MI.UserProperties
If mObj_UserProperty.Name = "JT" Then
'Will the magic happen?!
Outlook.Application.Test
'Remove the custom property, to keep things clean
mObj_UserProperty.Delete
'Cancel the Copy event. It makes the call transparent to the user
Cancel = True
Exit For
End If
Next
Set mObj_UserProperty = Nothing
Set mObj_MI = Nothing
End If
End Sub
Run Code Online (Sandbox Code Playgroud)
Outlook VBA方法:
Public Sub Test()
MsgBox ("Will this be called?")
End Sub
Run Code Online (Sandbox Code Playgroud)
很遗憾,我很遗憾地通知你,我的努力没有成功.也许它确实可以在VSTO上工作(我还没有尝试过),但在尝试像狗一样取骨之后,我现在愿意放弃!
从来没有作为一种安慰,您可以在此答案的修订历史中找到一个疯狂的想法(它显示了一种模拟Office对象模型的方法)来运行私有参数的Office VBA单元测试.
我将在线下与您讨论为RubberDuck GitHub项目做出贡献,我编写的代码与Prodiance的工作簿关系图表相同,然后Microsoft将其购买并将其产品包含在Office Audit和Version Control Server中.
您可能希望在完全解除它之前检查此代码,我甚至无法使mpubObj_Explorer_BeforeItemCopy事件起作用,因此如果您可以在Outlook中正常工作,您可能会更好.(我在家里使用Outlook 2013,所以2010年可能会有所不同).
ps你会想到在逆时针方向跳到一条腿后,顺时针揉搓我的头部,然后像这篇KB文章中的解决方法2那样点击我的手指,我会把它钉在它上面......我只是丢了更多的头发!
更新2:
在你的内部你Outlook.Application.TestMethod1不能只使用VB经典的CallByName方法,所以你不需要反射?在调用包含CallByName的方法之前,您需要设置字符串属性"Sub/FunctionNameToCall"以指定要调用的子/函数.
不幸的是,用户需要在其中一个模块中插入一些锅炉板代码.
更新1:
这听起来真的很狡猾,但是由于Outlook的对象模型完全限制了它的Run方法,你可以诉诸... SendKeys (是的,我知道,但它会起作用).
不幸的是,oApp.GetType().InvokeMember("Run"...)下面描述的方法适用于除Outlook之外的所有 Office应用程序 - 基于此知识库文章中的"属性"部分:https://support.microsoft.com/en-us/kb/306683,抱歉我直到现在才知道并且发现它非常令人沮丧的尝试和MSDN文章误导,最终微软锁定了它:
**请注意,SendKeys支持且使用的唯一其他已知方式ThisOutlookSession不是:https:
//groups.google.com/forum/?hl = en#!topic/microsoft.public.outlook.program_vba / cQ8gF9ssN3g - 即使Sue isn微软PSS 她会问,并发现它不受支持.
旧...以下方法适用于Office应用程序,Outlook除外
问题是Outlook的Application对象没有公开Run方法,所以我很好,卡住了.这个答案链接到MSDN上看起来很有前途的博客帖子,所以我尝试了这个...但OUTLOOK.EXE进程退出代码-1073741819(0xc0000005)'访问冲突'
问题是,我如何使用Reflection进行此工作?
1)以下是我使用的代码,适用于Excel(应该适用于Outlook),使用.Net参考: Microsoft.Office.Interop.Excel v14(不是ActiveX COM参考):
using System;
using Microsoft.Office.Interop.Excel;
namespace ConsoleApplication5
{
class Program
{
static void Main(string[] args)
{
RunVBATest();
}
public static void RunVBATest()
{
Application oExcel = new Application();
oExcel.Visible = true;
Workbooks oBooks = oExcel.Workbooks;
_Workbook oBook = null;
oBook = oBooks.Open("C:\\temp\\Book1.xlsm");
// Run the macro.
RunMacro(oExcel, new Object[] { "TestMsg" });
// Quit Excel and clean up (its better to use the VSTOContrib by Jake Ginnivan).
oBook.Saved = true;
oBook.Close(false);
System.Runtime.InteropServices.Marshal.ReleaseComObject(oBook);
System.Runtime.InteropServices.Marshal.ReleaseComObject(oBooks);
System.Runtime.InteropServices.Marshal.ReleaseComObject(oExcel);
}
private static void RunMacro(object oApp, object[] oRunArgs)
{
oApp.GetType().InvokeMember("Run",
System.Reflection.BindingFlags.Default |
System.Reflection.BindingFlags.InvokeMethod,
null, oApp, oRunArgs);
//Your call looks a little bit wack in comparison, are you using an instance of the app?
//Application.GetType().InvokeMember(qualifiedMemberName.MemberName, BindingFlags.InvokeMethod, null, Application, null);
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
2)确保将宏代码放在模块(全局BAS文件)中.
Public Sub TestMsg()
MsgBox ("Hello Stackoverflow")
End Sub
Run Code Online (Sandbox Code Playgroud)
3)确保启用对VBA Project对象模型的宏安全性和信任访问:

试试这个帖子,看起来 Outlook 是不同的,但我想你已经知道了。给出的 hack 可能就足够了。
将代码创建为 Public Subs 并将代码放入 ThisOutlookSession 类模块中。然后,您可以使用 Outlook.Application.MySub() 调用名为 MySub 的子项目。当然,将其更改为正确的名称。
社交 MSDN:<Application.Run> 相当于 Microsoft Outlook
| 归档时间: |
|
| 查看次数: |
2105 次 |
| 最近记录: |