External SendMessage to Notepad++ for NPPM_GETOPENFILENAMES and other TCHAR**
-
c-code which wouldn’t fit in previous post:
#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::NotepadPlusPlus=: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), (WPARAM)(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), (WPARAM)(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), (WPARAM)(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, (LRESULT)hProcHnd); #if 1 // I think this allocates virtual space for an array of LPVOID elements LPVOID* fileNames = calloc( n_all , sizeof(LPVOID) ); LPVOID pVirtFileNames = VirtualAllocEx( hProcHnd, NULL, sizeof( LPVOID* ), 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(TCHAR), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); snprintf(fileName, 1024, "DummyString#%d", i); WriteProcessMemory( hProcHnd, fileNames[i], fileName, 1025*sizeof(TCHAR), &copied ); //fprintf(stderr, "WriteProcessMemory(fileNames[%d], \"%s\"): %016I64d\n", i, fileName, copied); snprintf(fileName, 1024, "This Overwrites Whatever Was There Before"); //fprintf(stderr, "fileName cleared = \"%s\"\n", fileName); ReadProcessMemory( hProcHnd, fileNames[i], (LPVOID)fileName, 1025, &copied ); //fprintf(stderr, "ReadProcessMemory(buf0): \"%s\", copied %016I64d\n", fileName, copied); //for(int i=0; i<1025; i++) fprintf(stderr, "%02x ", fileName[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*sizeof(LPVOID), &copied ); fprintf(stderr, "WriteProcessMemory(fileNames): %016I64d \n", copied); // 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, 1025, &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 begin 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(TCHAR), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); fprintf(stderr, "buf0: %016I64x\n", (LRESULT)charp_item.buf0); charp_item.buf1 = VirtualAllocEx( hProcHnd, NULL, 1025*sizeof(TCHAR), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); fprintf(stderr, "buf1: %016I64x\n", (LRESULT)charp_item.buf1); charp_item.buf2 = VirtualAllocEx( hProcHnd, NULL, 1025*sizeof(TCHAR), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); fprintf(stderr, "buf2: %016I64x\n", (LRESULT)charp_item.buf2); charp_item.buf3 = VirtualAllocEx( hProcHnd, NULL, 1025*sizeof(TCHAR), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); fprintf(stderr, "buf3: %016I64x\n", (LRESULT)charp_item.buf3); charp_item.buf4 = VirtualAllocEx( hProcHnd, NULL, 1025*sizeof(TCHAR), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); fprintf(stderr, "buf4: %016I64x\n", (LRESULT)charp_item.buf4); charp_item.buf5 = VirtualAllocEx( hProcHnd, NULL, 1025*sizeof(TCHAR), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); fprintf(stderr, "buf5: %016I64x\n", (LRESULT)charp_item.buf5); charp_item.buf6 = VirtualAllocEx( hProcHnd, NULL, 1025*sizeof(TCHAR), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); fprintf(stderr, "buf6: %016I64x\n", (LRESULT)charp_item.buf6); charp_item.buf7 = VirtualAllocEx( hProcHnd, NULL, 1025*sizeof(TCHAR), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); fprintf(stderr, "buf7: %016I64x\n", (LRESULT)charp_item.buf7); charp_item.buf8 = VirtualAllocEx( hProcHnd, NULL, 1025*sizeof(TCHAR), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); fprintf(stderr, "buf8: %016I64x\n", (LRESULT)charp_item.buf8); charp_item.buf9 = VirtualAllocEx( hProcHnd, NULL, 1025*sizeof(TCHAR), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); fprintf(stderr, "buf9: %016I64x\n", (LRESULT)charp_item.buf9); SIZE_T copied = 0; WriteProcessMemory( hProcHnd, pVirtFileNames, &charp_item, sizeof( 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, 1025, &copied ); fprintf(stderr, "ReadProcessMemory(buf0): \"%s\", copied %016I64d\n", fileName, copied); for(int i=0; i<1025; i++) fprintf(stderr, "%02x ", fileName[i]); fprintf(stderr, "\n"); ReadProcessMemory( hProcHnd, charp_item.buf1, (LPVOID)fileName, 1025, &copied ); fprintf(stderr, "ReadProcessMemory(buf1): \"%s\", copied %016I64d\n", fileName, copied); for(int i=0; i<1025; i++) fprintf(stderr, "%02x ", fileName[i]); fprintf(stderr, "\n"); ReadProcessMemory( hProcHnd, charp_item.buf2, (LPVOID)fileName, 1025, &copied ); fprintf(stderr, "ReadProcessMemory(buf2): \"%s\", copied %016I64d\n", fileName, copied); for(int i=0; i<1025; i++) fprintf(stderr, "%02x ", fileName[i]); fprintf(stderr, "\n"); ReadProcessMemory( hProcHnd, charp_item.buf3, (LPVOID)fileName, 1025, &copied ); fprintf(stderr, "ReadProcessMemory(buf3): \"%s\", copied %016I64d\n", fileName, copied); for(int i=0; i<1025; i++) fprintf(stderr, "%02x ", fileName[i]); fprintf(stderr, "\n"); ReadProcessMemory( hProcHnd, charp_item.buf4, (LPVOID)fileName, 1025, &copied ); fprintf(stderr, "ReadProcessMemory(buf4): \"%s\", copied %016I64d\n", fileName, copied); for(int i=0; i<1025; i++) fprintf(stderr, "%02x ", fileName[i]); fprintf(stderr, "\n"); ReadProcessMemory( hProcHnd, charp_item.buf5, (LPVOID)fileName, 1025, &copied ); fprintf(stderr, "ReadProcessMemory(buf5): \"%s\", copied %016I64d\n", fileName, copied); for(int i=0; i<1025; i++) fprintf(stderr, "%02x ", fileName[i]); fprintf(stderr, "\n"); ReadProcessMemory( hProcHnd, charp_item.buf6, (LPVOID)fileName, 1025, &copied ); fprintf(stderr, "ReadProcessMemory(buf6): \"%s\", copied %016I64d\n", fileName, copied); for(int i=0; i<1025; i++) fprintf(stderr, "%02x ", fileName[i]); fprintf(stderr, "\n"); ReadProcessMemory( hProcHnd, charp_item.buf7, (LPVOID)fileName, 1025, &copied ); fprintf(stderr, "ReadProcessMemory(buf7): \"%s\", copied %016I64d\n", fileName, copied); for(int i=0; i<1025; i++) fprintf(stderr, "%02x ", fileName[i]); fprintf(stderr, "\n"); ReadProcessMemory( hProcHnd, charp_item.buf8, (LPVOID)fileName, 1025, &copied ); fprintf(stderr, "ReadProcessMemory(buf8): \"%s\", copied %016I64d\n", fileName, copied); for(int i=0; i<1025; i++) fprintf(stderr, "%02x ", fileName[i]); fprintf(stderr, "\n"); ReadProcessMemory( hProcHnd, charp_item.buf9, (LPVOID)fileName, 1025, &copied ); fprintf(stderr, "ReadProcessMemory(buf9): \"%s\", copied %016I64d\n", fileName, copied); for(int i=0; i<1025; i++) fprintf(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; }
-
You need to get the process handle from OpenProcess etc… not the window handle. See VirtualAllocEx.
I will give it a try and see what needs to be done using python.
-
I think exchanging a TCHAR** between two different processes can not work the way you need it.
The documentation of VirtualAllocEx states:
Reserves, commits, or changes the state of a region of memory within the virtual address space of a specified process.
That means in your process you can allocate memory in the address space of e.g. Notepad++ and transfer a pointer to that memory via SendMessage to Npp. Npp will be able to write to that memory and your process will be able to read from that memory. That’s the reason why using a TCHAR* works.
But what has to happen technically to make it possible using a TCHAR** in the desired way?
- Your process has to allocate memory in the address space of Npp via VirtualAllocEx that can hold a pointer.
- You send a pointer to this memory using SendMessage to Npp.
- Npp has to allocate memory in the address space of your process via VirtualAllocEx, writes the results of e.g. a NPP_GETOPENFILENAMES call to that memory and writes the address of the memory to the piece of memory whose address it has got via your SendMessage call (remember: this is the memory of step 1 that can hold a pointer).
- When the SendMessage call returns, you process can dereference the pointer from step 1 to get the address of/pointer to the result data. When dereferencing that pointer, your process can gain access to the result data.
It is totally clear that step 3 never happens in the way like noted above. Instead Npp allocates memory in its own address space and writes a pointer to that memory to the piece of memory your process referenced in its SendMessage call. Thus your process can read that pointer (the address of the result data), but since the result data resides in the address space of Npp your process can not dereference its pointer. That’s the reason why using a TCHAR** doesn’t work.
-
Addition: To solve the problem you need one of the following:
- A COM or ActiveX interface for Notepad++ that allows an external process to interact with it.
- A bridge plugin that you can control via
NPPM_MSGTOPLUGIN
messages sent to the Npp window. This plugin should forward messages from external processes to Npp and send back the results.
-
Good news, I got a solution at perlmonks (below).
But first, replying to a couple of things:
@Ekopalypse said in External SendMessage to Notepad++ for NPPM_GETOPENFILENAMES and other TCHAR**:
You need to get the process handle from OpenProcess etc… not the window handle.
The perl wrappers for
AllocateVirtualBuffer
->VirtualAllocEx
take care of the process handle. (Otherwise it wouldn’t have worked for the plain string)- Npp has to allocate memory in the address space of your process via VirtualAllocEx
Actually, no. The
my @strBufs = map { AllocateVirtualBuffer( $hwnd, 1024 ) } 1 .. $nFiles;
is a perl-idiom for loop that allocates n different buffers; thepack
a few lines later packs those virtual-buffer pointers into the main virtual-buffer (loading the TCHAR** with n TCHAR* memories). The pointer to the main virtual buffer is then what functions as the TCHAR**Solution
In perlmonks, vr pointed out that I when I called the SendMessage, I accidentally sent it the perl variable
$tcharpp
instead of the pointer thatAllocateVirtualBuffer
->VirtualAllocEx
created:$tcharpp->{ptr}
. And I forgot to pass it the number of files.Changing that one line to
print "SendMessage status: ", my $ret = SendMessage( $hwnd, NPPM_GETOPENFILENAMES, $tcharpp->{ptr}, $nFiles);
solved both problems, and I now can get the file names back.
I feel silly about missing the ->{ptr}, because I got that right in the normal string SendMessage, so I should have noticed that! And if I hadn’t been so distracted by that not working, I may have eventually noticed that I was asking for 0 filenames… then again, maybe not. :-)
-
@dinkumoil said in External SendMessage to Notepad++ for NPPM_GETOPENFILENAMES and other TCHAR**:
To solve the problem you need one of the following:
Fortunately, it ended up being simpler than that. :-)
-
Hmm, no more mark-as-question / mark-as-solved in the topic tools.
-
@PeterJones said in External SendMessage to Notepad++ for NPPM_GETOPENFILENAMES and other TCHAR**:
loop that allocates n different buffers;
In case I wasn’t clear (3min passed, so cannot edit): when passing it the TCHAR**, it is the calling process’s responsibility to create the memory, both for the n pointers, and what those n pointers point to. Npp expects the caller of GETOPENFILENAMES to have done the allocation.
-
But again, thank you for your thoughts and willingness to help.
-
@PeterJones said:
when passing it the TCHAR**, it is the calling process’s responsibility to create the memory, both for the n pointers, and what those n pointers point to. Npp expects the caller of GETOPENFILENAMES to have done the allocation.
Ahh, I forgot the
and what those n pointers point to
part, now it makes sense. Congratulations! I stay tuned to see an external Perl instance to interact with Npp. -
@dinkumoil said in External SendMessage to Notepad++ for NPPM_GETOPENFILENAMES and other TCHAR**:
I stay tuned to see an external Perl instance to interact with Npp.
I should start making forward progress much more quickly again. I hope to have the Notepad object by the end of the week, and maybe the scintilla object by next week.
Except callbacks. Without the intimacy of Npp->plugin, I don’t think callbacks will be able to be registered. But I’ll try that after I’m done with the rest of the NPP and SCINTILLA object code.
My long-term plan, after I get as much as I can done externally, is to have a very simple plugin interface, to give me visibility to the few things I cannot get externally, and allow either version for Perl users – if all they want is some simple remote control, then they can just download the module from CPAN. If they want full-fledged plugin, with callbacks and a menu-system for running the scripts inside Notepad++, then they will also need the plugin (though I still haven’t decided whether I’ll follow @Ekopalypse and compile my own matching perl, embedding it in the plugin, or whether the plugin will just ask for the path-to-perl.exe when being configured).
-
@PeterJones said in External SendMessage to Notepad++ for NPPM_GETOPENFILENAMES and other TCHAR**:
I hope to have the Notepad object by the end of the week, and maybe the scintilla object by next week
Three months later… Yeah. I was only a week or two late on the Notepad object, though I didn’t announce it. And I’ve made significant progress on the Scintilla object, but there’s still a lot to go: I think I’m about halfway. (I’m using some of Perl’s AUTOLOAD tricks to generate some of the wrapper functions at-request rather than having to manually define hundreds of wrappers for individual messages, so one piece of code can be re-used to wrap dozens or hundreds of messages automatically; if my assumptions about which groups of messages should use the same wrapping style, I think I’ve got about 300-400 messages that should work, with another 300-400 to go; but those assumptions could be wrong; I’m tracking my progress in https://github.com/pryrt/Win32-Mechanize-NotepadPlusPlus/blob/master/debug/sortSciMsgs.txt.)
But it is to the point where someone with a lot of Perl experience ( @Michael-Vincent : hint, hint) should be able to pick it up, look at my docs and the test suite (which gives some example idioms), and try running various functions, and giving me feedback. I believe the Notepad object is good enough for a production release; the Scintilla methods still have a way to go, but I’d like alpha-testers/feedback to see whether my bulk wrapper-generator is working (ie, if the other methods I’ve grouped together really work correctly; I’ve tested one or two functions from each “completed” group, but there is the possibility that the ones I’ve grouped aren’t quite as similar as I thought).
Anyway, for those who are interested, you can grab the source code from https://github.com/pryrt/Win32-Mechanize-NotepadPlusPlus (and then either install the Perl module, or just set your Perl library search path to include the lib directory from that repo)
Feedback can go here, or as issues if you think it’s something worth tracking
-
Wow, I made drastic progress today. It turned out most of those last hundreds of messages were just the simplest mapping from wrapper to Scintilla message.
There now remain a few messages that use various Scintilla structures (Sci_RangeToFormat, Sci_TextRange, cell), and a few more that will require manual coding for other reasons, to correctly map arguments from the method to the message.
And I probably need to audit things, because I found that for a few methods, my regex which had mapped the old PythonScript v1.4 docs (which I was starting with) to the Scintilla message didn’t quite get things right; so I need to make sure I don’t have methods mapped to the completely wrong message; and also need to check that I’m not missing important methods from newer releases.
But I think the bulk of the methods that can be handled by AUTOLOAD are done.
Except for the special structures, and anything regarding callbacks and the like (which aren’t going to be implemented in the first release), I think it should be reasonably usable at this point.
This morning, I was thinking this was more of an alpha-test situation. Now I’m thinking it’s really more of a beta test. Cool. :-)
-
@PeterJones,
from the github repo it seems that you do not use the iface file.
Any reason for that? -
@Ekopalypse said in External SendMessage to Notepad++ for NPPM_GETOPENFILENAMES and other TCHAR**:
Any reason for that?
Yes. Mostly, because I didn’t know it contained much the same information as Scintilla.h. And I already had my script to convert from
.h
to a Perl data structure, so even if I had known it was there, I probably would have preferred the.h
.