External SendMessage to Notepad++ for NPPM_GETOPENFILENAMES and other TCHAR**



  • Community,

    As I started talking about a couple months ago, and mentioned recently in … Send Messages and Delphi, 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, with the TCHAR** technique and this example of a struct, I think I should be able to generalize it.)

    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 hopefully 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.html#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_GETCURRENTBUFFERID, 0, 0 );
    
    # I can read a single string in the LPARAM
    print "getstr = ", my $getstr = SendMessage_getUcs2le( $hwnd, NPPM_GETFULLPATHFROMBUFFERID, $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;   # return 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_GETNBOPENFILES , 0 , 0 );
    
        # allocate remote memory for the n pointers, 8 bytes per pointer
        my $tcharpp = AllocateVirtualBuffer( $hwnd, $nFiles*$Config{ptrsize} ); #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 .. $nFiles;
    
        # 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 maybe 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-plus-plus.org/index.php/Messages_And_Notifications
        #   wParam = [out] TCHAR ** fileNames
        #   lParam = [in] int nbFile
        print "SendMessage status: ", my $ret = SendMessage( $hwnd, NPPM_GETOPENFILENAMES, $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: (See next post – this post was too long)

    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:



  • 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.



  • @PeterJones

    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?

    1. Your process has to allocate memory in the address space of Npp via VirtualAllocEx that can hold a pointer.
    2. You send a pointer to this memory using SendMessage to Npp.
    3. 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).
    4. 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.



  • @PeterJones

    Addition: To solve the problem you need one of the following:

    1. A COM or ActiveX interface for Notepad++ that allows an external process to interact with it.
    2. 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)

    @dinkumoil ,

    1. 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; the pack 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 that AllocateVirtualBuffer -> 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).