Question

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);
Was it helpful?

Solution

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.

OTHER TIPS

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top