7

Windows サービスを介して Office InfoPath 2010 の複数の並列インスタンスを自動化しようとしています。サービスからの Office の自動化がサポートされていないことは理解していますが、これは顧客の要件です。

他の Office アプリケーションを並行して自動化できますが、InfoPath の動作は異なります。

私が見つけたのは、いくつの並列呼び出しが行われたとしても、作成される INFOPATH.EXE プロセスのインスタンスは 1 つしかないということ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 の場合は、より複雑なテストを完了しました)。

コンソール アプリを実行すると、(タスク マネージャーを介して) 2 つの WINWORD.EXE プロセスが並行して作成され、1 つの INFOPATH.EXE プロセスのみが作成されていることがわかります。実際、InfoPathTest の最初のインスタンスが ip.Quit を呼び出すと、INFOPATH.EXE プロセスが終了します。InfoPathTest の 2 番目のインスタンスが ip.Quit を呼び出すと、DCOM タイムアウト例外がスローされます。つまり、2 つのインスタンスが同じ基になるオートメーション オブジェクトを共有しているように見え、ip.Quit の最初の呼び出しの後、そのオブジェクトは存在しなくなります。

この段階では、ユーザー ログインごとにサポートされる INFOPATH.EXE は 1 つだけだと思いました。Windows サービスを拡張して 2 つの新しいプロセス (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() メソッドを呼び出すだけです。

要約すると、これは機能しますが、特定のマシンでのみ機能します。失敗すると、2 番目の INFOPATH.EXE プロセスが実際に作成されますが、終了コード 0 ですぐに終了します。イベント ログには何も記録されず、コードにも例外はありません。

動作中のマシンと動作していないマシンを区別するために多くのことを調べましたが、今は行き詰まっています。

特に、複数の InfoPath インスタンスを並行して自動化する方法について他の考えがある場合は、ポインタを高く評価します。

4

2 に答える 2

1

Outlookで同じことを行おうとすると、同様の動作が発生すると思います。つまり、Microsoftは、複数のコピーを実行するのは悪い考えだと考えています。

そうだとすれば、2つの選択肢があります。

オプション1は、Infopathの自動化を同期させ、一度に1つのインスタンスを実行することです。

オプション2は、それが機能するかどうかはわかりませんが、仮想マシンを起動してInfoPathの作業を実行できるかどうかを確認することです。

これが少なくともいくつかの新しい列車の火付け役になることを願っていますが、それは成功につながるでしょう。

于 2012-05-18T13:16:25.620 に答える
1

Outlook で非常によく似た問題が発生しました。アプリケーションの単一インスタンスのみの実行を許可するという制限は、ユーザーごとではなく、対話型ログイン セッションごとに適用されます。詳細については、Outlook の単一インスタンス制限の調査を参照してください。

Outlook は、別のインスタンスが対話型ログイン セッションで既に実行されているかどうかを判断していました。[…] Outlook の初期化中に、クラス名が「mspim_wnd32」の「Microsoft Outlook」という名前のウィンドウが存在するかどうかを確認し、存在する場合は、別のインスタンスが既に実行されていると見なします。

これをハッキングする方法はいくつかあります。Hammer of God のサイト (下にスクロール) に複数の Outlook インスタンスを起動するためのツールがありますが、おそらく Win32 呼び出しのインターセプトが必要になるでしょう。

特定のマシンでのみ動作するコードについては、おそらく競合状態が原因です。両方のプロセスが同時に十分に速く起動することができた場合、それらは互いのウィンドウを検出せず、実行中の唯一のインスタンスであると想定します。ただし、マシンの速度が遅い場合、1 つのプロセスが他のプロセスよりも先にウィンドウを開くため、2 番目のプロセスが最初のプロセスのウィンドウを検出し、それ自体をシャットダウンします。再現するには、最初のプロセスの起動と 2 番目のプロセスの起動の間に数秒の遅延を導入してみてください。この方法では、最初のプロセスのみが成功するはずです。

于 2012-05-21T18:24:11.977 に答える