开发者

Junk characters coming in from pinvoke WM_GETTEXT

I have a method set that uses pinvoke to call WM_GETTEXT on another program's textbox - and it works fairly well, but frequently I just get back total junk text appended to the end of it. (The ORIGINAL text is always intact.)

This is random, I cannot reproduce it on demand, but it is frequent enough to be stopship.

Here is the text to get the information.

System.Text.StringBuilder strBuffer = new System.Text.StringBuilder();

int nLen = 0;

bool nUpdated = false;

try
{
    this.isOpen = false;

    if (ptrHandle == null)
        return;

    if (ptrHandle == IntPtr.Zero)
        return;

    nLen =
        Converter.SendMessage(ptrHandle, Converter.WM_GETTEXTLENGTH, 0, 0);

    if (nLen <= 0)
        return;

    if (nPreviousLen != nLen)
        nUpdated = true;

    if (nUpdated)
    {
        System.Diagnostics.Debug.WriteLine("nLen:\t{0}", nLen);

        strBuffer = new System.Text.StringBuilder(null, nLen + 1);

        System.Diagnostics.Debug.WriteLine("strBuffer:\t{0}", strBuffer.ToString());

        int sLen = Converter.SendMessageByString(ptrHandle, Converter.WM_GETTEXT, nLen
            , strBuffer);

        System.Diagnostics.Debug.WriteLine("sLen:\t{0}", sLen);

        System.Diagnostics.Debug.WriteLine("\n\nstrBuffern\n\n{0}", strBuffer.ToString());

        strBuffer = new System.Text.StringBuilder(strBuffer.ToString().Left(sLen));

        System.Diagnostics.Debug.WriteLine("\n\nsLenBuffer\n\n{0}", strBuffer.ToString());

source = new Special.IO.TextReader( 
                    new System.IO.MemoryStream(  System.Text.Encoding.Default.GetBytes(strBuffer.ToString() ) ), nUpdated );
        }
    }
}


    /// <summary>
    /// Sends the specified message to a window or windows. The SendMessage function calls the window procedure for the specified window and does not return until the window procedure has processed the message.
    /// <br />
    /// To send a message and return immediately, use the SendMessageCallback or SendNotifyMessage function. To post a message to a thread's message queue and return immediately, use the PostMessage or PostThreadMessage function.
    /// </summary>
    /// <param name="hWnd">
    /// Handle to the window whose window procedure will receive the message. 
    /// If this parameter is HWND_BROADCAST, the message is sent to all top-level windows in the system, including disabled or invisible unowned windows, overlapped windows, and pop-up windows; but the message is not sent to child windows.
    /// </param>
    /// <param name="Msg">
    /// [in] Specifies the message to be sent.
    /// </param>
    /// <param name="wParam">
    /// [in] Specifies additional message-specific information.
    /// </param>
    /// <param name="lParam">
    /// [in] Specifies additional message-specific information.
    /// </param>
    /// <returns>
    /// The return value specifies the result of the message processing; it depends on the message sent.
    /// </returns>
    [DllImport("user32.dll", EntryPoint = "SendMessageA", CharSet = CharSet.Ansi, SetLastError = false)]
    internal static extern int SendMessageByString(IntPtr hWnd, uint Msg, int wParam, StringBuilder lParam);

    /// <summary>
    /// Sends the specified message to a window or windows. The SendMessage function calls the window procedure for the specified window and does not return until the window procedure has processed the message.
    /// <br />
    /// To send a message and return immediately, use the SendMessageCallback or SendNotifyMessage function. To post a message to a thread's message queue and return immediately, use the PostMessage or PostThreadMessage function.
    /// </summary>
    /// <param name="hWnd">
    /// Handle to the window whose window procedure will receive the message. 
    /// If this parameter is HWND_BROADCAST, the message is sent to all top-level windows in the system, including disabled or invisible unowned windows, overlapped windows, and pop-up windows; but the message is not sent to child windows.
    /// </param>
    /// <param name="Msg">
    /// [in] Specifies the message to be sent.
    /// </param>
    /// <param name="wParam">
    /// [in] Specifies additional message-specific information.
    /// </param>
    /// <param name="lParam">
    /// [in] Specifies additional message-specific information.
    /// </param>
    /// <returns>
    /// The return value specifies the result of the message processing; it depends on the message sent.
    /// </returns>
    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    internal s开发者_如何学Pythontatic extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);


There seems to be some-thing weird going on with this, it looks like the control you are targeting, when using P/Invoke via WM_GETTEXT is returning the junk... I suggest the following, instead of returning the whole buffer, return back the current line which would make things a bit snappier...

try{
    int nLineCount = Converter.SendMessage(ptrHandle, Converter.EM_GETLINECOUNT, 0, 0);
    int nIndex = Converter.SendMessage(ptrHandle, Converter.EM_LINEINDEX, nLineCount, 0);
    int nLineLen = Converter.SendMessage(ptrHandle, Converter.EM_LINELENGTH, nIndex, 0);
    //
    strBuffer = new System.Text.StringBuilder(nLineLen);
    strBuffer.Append(Convert.ToChar(nLineLen));
    strBuffer.Length = nLineLen;
    int nCharCnt = Converter.SendMessage(ptrHandle, Converter.EM_GETLINE, new IntPtr(nLineCount),     strBuffer).ToInt32();
    nLen = nCharCnt;
    if (nLen <= 0) return;
    if (nPreviousLen != nLen) nUpdated = true;
}finally{
    source = new TextReader(strBuffer.ToString(), nUpdated, isOpen ? true : false);
    this.isOpen = true;
    nPreviousLen = nLen;
}

In that way, we obtain:

  • the line count in the control - nLineCount
  • Obtain the character index that is the start of the line nLineCount - nIndex
  • Finally, obtain the line length using the nIndex - nLineLen

Using nLineLen, then we can set up the StringBuilder buffer, the tricky part in using EM_GETLINE is, the zero'th position of the buffer MUST contain the length in char's - hence the usage of strBuffer.Append(Convert.ToChar(nLineLen)), and the stringbuilder's Length property specified.

Here are the constants required for the above P/Invoke

  • const int EM_GETLINECOUNT = 0xBA;
  • const int EM_LINEINDEX = 0xBB;
  • const int EM_LINELENGTH = 0xC1;


You shouldn't ignore the return value when you send WM_GETTEXT. From MSDN:

The return value is the number of characters copied, not including the terminating null character.

If the other app changes the control's text (to something shorter) between your WM_GETTEXTLENGTH and your WM_GETTEXT, then that would explain what you're seeing: WM_GETTEXT fills the first (let's say) 5 characters of your 20-character StringBuilder, and the rest is undefined. It might have null characters or it might have garbage (depends on things like whether you're calling the ANSI version of SendMessage, which would force the OS to allocate a probably-garbage-filled temporary buffer on your behalf), but either way, you need to strip them off before you use the string.

You need to read the return value of your SendMessageByString call, and truncate the StringBuilder to that length before using it.


It seems to me that your error is in the wrong usage of one parameter of WM_GETTEXT message. You should use nLen + 1 instead of nLen as the wParam.

At the beginning you use WM_GETTEXTLENGTH to get nLen, which will be the number of TCHARs copied, not including the terminating null character. Then you allocate the buffer of the size nLen + 1 characters. There steps are absolutely correctly, but then you send WM_GETTEXT with nLen as the wParam which is wrong, because corresponds to http://msdn.microsoft.com/en-us/library/ms632627.aspx wParam must contain the maximum number of characters to be copied, including the terminating null character. So the correct parameter of WM_GETTEXT message must be nLen + 1 instead of nLen.

The usage of the buffer which are larger as nLen I find for the best way. I'll recommend you to allocate buffer at least 2 characters longer as the nLen value returned by WM_GETTEXTLENGTH and use nLen + 2 as the parameter of WM_GETTEXT (exactly how large are you buffer size). If the returned value of the WM_GETTEXT are nLen or less, then you can be sure that the returned string contain full text which you want to read. If the result of WM_GETTEXT will be nLen + 1, then the text are changed between sending of WM_GETTEXTLENGTH and WM_GETTEXT messages and you should repeat all the steps starting with WM_GETTEXTLENGTH one more time to know the new text size.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜