Beefy Boxes and Bandwidth Generously Provided by pair Networks
P is for Practical
 
PerlMonks  

comment on

( #3333=superdoc: print w/replies, xml ) Need Help??

Monks,

As I started talking about a couple months ago in the Notepad++ Community forum, and mentioned recently in another post there, I'm working on a library that will allow an external perl instance (ie, not via a plugin) to use Win32::GuiTest::SendMessage to control Notepad++.

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 , &amp; 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), &amp;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, &amp;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), &amp;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, &amp;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, &amp;charp_item, siz +eof( charp_item ), &amp;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, &amp;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, &amp;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, &amp;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, &amp;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, &amp;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, &amp;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, &amp;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, &amp;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, &amp;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, &amp;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; }

Unfortunately, I'm out of ideas, so I'm asking in both the Notepad++ Community and in perlmonks. If there's someone that can show an external C example using VirtualAllocEx or an external Perl script using Win32::GuiTest::AllocateVirtualBuffer, and successfully talk with Notepad++'s NPP_GETOPENFILENAMES message, I'd appreciate it.

(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.)

crosslinks:

edit: fixed link once I had the post actually made in the Community. Sorry for the two minutes when I didn't have a URL yet. I tried a stealth edit, but you guys started reading too fast. :-)


In reply to [SOLVED] Win32::GuiTest::SendMessage/AllocateVirtualMemory and TCHAR** by pryrt

Title:
Use:  <p> text here (a paragraph) </p>
and:  <code> code here </code>
to format your post; it's "PerlMonks-approved HTML":



  • Posts are HTML formatted. Put <p> </p> tags around your paragraphs. Put <code> </code> tags around your code and data!
  • Titles consisting of a single word are discouraged, and in most cases are disallowed outright.
  • Read Where should I post X? if you're not absolutely sure you're posting in the right place.
  • Please read these before you post! —
  • Posts may use any of the Perl Monks Approved HTML tags:
    a, abbr, b, big, blockquote, br, caption, center, col, colgroup, dd, del, div, dl, dt, em, font, h1, h2, h3, h4, h5, h6, hr, i, ins, li, ol, p, pre, readmore, small, span, spoiler, strike, strong, sub, sup, table, tbody, td, tfoot, th, thead, tr, tt, u, ul, wbr
  • You may need to use entities for some characters, as follows. (Exception: Within code tags, you can put the characters literally.)
            For:     Use:
    & &amp;
    < &lt;
    > &gt;
    [ &#91;
    ] &#93;
  • Link using PerlMonks shortcuts! What shortcuts can I use for linking?
  • See Writeup Formatting Tips and other pages linked from there for more info.
  • Log In?
    Username:
    Password:

    What's my password?
    Create A New User
    Chatterbox?
    and the web crawler heard nothing...

    How do I use this? | Other CB clients
    Other Users?
    Others exploiting the Monastery: (4)
    As of 2020-05-26 23:48 GMT
    Sections?
    Information?
    Find Nodes?
    Leftovers?
      Voting Booth?
      If programming languages were movie genres, Perl would be:















      Results (152 votes). Check out past polls.

      Notices?