12

これは、私の前の質問の続きです。InprocCOMサーバーが表示するダイアログボックスを抑制する方法


バックグラウンド:

私の状況の要約:サードパーティからDelphiで記述されたInprocCOMサーバーがあります。私が呼び出す関数の1つは、特定のタイプのエラーをトラップすると、エラーメッセージダイアログボックスを表示します。問題は、データをまとめて処理しようとしていることです。使用しているデータソースにより、エラーダイアログが頻繁にポップアップ表示されます(前の質問の回答のおかげで、自動的に閉じて実行できました。完了すると、ダイアログボックスが表示され、誰かが[OK]を9923回押す必要があります)。メッセージボックスが閉じられるまで、プロセスはブロックされます。


質問:

エラーダイアログの内容をより適切にログに記録したいと思います。ただし、ダイアログボックスの本文テキストを取得する試みは失敗しました。

ダイアログボックスの画像

//Snip

private void StartWindowListener()
{
    //Queue the watcher on the message pump if we are not watching.
    if (_watcherRunning == false)
    {
        _watcherRunning = true;
        _dummyForm.BeginInvoke(new Action(() =>
        {
            _watcherRunning = false;

            //If we are not inside the com object don't enumerate.
            if (_insideCom == false) return;

            // Enumerate windows to find dialogs
            EnumThreadWndProc callback = new EnumThreadWndProc(CheckWindow);
            EnumThreadWindows(GetCurrentThreadId(), callback, IntPtr.Zero);
            GC.KeepAlive(callback);
        }));
    }
}

private bool CheckWindow(IntPtr hWnd, IntPtr lp)
{
    // Checks if hWnd is the expected dialog
    StringBuilder sb = new StringBuilder(260);
    GetClassName(hWnd, sb, sb.Capacity);
    if (sb.ToString() == "TMessageForm")
    {
        //This returns the dialog box's title
        GetWindowText(hWnd, sb, sb.Capacity);

        //This returns IntPtr.Zero
        var hDialogText = GetDlgItem(hWnd, 0xFFFF);
        if (hDialogText != IntPtr.Zero)
            GetWindowText(hDialogText, sb, sb.Capacity);

        //This returns a empty string
        GetDlgItemText(hWnd, 0xFFFF, sb, sb.Capacity);


        //Only sees the OK button.
        IntPtr hCtl = IntPtr.Zero;
        HashSet<IntPtr> seen = new HashSet<IntPtr>();
        while ((hCtl = GetNextDlgGroupItem(hWnd, hCtl, false)) != IntPtr.Zero)
        {
            //When we see the same control twice, break out of the loop.
            if (seen.Add(hCtl) == false)
                break;

            GetClassName(hCtl, sb, sb.Capacity);
            SendMessage(hCtl, WM_GETTEXT, sb.Capacity, sb)

            //Close the dialog by sending WM_CLOSE to the window
            SendMessage(hWnd, WM_CLOSE, IntPtr.Zero, IntPtr.Zero);
        }

         //Snip...
    }
    return true;
}

//Snip...

// P/Invoke declarations
const int WM_CLOSE = 0x0010;
private delegate bool EnumThreadWndProc(IntPtr hWnd, IntPtr lp);
[DllImport("user32.dll")]
private static extern bool EnumThreadWindows(int tid, EnumThreadWndProc callback, IntPtr lp);
[DllImport("user32.dll")]
private static extern int GetClassName(IntPtr hWnd, StringBuilder buffer, int buflen);
[DllImport("user32.dll")]
private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, IntPtr lp);
[DllImport("kernel32.dll")]
private static extern int GetCurrentThreadId();

テキストを追加する前にダイアログを中断している可能性があると思いました(上記のコードを中断すると、まだ完全にペイントされていません)。ただしApplication.DoEvents、列挙を開始する前にStartWindowListenerを内部に配置すると、ダイアログボックスを完全にペイントできますが、上記のコードで投稿したのと同じ結果が得られます。

Ctrl-Cを実行すると、ダイアログボックスで正しく機能するため、ピンチで使用できますが、これを9923回繰り返す必要があるため、プログラムで使用することは避けたいと思います。

メッセージボックスからテキストを取得する方法は他にありますか?

4

1 に答える 1

7

Sertac のコメントのおかげで、Delphi のメッセージ ボックス内のテキストはウィンドウ オブジェクトではなく、「DrawText」メソッドで描画されることがわかりました。EasyHookを使用して Windows API 呼び出しをインターセプトしたところ、気になるテキストを取得できるようになりました。

////It appears that DrawText always calls DrawTextEx so it is getting intercepted twice.
//// Only need to hook DrawTextEx
static EasyHook.LocalHook _drawTextExAHook;

//Snip...

public override void Run()
{
    //Snip...

    IntPtr drawTextExAPtr = EasyHook.LocalHook.GetProcAddress("user32", "DrawTextExA");
    _drawTextExAHook = EasyHook.LocalHook.Create(drawTextExAPtr, new DrawTextExDelegate(DrawTextEx_Hooked), null);

    //The COM stuff must be run in a STA Thread so we can intercept the message boxes that it throws up.
    var staThread = new Thread(() =>
        {
            try
            {
                var threadID = new[] { GetCurrentThreadId() };
                //Enable the hook on the current thread.
                _drawTextExAHook.ThreadACL.SetInclusiveACL(threadID);

                //Tell the dummy form to start ComThread
                _dummyForm = new DummyForm(ComThread);
                Application.Run(_dummyForm);
            }
            finally
            {
                if(_drawTextExAHook != null)
                    _drawTextExAHook.Dispose();
            }
        });
    staThread.SetApartmentState(ApartmentState.STA);
    staThread.Name = "Com Thread";
    staThread.Start();

    //Wait for the Com Thread to finish.
    staThread.Join();

}

//Snip...

private delegate int DrawTextExDelegate(IntPtr hdc, string lpchText, int cchText,
                ref Rect lprc, uint dwDTFormat, ref DRAWTEXTPARAMS lpDTParams);

private int DrawTextEx_Hooked(IntPtr hdc, string lpchText, int cchText, ref Rect lprc, 
                                     uint dwDTFormat, ref DRAWTEXTPARAMS lpDTParams)
{
    LogErrorText(lpchText);
    return DrawTextEx(hdc, lpchText, cchText, ref lprc, dwDTFormat, ref lpDTParams);
}

[DllImport("user32.dll")]
static extern int DrawTextEx(IntPtr hdc, string lpchText, int cchText,
                             ref Rect lprc, uint dwDTFormat, ref DRAWTEXTPARAMS lpDTParams);
于 2012-09-26T20:29:59.560 に答える