For communicating with Notepad++ itself, I've got most message-types (sending messages, getting back return-values and lParam [out] values, including sending or receiving a single string). I believe there's only one more type of wrapper I need: the interface to allow wParam to be a TCHAR** (in or out; since it's a **, if I can get one working, I should be able to get both), which is required for NPPM_GETOPENFILENAMES and related (1), and for NPPM_GETSESSIONFILES and NPPM_SAVESESSIONS (2)
(1: okay, techincally, I have a workaround for GETOPENFILES: I manually loop through the open buffers, and do single-filename-reads instead.)
(2: For NPPM_SAVESESSIONS, it actually requires wrapping it in a struct; but I think if I can make the TCHAR** work, then wrapping a struct around that should be doable. At least, once I have the technique, I'll have a technique, and I have this as a guide for how to do a struct.)
If I were in a plugin directly, and thus had access to the parent process memory space, in a C-like language, I could just define the wParam as a TCHAR**, and it would just work -- there are plenty of instances on github of plugins doing that, and I could have just translated those into the Perl equivalent. Unfortunately, because I'm in an external process, I have to use VirtualAllocEx and friends (as mentioned). As I said above, I was able to figure out enough of VirtualAllocEx (and its the Win32::GuiTest wrapper of AllocateVirtualBuffer) to get it to pass a single TCHAR* string back and forth. But I haven't been able to implement the TCHAR** the way I understand -- all I can read back is a string full of NULL \0 bytes.
My example code, which shows working Perl for a normal string, and my NULL-only result with TCHAR**:
#!c:\usr\local\apps\berrybrew\perls\system\perl\perl -l
# used http://www.piotrkaluski.com/files/winguitest/docs/winguitest.ht
+ml#id5446446 as a starting point
use Win32::GuiTest qw'WaitWindowLike SendMessage AllocateVirtualBuffer
+ WriteToVirtualBuffer ReadFromVirtualBuffer FreeVirtualBuffer';
use Encode;
use strict;
use Config;
use warnings;
use constant {
NPPM_GETFULLPATHFROMBUFFERID => 1024+1000+58,
NPPM_GETCURRENTBUFFERID => 1024+1000+60,
NPPM_GETNBOPENFILES => 1024+1000+7,
NPPM_GETOPENFILENAMES => 1024+1000+8,
};
my $hwnd;
BEGIN {
$hwnd = WaitWindowLike( 0, undef, '^Notepad\+\+$', undef, undef, 5
+ ) # wait up to 5sec
or die "could not open the Notepad++ application; try running
+a new instance of notepad++.exe"; # uncoverable branch true
}
# need a bufferID for my SSCCE
print "bufid = ", my $bufid = SendMessage( $hwnd, NPPM_GETCURRENTBUFF
+ERID, 0, 0 );
# I can read a single string in the LPARAM
print "getstr = ", my $getstr = SendMessage_getUcs2le( $hwnd, NPPM_GET
+FULLPATHFROMBUFFERID, $bufid, 0 );
# but having difficulty with getting TCHAR** back from WPARAM
my @fnames = getFileNames( $hwnd );
sub SendMessage_getUcs2le {
my $hwnd = shift; die "no hwnd sent" unless defined $hwnd;
my $msgid = shift; die "no message id sent" unless defined $msgid;
my $wparam = shift || 0;
my $buf_uc2le = Win32::GuiTest::AllocateVirtualBuffer( $hwnd, 1024
+ ); # 1024 byte string maximum
Win32::GuiTest::WriteToVirtualBuffer( $buf_uc2le, "\0"x1024 );
+ # pre-populate
my $rslt = SendMessage( $hwnd, $msgid, $wparam, $buf_uc2le->{ptr})
+;
my $rbuf = Win32::GuiTest::ReadFromVirtualBuffer( $buf_uc2le, 1024
+ );
Win32::GuiTest::FreeVirtualBuffer( $buf_uc2le );
return substr Encode::decode('ucs2-le', $rbuf), 0, $rslt; # retu
+rn the valid characters from the raw string
}
sub getFileNames {
my $hwnd = shift; die "no hwnd sent" unless defined $hwnd;
print "nOpenFiles = ", my $nFiles = SendMessage( $hwnd, NPPM_GETNB
+OPENFILES , 0 , 0 );
# allocate remote memory for the n pointers, 8 bytes per pointer
my $tcharpp = AllocateVirtualBuffer( $hwnd, $nFiles*$Config{ptrsiz
+e} ); #allocate 8-bytes per file for the pointer to each buffer
# allocate remote memory for the strings, each 1024 bytes long
my @strBufs = map { AllocateVirtualBuffer( $hwnd, 1024 ) } 1 .. $n
+Files;
# grab the pointers
my @strPtrs = map { $_->{ptr} } @strBufs;
# pack them into a string for writing into the virtual buffer
my $pk = $Config{ptrsize}==8 ? 'Q*' : 'L*'; # L is 32bit, so m
+aybe I need to pick L or Q depending on ptrsize?
my $tcharpp_val = pack $pk, @strPtrs;
# load the pointers into the tcharpp
WriteToVirtualBuffer( $tcharpp , $tcharpp_val );
# now send the message...
# https://web.archive.org/web/20190325050754/http://docs.notepad-p
+lus-plus.org/index.php/Messages_And_Notifications
# wParam = [out] TCHAR ** fileNames
# lParam = [in] int nbFile
print "SendMessage status: ", my $ret = SendMessage( $hwnd, NPPM_G
+ETOPENFILENAMES, $tcharpp, 0);
for my $text_buf ( @strBufs ) {
my $rd = ReadFromVirtualBuffer( $text_buf, 1024 );
printf "read '%s'\n", Encode::decode('ucs2-le', $rd);
# they all turn out as "\0" x 1024
}
FreeVirtualBuffer($_) for @strBufs, $tcharpp;
}
But part of it might just be my understanding of VirtualBufferEx, because even if I try in a simple C program, it does the same thing:
#include <windows.h>
#include <commctrl.h>
#include <stdio.h>
#include <stdlib.h>
// using http://www.piotrkaluski.com/files/winguitest/docs/winguitest.
+html as a guide, because it shows both C and Win32::GuiTest
#define NPPMSG (WM_USER + 1000)
#define NPP_GETNBOPENFILES (NPPMSG + 7)
#define NPP_GETOPENFILENAMES (NPPMSG + 8)
#define NPP_GETOPENFILENAMESPRIMARY (NPPMSG + 17)
#define NPP_GETOPENFILENAMESSECOND (NPPMSG + 18)
int main(int argc, char**argv) {
LRESULT ret, msg, w, l;
HWND hWnd;
int i;
for(i = 0; i<argc; i++) {
fprintf(stderr, "`%s` ", argv[i]);
}
fprintf(stderr, "\n");
if(argc<2) {
fprintf(stderr, "usage: %s _hWnd_\n", argv[0]);
fprintf(stderr, "perl -Ilib -MWin32::Mechanize::NotepadPlusPlu
+s=:main -le \"print $_, qq( => ), notepad()->{$_} for qw/_hwnd _exe _
+pid/\"\n");
return 255;
}
hWnd = (HWND)strtoull( argv[1], (char**)NULL, 0 );
// first need to verify I can do simple messages in C. Since I'll
+need it, grab the
ret = SendMessage((HWND)hWnd, (UINT)(msg=NPP_GETNBOPENFILES), (WPA
+RAM)(w=0), (LPARAM)(l=0));
fprintf(stderr, "run SendMessage(0x%016I64x,0x%016I64x,0x%016I64x,
+0x%016I64x) = %016I64x\n", (LRESULT)hWnd, msg, w, l, ret);
int n_all = ret;
ret = SendMessage((HWND)hWnd, (UINT)(msg=NPP_GETNBOPENFILES), (WPA
+RAM)(w=0), (LPARAM)(l=1));
fprintf(stderr, "run SendMessage(0x%016I64x,0x%016I64x,0x%016I64x,
+0x%016I64x) = %016I64x\n", (LRESULT)hWnd, msg, w, l, ret);
int n_one = ret;
ret = SendMessage((HWND)hWnd, (UINT)(msg=NPP_GETNBOPENFILES), (WPA
+RAM)(w=0), (LPARAM)(l=2));
fprintf(stderr, "run SendMessage(0x%016I64x,0x%016I64x,0x%016I64x,
+0x%016I64x) = %016I64x\n", (LRESULT)hWnd, msg, w, l, ret);
int n_two = ret;
fprintf(stderr, "all=%d, one=%d, two=%d\n", n_all, n_one, n_two);
// now get process info
DWORD pid = 0;
GetWindowThreadProcessId( hWnd , & pid );
HANDLE hProcHnd = OpenProcess( PROCESS_ALL_ACCESS, FALSE, pid );
fprintf(stderr, "pid = %ld, hProcHnd = 0x%016I64x\n", pid, (LRESUL
+T)hProcHnd);
#if 1
// I think this allocates virtual space for an array of LPVOID ele
+ments
LPVOID* fileNames = calloc( n_all , sizeof(LPVOID) );
LPVOID pVirtFileNames = VirtualAllocEx( hProcHnd, NULL, sizeof( LP
+VOID* ), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
fprintf(stderr, "pVirtFileNames = %p\n", pVirtFileNames);
SIZE_T copied = 0;
char fileName[1025];
// for each filename, allocate real and virtual for 1025 TCHAR
for(int i=0; i<n_all; i++) {
fileNames[i] = VirtualAllocEx( hProcHnd, NULL, 1025*sizeof(TCH
+AR), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
snprintf(fileName, 1024, "DummyString#%d", i);
WriteProcessMemory( hProcHnd, fileNames[i], fileName, 1025*siz
+eof(TCHAR), &copied );
//fprintf(stderr, "WriteProcessMemory(fileNames[%d], \"%s\
+"): %016I64d\n", i, fileName, copied);
snprintf(fileName, 1024, "This Overwrites Whatever Was There B
+efore");
//fprintf(stderr, "fileName cleared = \"%s\"\n", fileName)
+;
ReadProcessMemory( hProcHnd, fileNames[i], (LPVOID)fileName, 1
+025, &copied );
//fprintf(stderr, "ReadProcessMemory(buf0): \"%s\", copied
+ %016I64d\n", fileName, copied);
//for(int i=0; i<1025; i++) fprintf(stderr, "%02x ", fileN
+ame[i]); fprintf(stderr, "\n");
}
// now the local fileNames array should be populated...
// so write that array into process memory
WriteProcessMemory( hProcHnd, pVirtFileNames, fileNames, n_all*siz
+eof(LPVOID), &copied );
fprintf(stderr, "WriteProcessMemory(fileNames): %016I64d \n", copi
+ed);
// now that the process memory has been defined and loaded, let's
+try the SendMessage...
ret = SendMessage((HWND)hWnd, (UINT)(msg=NPP_GETOPENFILENAMES), w=
+(WPARAM)pVirtFileNames, l=(LPARAM)0);
fprintf(stderr, "run SendMessage(0x%016I64x,0x%016I64x,0x%016I64x,
+0x%016I64x) = %016I64x\n", (LRESULT)hWnd, msg, w, l, ret);
// read them back
for(int i=0; i<n_all; i++) {
snprintf(fileName, 1024, "DummyString#%d", i);
ReadProcessMemory( hProcHnd, fileNames[i], (LPVOID)fileName, 1
+025, &copied );
// it's not overwriting the dummy-string
}
// free virtual memory
for(int i=0; i<n_all; i++) {
VirtualFreeEx( hProcHnd, fileNames[i] , 0, MEM_RELEASE );
fprintf(stderr, "ReadProcessMemory(fileNames[%d]): \"%s\",
+ copied %016I64d\n", i, fileName, copied);
}
VirtualFreeEx( hProcHnd, pVirtFileNames , 0, MEM_RELEASE );
#else
// I'm going to try something different... use a structure (to beg
+in with)
typedef struct st_tcharpp { // allow up to 10 strings
TCHAR* buf0;
TCHAR* buf1;
TCHAR* buf2;
TCHAR* buf3;
TCHAR* buf4;
TCHAR* buf5;
TCHAR* buf6;
TCHAR* buf7;
TCHAR* buf8;
TCHAR* buf9;
} t_charpp;
LPVOID pVirtFileNames = VirtualAllocEx( hProcHnd, NULL, sizeof( t_
+charpp ), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
t_charpp charp_item;
charp_item.buf0 = VirtualAllocEx( hProcHnd, NULL, 1025*sizeof(TCHA
+R), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); fprintf(stderr, "buf0
+: %016I64x\n", (LRESULT)charp_item.buf0);
charp_item.buf1 = VirtualAllocEx( hProcHnd, NULL, 1025*sizeof(TCHA
+R), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); fprintf(stderr, "buf1
+: %016I64x\n", (LRESULT)charp_item.buf1);
charp_item.buf2 = VirtualAllocEx( hProcHnd, NULL, 1025*sizeof(TCHA
+R), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); fprintf(stderr, "buf2
+: %016I64x\n", (LRESULT)charp_item.buf2);
charp_item.buf3 = VirtualAllocEx( hProcHnd, NULL, 1025*sizeof(TCHA
+R), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); fprintf(stderr, "buf3
+: %016I64x\n", (LRESULT)charp_item.buf3);
charp_item.buf4 = VirtualAllocEx( hProcHnd, NULL, 1025*sizeof(TCHA
+R), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); fprintf(stderr, "buf4
+: %016I64x\n", (LRESULT)charp_item.buf4);
charp_item.buf5 = VirtualAllocEx( hProcHnd, NULL, 1025*sizeof(TCHA
+R), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); fprintf(stderr, "buf5
+: %016I64x\n", (LRESULT)charp_item.buf5);
charp_item.buf6 = VirtualAllocEx( hProcHnd, NULL, 1025*sizeof(TCHA
+R), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); fprintf(stderr, "buf6
+: %016I64x\n", (LRESULT)charp_item.buf6);
charp_item.buf7 = VirtualAllocEx( hProcHnd, NULL, 1025*sizeof(TCHA
+R), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); fprintf(stderr, "buf7
+: %016I64x\n", (LRESULT)charp_item.buf7);
charp_item.buf8 = VirtualAllocEx( hProcHnd, NULL, 1025*sizeof(TCHA
+R), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); fprintf(stderr, "buf8
+: %016I64x\n", (LRESULT)charp_item.buf8);
charp_item.buf9 = VirtualAllocEx( hProcHnd, NULL, 1025*sizeof(TCHA
+R), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); fprintf(stderr, "buf9
+: %016I64x\n", (LRESULT)charp_item.buf9);
SIZE_T copied = 0;
WriteProcessMemory( hProcHnd, pVirtFileNames, &charp_item, siz
+eof( charp_item ), &copied );
fprintf(stderr, "WriteProcessMemory(charp_item): %016I64d (should
+be sizeof(charp_item)=%016I64d)\n", copied, sizeof(charp_item));
// now that the process memory has been defined and loaded, let's
+try the SendMessage...
ret = SendMessage((HWND)hWnd, (UINT)(msg=NPP_GETOPENFILENAMES), w=
+(WPARAM)pVirtFileNames, l=(LPARAM)0);
fprintf(stderr, "run SendMessage(0x%016I64x,0x%016I64x,0x%016I64x,
+0x%016I64x) = %016I64x\n", (LRESULT)hWnd, msg, w, l, ret);
// didn't crash! try to read the buffers...
char fileName[1025];
ReadProcessMemory( hProcHnd, charp_item.buf0, (LPVOID)fileName, 10
+25, &copied ); fprintf(stderr, "ReadProcessMemory(buf0): \"%s\",
+ copied %016I64d\n", fileName, copied); for(int i=0; i<1025; i++) fpr
+intf(stderr, "%02x ", fileName[i]); fprintf(stderr, "\n");
ReadProcessMemory( hProcHnd, charp_item.buf1, (LPVOID)fileName, 10
+25, &copied ); fprintf(stderr, "ReadProcessMemory(buf1): \"%s\",
+ copied %016I64d\n", fileName, copied); for(int i=0; i<1025; i++) fpr
+intf(stderr, "%02x ", fileName[i]); fprintf(stderr, "\n");
ReadProcessMemory( hProcHnd, charp_item.buf2, (LPVOID)fileName, 10
+25, &copied ); fprintf(stderr, "ReadProcessMemory(buf2): \"%s\",
+ copied %016I64d\n", fileName, copied); for(int i=0; i<1025; i++) fpr
+intf(stderr, "%02x ", fileName[i]); fprintf(stderr, "\n");
ReadProcessMemory( hProcHnd, charp_item.buf3, (LPVOID)fileName, 10
+25, &copied ); fprintf(stderr, "ReadProcessMemory(buf3): \"%s\",
+ copied %016I64d\n", fileName, copied); for(int i=0; i<1025; i++) fpr
+intf(stderr, "%02x ", fileName[i]); fprintf(stderr, "\n");
ReadProcessMemory( hProcHnd, charp_item.buf4, (LPVOID)fileName, 10
+25, &copied ); fprintf(stderr, "ReadProcessMemory(buf4): \"%s\",
+ copied %016I64d\n", fileName, copied); for(int i=0; i<1025; i++) fpr
+intf(stderr, "%02x ", fileName[i]); fprintf(stderr, "\n");
ReadProcessMemory( hProcHnd, charp_item.buf5, (LPVOID)fileName, 10
+25, &copied ); fprintf(stderr, "ReadProcessMemory(buf5): \"%s\",
+ copied %016I64d\n", fileName, copied); for(int i=0; i<1025; i++) fpr
+intf(stderr, "%02x ", fileName[i]); fprintf(stderr, "\n");
ReadProcessMemory( hProcHnd, charp_item.buf6, (LPVOID)fileName, 10
+25, &copied ); fprintf(stderr, "ReadProcessMemory(buf6): \"%s\",
+ copied %016I64d\n", fileName, copied); for(int i=0; i<1025; i++) fpr
+intf(stderr, "%02x ", fileName[i]); fprintf(stderr, "\n");
ReadProcessMemory( hProcHnd, charp_item.buf7, (LPVOID)fileName, 10
+25, &copied ); fprintf(stderr, "ReadProcessMemory(buf7): \"%s\",
+ copied %016I64d\n", fileName, copied); for(int i=0; i<1025; i++) fpr
+intf(stderr, "%02x ", fileName[i]); fprintf(stderr, "\n");
ReadProcessMemory( hProcHnd, charp_item.buf8, (LPVOID)fileName, 10
+25, &copied ); fprintf(stderr, "ReadProcessMemory(buf8): \"%s\",
+ copied %016I64d\n", fileName, copied); for(int i=0; i<1025; i++) fpr
+intf(stderr, "%02x ", fileName[i]); fprintf(stderr, "\n");
ReadProcessMemory( hProcHnd, charp_item.buf9, (LPVOID)fileName, 10
+25, &copied ); fprintf(stderr, "ReadProcessMemory(buf9): \"%s\",
+ copied %016I64d\n", fileName, copied); for(int i=0; i<1025; i++) fpr
+intf(stderr, "%02x ", fileName[i]); fprintf(stderr, "\n");
// nope, 1025 NULLS, just like in Perl Win32::GuiTest. :-(
// 2019-Sep-20: one last thought before giving up and just looping
+ on getBufferFilename: go back to an array of VirtualMemory buffers,
+and actually implement that.
VirtualFreeEx( hProcHnd, charp_item.buf0 , 0 , MEM_RELEASE );
VirtualFreeEx( hProcHnd, charp_item.buf1 , 0 , MEM_RELEASE );
VirtualFreeEx( hProcHnd, charp_item.buf2 , 0 , MEM_RELEASE );
VirtualFreeEx( hProcHnd, charp_item.buf3 , 0 , MEM_RELEASE );
VirtualFreeEx( hProcHnd, charp_item.buf4 , 0 , MEM_RELEASE );
VirtualFreeEx( hProcHnd, charp_item.buf5 , 0 , MEM_RELEASE );
VirtualFreeEx( hProcHnd, charp_item.buf6 , 0 , MEM_RELEASE );
VirtualFreeEx( hProcHnd, charp_item.buf7 , 0 , MEM_RELEASE );
VirtualFreeEx( hProcHnd, charp_item.buf8 , 0 , MEM_RELEASE );
VirtualFreeEx( hProcHnd, charp_item.buf9 , 0 , MEM_RELEASE );
VirtualFreeEx( hProcHnd, pVirtFileNames , 0, MEM_RELEASE );
#endif
return 0;
}
(I know @ekopalypse in the Notepad++ community has shown some facility in hopping back and forth between languages... and vr in perlmonks has the only recent post on perlmonks involving AllocateVirtualBuffer... but I'm definitely not limiting my request for help to just those two.)