Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl-Sensitive Sunglasses
 
PerlMonks  

[SOLVED] Win32::GuiTest::SendMessage/AllocateVirtualMemory and TCHAR**

by pryrt (Prior)
on Sep 23, 2019 at 15:56 UTC ( #11106573=perlquestion: print w/replies, xml ) Need Help??

pryrt has asked for the wisdom of the Perl Monks concerning the following question:

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

Replies are listed 'Best First'.
Re: Win32::GuiTest::SendMessage/AllocateVirtualMemory and TCHAR**
by vr (Curate) on Sep 23, 2019 at 18:02 UTC

    But you were almost there:

    # print "SendMessage status: ", my $ret = SendMessage( $hwnd, NPPM_G +ETOPENFILENAMES, $tcharpp, 0); print "SendMessage status: ", my $ret = SendMessage( $hwnd, NPPM_G +ETOPENFILENAMES, $tcharpp->{ptr}, $nFiles);

      correction: On reread, I saw the $nFiles also. With that, it works! Thanks!


      It's annoying that I missed that in this SSCCE version... At one point, I know I had that right. Unfortunately, even with that fix, I still get all NULLs:

      ... print "SendMessage status: ", my $ret = SendMessage( $hwnd, NPPM_G +ETOPENFILENAMES, $tcharpp->{ptr}, 0); use Data::Dumper; $Data::Dumper::Useqq++; for my $text_buf ( @strBufs ) { my $rd = ReadFromVirtualBuffer( $text_buf, 1024 ); printf "read '%s'\n", Dumper Encode::decode('ucs2-le', $rd); # they all turn out as "\0" x 1024 } ... __END__ bufid = 2238557922048 getstr = C:\usr\local\share\GitHubSvn\Win32-Mechanize-NotepadPlusPlus\ +debug\tcharpp.pl nOpenFiles = 5 SendMessage status: 0 read '$VAR1 = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ +0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0 +\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ +0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0 +\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ +0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0 +\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ +0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0 +\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ +0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0 +\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ +0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0 +\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ +0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0 +\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ +0\0"; ' read '$VAR1 = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ +0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0 +\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ +0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0 +\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ +0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0 +\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ +0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0 +\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ +0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0 +\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ +0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0 +\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ +0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0 +\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ +0\0"; ' read '$VAR1 = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ +0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0 +\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ +0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0 +\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ +0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0 +\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ +0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0 +\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ +0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0 +\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ +0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0 +\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ +0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0 +\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ +0\0"; ' read '$VAR1 = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ +0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0 +\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ +0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0 +\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ +0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0 +\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ +0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0 +\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ +0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0 +\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ +0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0 +\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ +0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0 +\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ +0\0"; ' read '$VAR1 = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ +0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0 +\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ +0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0 +\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ +0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0 +\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ +0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0 +\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ +0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0 +\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ +0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0 +\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ +0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0 +\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ +0\0";

        It's such a wonderful sensation of enlightenment and finalization when someone (in this case vr) comes along and points out such a (seemingly) simple thing :D

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://11106573]
Approved by stevieb
Front-paged by Corion
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others browsing the Monastery: (6)
As of 2020-06-01 13:48 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found

    Notices?