从不使用标签控件的对话框中获取文本? [英] Getting the text from a dialog box that does not use a label control?

查看:183
本文介绍了从不使用标签控件的对话框中获取文本?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这是我以前的问题的延续我发现Delphi的消息框中的文本不是窗口对象,它们是用DrawText方法绘制的。我使用 EasyHook 拦截Windows API调用,现在我可以抓住我关心的文本。 p>

  ////看来,DrawText总是调用DrawTextEx,所以它被截获两次。 
////只需要钩住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);

// COM东西必须在STA线程中运行,所以我们可以拦截它抛出的消息框。
var staThread = new Thread(()=>
{
try
{
var threadID = new [] {GetCurrentThreadId()};
//启用当前线程上的钩子
_drawTextExAHook.ThreadACL.SetInclusiveACL(threadID);

//告诉虚拟窗体启动ComThread
_dummyForm = new DummyForm(ComThread );
Application.Run(_dummyForm);
}
finally
{
if(_drawTextExAHook!= null)
_drawTextExAHook.Dispose();
}
});
staThread.SetApartmentState(ApartmentState.STA);
staThread.Name =Com Thread;
staThread.Start();

//等待Com Thread完成。
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);


This is a continuation of my previous question How to supress a dialog box an Inproc COM Server displays.


Background:

A recap of my situation: I have a Inproc COM Server written in Delphi from a 3rd party. One of the functions I call will display a error message dialog box if it traps a specific type of error. The issue is I am trying to process data in bulk, and the data source I am using is causing that error dialog to pop up a lot (thanks to the answer of my previous question it now auto closes and I was able to run it to completion, it would have shown the dialog box and required someone to press OK 9923 times). The process blocks till the message box is closed.


Question:

I would like to have better logging of what the error dialog said. However any attempt to get get the body text of the dialog box has failed.

//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();

I thought I may have been interrupting the dialog before it gets to add the text to it (it is not fully painted yet when I break in the above code). However putting a Application.DoEvents inside StartWindowListener before it starts the enumeration allows the dialog box to fully paint but I still get the same results as I posted with the above code.

Doing a Ctrl-C works correctly on the dialog box so I could use that in a pinch, but being that I have to repeat this 9923 times I would like to avoid using that programmatically.

Is there any other methods I can try to get the text from the Message box?

解决方案

Thanks to Sertac's comment I found out that the text in Delphi's message boxes are not window objects, they are drawn on with the 'DrawText' methods. I used EasyHook to intercept the Windows API calls and I am now able to grab the text I care about.

////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);

这篇关于从不使用标签控件的对话框中获取文本?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆