来自pinvoke WM_GETTEXT的垃圾字符

时间:2010-08-18 22:07:51

标签: c# pinvoke

我有一个方法集,它使用pinvoke在另一个程序的文本框上调用WM_GETTEXT - 它工作得相当好,但我经常回到它后面的全部垃圾文本。 (ORIGINAL文本始终完好无损。)

这是随机的,我不能按要求重现它,但它经常足以成为停止。

以下是获取信息的文字。

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 static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);

3 个答案:

答案 0 :(得分:1)

发送WM_GETTEXT时不应忽略返回值。来自MSDN:

  

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

如果另一个应用程序更改了WM_GETTEXTLENGTH和WM_GETTEXT之间的控件文本(更短的内容),那么这将解释您所看到的内容:WM_GETTEXT填充了20个字符的StringBuilder的第一个(比方说)5个字符,其余的是未定义的。它可能有空字符或者它可能有垃圾(取决于你是否正在调用ANSI版本的SendMessage,这将迫使操作系统代表你分配一个可能是垃圾填充的临时缓冲区),但不管怎样,你需要在使用字符串之前将它们剥离。

您需要读取SendMessageByString调用的返回值,并在使用之前将StringBuilder截断为该长度。

答案 1 :(得分:1)

在我看来,您的错误是WM_GETTEXT消息的一个参数的错误使用。您应该使用nLen + 1代替nLen作为wParam

一开始,您使用WM_GETTEXTLENGTH获取nLen,这将是复制的TCHAR数量,不包括终止空字符。然后分配大小为nLen + 1个字符的缓冲区。步骤绝对正确,但是您发送WM_GETTEXT nLen作为wParam错误,因为对应http://msdn.microsoft.com/en-us/library/ms632627.aspx wParam必须包含最大数量要复制的字符,包括终止空字符。因此WM_GETTEXT消息的正确参数必须为nLen + 1而不是nLen

更大缓冲区的用法为nLen我找到了最好的方法。我建议你将缓冲区分配至少2个字符作为nLen返回的WM_GETTEXTLENGTH值,并使用nLen + 2作为WM_GETTEXT的参数(具体到你有多大)缓冲区大小)。如果WM_GETTEXT的返回值为nLen或更少,则可以确保返回的字符串包含您要读取的全文。如果WM_GETTEXT的结果为nLen + 1,则会在发送WM_GETTEXTLENGTHWM_GETTEXT消息之间更改文本,您应该重复从{{1}开始的所有步骤再一次知道新的文字大小。

答案 2 :(得分:1)

似乎有一些奇怪的事情,它看起来像你所针对的控件,当使用P / Invoke通过WM_GETTEXT返回垃圾...我建议以下,而不是返回整个缓冲区,返回当前行,这会让事情变得更加快捷......

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;
}

通过这种方式,我们获得:

  • 控件中的行数 - nLineCount
  • 获取作为行nLineCount的开头的字符索引 - nIndex
  • 最后,使用nIndex - nLineLen
  • 获取行长

使用nLineLen,然后我们可以设置StringBuilder缓冲区,使用EM_GETLINE的棘手部分是,缓冲区的第零个位置必须包含char的长度 - 因此strBuffer.Append(Convert.ToChar(nLineLen))的用法,以及指定的stringbuilder的Length属性。

以下是上述P / Invoke所需的常量

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