使用 SendMessage 从 scintilla 控件中检索文本

发布于 2024-07-14 04:11:37 字数 661 浏览 5 评论 0原文

我正在尝试使用 C# 中的 SendMessage 检索 Notepad++ 中的文档文本。 下面是我当前的代码。 第一次调用 SendMessage 正确返回文本的长度。 对 SendMessage 的第二次调用不会将文本插入到 StringBuilder 变量 text 中。 为什么不?

    [DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)]
    static extern int SendMessage(IntPtr hWnd, int msg, int wParam, int lParam);

    [DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)]
    static extern int SendMessage(IntPtr hWnd, int msg, int wParam, StringBuilder lParam);


    var length = SendMessage(hWnd, 2183, 0,0);
    var text = new StringBuilder(length +1);
    SendMessage(hWnd, 2182, length + 1, text);

I'm attempting to retrieve the document text in Notepad++ using SendMessage in C#. Below is my current code. The first call to SendMessage correctly returns the length of the text. The second call to SendMessage does not insert the text into the StringBuilder variable text. Why not?

    [DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)]
    static extern int SendMessage(IntPtr hWnd, int msg, int wParam, int lParam);

    [DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)]
    static extern int SendMessage(IntPtr hWnd, int msg, int wParam, StringBuilder lParam);


    var length = SendMessage(hWnd, 2183, 0,0);
    var text = new StringBuilder(length +1);
    SendMessage(hWnd, 2182, length + 1, text);

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

攒一口袋星星 2024-07-21 04:11:37

问题在于,您向 Scintilla 控件发送消息,该消息的 lParam 中包含 StringBuilder 缓冲区的地址,但 Notepad++ 中的 Scintilla 控件位于不同的地址空间,因此无法写入它接收到的窗口消息中的地址到。 WM_GETTEXTWM_SETTEXT 等标准消息的处理方式是为您执行必要的地址映射,但 Scintilla 控件使用的特殊消息不会发生这种情况。 有关更多信息,请查找编组。

不幸的是,Scintilla 控件正在逐步淘汰对 WM_GETTEXTLENGTHWM_GETTEXT 的支持,并且文档建议使用特殊的 SCI_XXX 消息。 Notepad++ 已经无法使用 WM_GETTEXT,因此您需要使用 SCI_GETTEXTLENGTH (2183) 和 SCI_GETTEXT (2182),并自行进行编组。

警告:从另一个应用程序发送 SCI_GETTEXT 消息而不对缓冲区地址进行特殊处理实际上是危险的 - Notepad++ 会将数据复制到缓冲区,但由于该地址在其自己的地址空间中无效这可能会立即导致访问冲突,或者(更糟糕)它可能会默默地覆盖内部数据。


您可以使用 VirtualAllocEx() 和 ReadProcessMemory() 来使用具有 Notepad++ 可用地址的缓冲区。 我已经整理了一个适合我的快速 Delphi 程序,重要的代码是这样的:

procedure TForm1.Button1Click(Sender: TObject);
const
  VMFLAGS = PROCESS_VM_OPERATION or PROCESS_VM_READ or PROCESS_VM_WRITE;
var
  Wnd: HWND;
  Len: integer;
  ProcessId, BytesRead: Cardinal;
  ProcessHandle: THandle;
  MemPtr: PChar;
  s: string;
begin
  Wnd := $30488;
  Len := SendMessage(Wnd, 2183, 0, 0);
  if Len > 0 then begin
    GetWindowThreadProcessId(Wnd, @ProcessId);
    ProcessHandle := OpenProcess(VMFLAGS, FALSE, ProcessId);
    MemPtr := VirtualAllocEx(ProcessHandle, nil, Len + 1,
      MEM_RESERVE or MEM_COMMIT, PAGE_READWRITE);
    if MemPtr <> nil then try
      SendMessage(Wnd, 2182, Len + 1, integer(MemPtr));
      SetLength(s, Len + 1);
      ReadProcessMemory(ProcessHandle, MemPtr, @s[1], Len + 1, BytesRead);
      SetLength(s, BytesRead);
      Memo1.Lines.Text := s;
    finally
      VirtualFreeEx(ProcessId, MemPtr, Len + 1, MEM_RELEASE);
    end;
  end;
end;

这是使用 Ansi 版本的 API 函数的早期 Delphi 版本,您可能会使用 SendMessageW 和 WideChar 缓冲区,但总体思路应该是清除。

The problem is that you send a message to the Scintilla control that has the address of your StringBuilder buffer in the lParam, but the Scintilla control in Notepad++ lives in a different address space, so the address in the window message it receives can not be written to. Standard messages like WM_GETTEXT and WM_SETTEXT are handled in a way that the necessary address mapping is performed for you, but this does not happen for the special messages the Scintilla control uses. For more information lookup marshalling.

Unfortunately support for WM_GETTEXTLENGTH and WM_GETTEXT is being phased out of the Scintilla control, and the documentation advises to use the special SCI_XXX messages. Notepad++ does already not work with WM_GETTEXT, so you need to use SCI_GETTEXTLENGTH (2183) and SCI_GETTEXT (2182), and do the marshalling yourself.

Warning: It is actually dangerous to send the SCI_GETTEXT message from another application without special handling of the buffer address - Notepad++ will copy the data to the buffer, but since the address is not valid in its own address space this can cause an access violation immediately, or (worse) it could silently overwrite internal data.


You can use VirtualAllocEx() and ReadProcessMemory() to use a buffer with an address usable by Notepad++. I have put together a quick Delphi program that works for me, the important code is this:

procedure TForm1.Button1Click(Sender: TObject);
const
  VMFLAGS = PROCESS_VM_OPERATION or PROCESS_VM_READ or PROCESS_VM_WRITE;
var
  Wnd: HWND;
  Len: integer;
  ProcessId, BytesRead: Cardinal;
  ProcessHandle: THandle;
  MemPtr: PChar;
  s: string;
begin
  Wnd := $30488;
  Len := SendMessage(Wnd, 2183, 0, 0);
  if Len > 0 then begin
    GetWindowThreadProcessId(Wnd, @ProcessId);
    ProcessHandle := OpenProcess(VMFLAGS, FALSE, ProcessId);
    MemPtr := VirtualAllocEx(ProcessHandle, nil, Len + 1,
      MEM_RESERVE or MEM_COMMIT, PAGE_READWRITE);
    if MemPtr <> nil then try
      SendMessage(Wnd, 2182, Len + 1, integer(MemPtr));
      SetLength(s, Len + 1);
      ReadProcessMemory(ProcessHandle, MemPtr, @s[1], Len + 1, BytesRead);
      SetLength(s, BytesRead);
      Memo1.Lines.Text := s;
    finally
      VirtualFreeEx(ProcessId, MemPtr, Len + 1, MEM_RELEASE);
    end;
  end;
end;

This is an earlier Delphi version using the Ansi version of the API functions, you would probably use SendMessageW and a WideChar buffer, but the general idea should be clear.

风苍溪 2024-07-21 04:11:37

经过一番摆弄后我得到了它的工作。

在 VirtualFreeEx 中使用 MEM_RELEASE 时,我相信大小必须为 0,否则函数将返回 false

I got it working after some fiddling.

When using MEM_RELEASE in VirtualFreeEx, I believe the size must be 0, or the function will return false.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文