views:

648

answers:

1

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);
+5  A: 

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.

mghie
Thanks for the tips! So would I be able to use VirtualAllocEx to allocate some memory and then pass the memory in the SendMessage? How else could I retrieve the document text from Notepad++?
chris
I got it working. I was missing the OpenProcess call. I was also attempting to read the memory with the Marhal class provided in the .Net framework. I'll have to investigate as to why that method won't work. Thanks again!
chris
My pleasure, I'm happy you got it working.
mghie