7

我正在尝试通过 Windows 服务自动化 Office InfoPath 2010 的多个并行实例。我了解不支持通过服务自动化 Office,但这是我的客户的要求。

我可以以并行方式自动化其他 Office 应用程序,但是 InfoPath 的行为不同。

我发现,无论进行多少次并行调用,都只会创建一个 INFOPATH.EXE 进程实例CreateObject("InfoPath.Application")。与此相反,可以通过类似的机制创建多个 WINWORD.EXE 实例CreateObject("Word.Application")

要重现此问题,可以使用简单的控制台应用程序。

static void Main(string[] args) {
    // Create two instances of word in parallel
    ThreadPool.QueueUserWorkItem(Word1);
    ThreadPool.QueueUserWorkItem(Word2);

    System.Threading.Thread.Sleep(5000);

    // Attempt to create two instances of infopath in parallel
    ThreadPool.QueueUserWorkItem(InfoPath1);
    ThreadPool.QueueUserWorkItem(InfoPath2);
}

static void Word1(object context) {
    OfficeInterop.WordTest word = new OfficeInterop.WordTest();
    word.Test();
}

static void Word2(object context) {
    OfficeInterop.WordTest word = new OfficeInterop.WordTest();
    word.Test();
}

static void InfoPath1(object context) {
    OfficeInterop.InfoPathTest infoPath = new OfficeInterop.InfoPathTest();
    infoPath.Test();
}

static void InfoPath2(object context) {
    OfficeInterop.InfoPathTest infoPath = new OfficeInterop.InfoPathTest();
    infoPath.Test();
}

InfoPathTest 和 WordTest 类 (VB) 在另一个项目中。

Public Class InfoPathTest
    Public Sub Test()
        Dim ip As Microsoft.Office.Interop.InfoPath.Application
        ip = CreateObject("InfoPath.Application")
        System.Threading.Thread.Sleep(5000)
        ip.Quit(False)
    End Sub
End Class

Public Class WordTest
    Public Sub Test()
        Dim app As Microsoft.Office.Interop.Word.Application
        app = CreateObject("Word.Application") 
        System.Threading.Thread.Sleep(5000)
        app.Quit(False)
    End Sub
End Class

互操作类只是创建自动化对象、休眠然后退出(尽管在 Word 的情况下,我已经完成了更复杂的测试)。

运行控制台应用程序时,我可以看到(通过任务管理器)并行创建了两个 WINWORD.EXE 进程,并且只创建了一个 INFOPATH.EXE 进程。事实上,当 InfoPathTest 的第一个实例调用 ip.Quit 时,INFOPATH.EXE 进程就会终止。当 InfoPathTest 的第二个实例调用 ip.Quit 时,会引发 DCOM 超时异常 - 看起来这两个实例共享相同的底层自动化对象,并且在第一次调用 ip.Quit 后该对象不再存在。

在这个阶段,我的想法是每个用户登录只支持一个 INFOPATH.EXE。我扩展了 Windows 服务以启动两个新进程(一个名为 InfoPathTest 的控制台应用程序),每个进程都在不同的用户帐户下运行。然后,这些新进程将尝试自动化 INFOPATH.EXE

这就是有趣的地方,这确实有效,但仅在某些机器上有效,我无法弄清楚为什么会这样。

以及服务代码(在AsproLock的帮助下):

public partial class InfoPathService : ServiceBase {
    private Thread _mainThread;
    private bool isStopping = false;

    public InfoPathService() {
        InitializeComponent();
    }

    protected override void OnStart(string[] args) {
        if (_mainThread == null || _mainThread.IsAlive == false) {
            _mainThread = new Thread(ProcessController);
            _mainThread.Start();
        }
    }

    protected override void OnStop() {
        isStopping = true;
    }        

    public void ProcessController() {
        while (isStopping == false) {
            try {

                IntPtr hWinSta = GetProcessWindowStation();
                WindowStationSecurity ws = new WindowStationSecurity(hWinSta, System.Security.AccessControl.AccessControlSections.Access);
                ws.AddAccessRule(new WindowStationAccessRule("user1", WindowStationRights.AllAccess, System.Security.AccessControl.AccessControlType.Allow));
                ws.AddAccessRule(new WindowStationAccessRule("user2", WindowStationRights.AllAccess, System.Security.AccessControl.AccessControlType.Allow));
                ws.AcceptChanges();

                IntPtr hDesk = GetThreadDesktop(GetCurrentThreadId());
                DesktopSecurity ds = new DesktopSecurity(hDesk, System.Security.AccessControl.AccessControlSections.Access);
                ds.AddAccessRule(new DesktopAccessRule("user1", DesktopRights.AllAccess, System.Security.AccessControl.AccessControlType.Allow));
                ds.AddAccessRule(new DesktopAccessRule("user2", DesktopRights.AllAccess, System.Security.AccessControl.AccessControlType.Allow));
                ds.AcceptChanges();

                ThreadPool.QueueUserWorkItem(Process1);
                ThreadPool.QueueUserWorkItem(Process2);

            } catch (Exception ex) {
                System.Diagnostics.Debug.WriteLine(String.Format("{0}: Process Controller Error {1}", System.Threading.Thread.CurrentThread.ManagedThreadId, ex.Message));
            }

            Thread.Sleep(15000);
        }
    }

    private static void Process1(object context) {

        SecureString pwd2;

        Process process2 = new Process();
        process2.StartInfo.FileName = @"c:\debug\InfoPathTest.exe";

        process2.StartInfo.UseShellExecute = false;
        process2.StartInfo.LoadUserProfile = true;
        process2.StartInfo.WorkingDirectory = @"C:\debug\";
        process2.StartInfo.Domain = "DEV01";
        pwd2 = new SecureString(); foreach (char c in "password") { pwd2.AppendChar(c); };
        process2.StartInfo.Password = pwd2;
        process2.StartInfo.UserName = "user1";
        process2.Start();

        process2.WaitForExit();
    }

    private static void Process2(object context) {
        SecureString pwd2;

        Process process2 = new Process();
        process2.StartInfo.FileName = @"c:\debug\InfoPathTest.exe";
        process2.StartInfo.UseShellExecute = false;
        process2.StartInfo.LoadUserProfile = true;
        process2.StartInfo.WorkingDirectory = @"C:\debug\";
        process2.StartInfo.Domain = "DEV01";
        pwd2 = new SecureString(); foreach (char c in "password") { pwd2.AppendChar(c); };
        process2.StartInfo.Password = pwd2;
        process2.StartInfo.UserName = "user2";
        process2.Start();

        process2.WaitForExit();
    }

    [DllImport("user32.dll", SetLastError = true)]
    public static extern IntPtr GetProcessWindowStation();

    [DllImport("user32.dll", SetLastError = true)]
    public static extern IntPtr GetThreadDesktop(int dwThreadId);

    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern int GetCurrentThreadId();

}

InfoPathTest.exe 进程只调用上面详述的 InfoPathTest.Test() 方法。

总之,这有效,但仅在某些机器上有效。当它失败时,实际上创建了第二个 INFOPATH.EXE 进程,但立即退出,退出代码为 0。事件日志中没有任何内容,代码中也没有任何异常。

我已经查看了很多东西来尝试区分工作/非工作机器,但我现在卡住了。

任何指针表示赞赏,特别是如果您对如何并行自动化多个 InfoPath 实例有其他想法。

4

2 回答 2

1

我猜如果你尝试对 Outlook 做同样的事情,你会得到类似的行为,这意味着微软认为运行多个副本是个坏主意。

如果是这样,我看到两个选项。

选项一是让您的 Infopath 自动化同步,一次运行一个实例。

选项二,我没有知道它是否会起作用,看看您是否可以启动虚拟机来完成您的 InfoPath 工作。

我希望这至少可以激发一些新的尝试,尽管这将导致成功。

于 2012-05-18T13:16:25.620 回答
1

我在 Outlook 中遇到了非常相似的问题。仅允许运行应用程序的单个实例的限制不适用于每个用户,而是适用于每个交互式登录会话。您可以在Investigating Outlook's Single-Instance Restriction中了解更多信息:

Outlook 正在确定另一个实例是否已经在交互式登录会话中运行。[…] 在 Outlook 初始化期间,它会检查是否存在名为“Microsoft Outlook”且类名为“mspim_wnd32”的窗口,如果存在,则假定另一个实例已在运行。

有一些方法可以绕过它——在Hammer of God网站上有一个用于启动多个 Outlook 实例的工具(向下滚动)——但它们可能会涉及拦截 Win32 调用。

至于您的代码仅适用于某些机器:这可能是由于竞争条件。如果两个进程设法同时启动得足够快,那么它们就不会检测到彼此的窗口,并假设它们是唯一运行的实例。但是,如果机器速度很慢,一个进程会先于另一个进程打开它的窗口,从而导致第二个进程检测到第一个进程的窗口并自行关闭。要重现,请尝试在启动第一个进程和第二个进程之间引入几秒钟的延迟——这样,只有第一个进程应该成功。

于 2012-05-21T18:24:11.977 回答