Beefy Boxes and Bandwidth Generously Provided by pair Networks
Problems? Is your data what you think it is?
 
PerlMonks  

Win32::API Memory Exception with GetCommandLine() (which returns a static string)

by Wyrdweaver (Beadle)
on Jul 05, 2007 at 23:55 UTC ( #625151=perlquestion: print w/replies, xml ) Need Help??

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

Monks,

I'm trying to reparse a Win32 command line using the Win32::API to retrieve it. I've composed the function to get the command line from the examples within Win32::API, but, depending on padding of the code itself, I get an occasional GPF. I believe that Perl is trying to garbage collect the returned "static" string. Any thoughts on this or how to get around it?

I can't find any CPAN modules that retrieve the Win32 command line. So, I'm trying to roll my own. Would like to avoid going all the way to XS as I've never delved into that realm (although I have a working compilation environment with mingw).

Thanks, in advance.

Note: save the following code as "t-getCL.bat" and try "t-getCL a". For me, $string prints and then I get a GPF just before program completion. The GPF is sensitive to code size and less padding at the end of the script can make the problem go away.

Note#2: It's a .bat file to deal correctly with I/O redirection on XP.

@rem = '--*-Perl-*-- @echo off if "%OS%" == "Windows_NT" goto WinNT perl -x -S "%0" %1 %2 %3 %4 %5 %6 %7 %8 %9 goto endofperl :WinNT perl -x -S %0 %* if NOT "%COMSPEC%" == "%SystemRoot%\system32\cmd.exe" goto endofperl if %errorlevel% == 9009 echo You do not have Perl in your PATH. if errorlevel 1 goto script_failed_so_exit_with_non_zero_val 2>nul goto endofperl @rem '; #!/usr/bin/perl -w #line 15 use Win32::API; Win32::API->Import("kernel32", "LPTSTR GetCommandLine()"); my $string = pack("Z*", GetCommandLine()); print "string[".length($string)."] = '$string'\n"; # ------ padding ----------------------------------------------------- +--------------------------------- __END__ :endofperl
  • Comment on Win32::API Memory Exception with GetCommandLine() (which returns a static string)
  • Download Code

Replies are listed 'Best First'.
Re: Win32::API Memory Exception with GetCommandLine() (which returns a static string)
by BrowserUk (Pope) on Jul 06, 2007 at 00:49 UTC

    Which versions of perl/Win32::API are you using?

    I cannot reproduce your problem using AS811(5.8.6)/0.41

    C:\test>junk a string[18] = 'perl -x -S junk a ' C:\test>junk a b c d e f g h string[32] = 'perl -x -S junk a b c d e f g h ' C:\test>type junk.bat @rem = '--*-Perl-*-- @echo off if "%OS%" == "Windows_NT" goto WinNT perl -x -S "%0" %1 %2 %3 %4 %5 %6 %7 %8 %9 goto endofperl :WinNT perl -x -S %0 %* if NOT "%COMSPEC%" == "%SystemRoot%\system32\cmd.exe" goto endofperl if %errorlevel% == 9009 echo You do not have Perl in your PATH. if errorlevel 1 goto script_failed_so_exit_with_non_zero_val 2>nul goto endofperl @rem '; #!/usr/bin/perl -w #line 15 use Win32::API; Win32::API->Import("kernel32", "LPTSTR GetCommandLine()"); my $string = pack("Z*", GetCommandLine()); print "string[".length($string)."] = '$string'\n"; __END__ :endofperl C:\test>perl -MWin32::API -wle"print $Win32::API::VERSION" 0.41

    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.
      I'm using the most recent version: 0.46
      >perl -MWin32::API -wle"print $Win32::API::VERSION" 0.46

      It's a hit or miss problem as most memory read/write exceptions seem to be...

      For me, a combination of padding and differing program name lengths gives me the GPF. Try extending the name of the script by a character at a time. "junk-kk" causes it for me with your non-padded version.

      Do you think that garbage collection of a returned static string is incorrect?

      - W

        Nothing I've tried has caused so much as a murmer of trouble:

        C:\test>junkjunkjunkjunkjunkjunkxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.bat string[178] = 'perl -x -S junkjunkjunkjunkjunkjunkxxxxxxxxxxxxxxxxxxxx +xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.bat '

        I'll have a go at building the latest Win32::API later today. You failed to mention which version of perl?

        Do you think that garbage collection of a returned static string is incorrect?

        I don't know for sure, but looking at the source code, it appears to do the right thing by copying the return value into a perl SV before returning it:

        case T_POINTER: ApiFunctionPointer = (ApiPointer *) ApiFunction; pReturn = ApiFunctionPointer(); /* #### only works with strings... #### */ cReturn = (char *) safemalloc(strlen(pReturn)); strcpy(cReturn, pReturn); break; ... XSRETURN_PV(cReturn);

        Which looks right as far as my nascent XS skills allow me to tell.

        The one thing I didn't see was any attempt to free up the memory for the string passed--which constitutes a memory leak if you call it huge numbers of times. The following script terminates having grown to 35MB. Not that there is any point in calling this particular call multiple times as the return will never change, but it might be significant for other apis. Presumably such returned strings should be freed using Memfree() or VirtualFree() though there is no mention of this in the docs.

        #!/usr/bin/perl -w use strict; use Win32::API; Win32::API->Import("kernel32", "LPTSTR GetCommandLine()"); printf "\r%d : %s\t", $_, GetCommandLine() for 1 .. 1e6;

        Do you get the same problems if you run the perl code directly rather than via the .bat wrapper?

        Also, why are you using pack in your code? And does it help if you remove it?

        (Incidently, why do you wrap it anyway?)


        Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
        "Science is about questioning the status quo. Questioning authority".
        In the absence of evidence, opinion is indistinguishable from prejudice.
Re: Win32::API Memory Exception with GetCommandLine() (which returns a static string)
by syphilis (Bishop) on Jul 06, 2007 at 04:01 UTC
    Hi Wyrdweaver,
    When I run that batch file (as 'yrt.bat') I get the following output, followed by a GPF:
    C:\_32\pscrpt\inline>yrt.bat string[21] = 'perl -x -S yrt.bat '
    Here's the same batch file using Inline::C (which you could install and use, since you have MinGW) instead of Win32::API:
    @rem = '--*-Perl-*-- @echo off if "%OS%" == "Windows_NT" goto WinNT perl -x -S "%0" %1 %2 %3 %4 %5 %6 %7 %8 %9 goto endofperl :WinNT perl -x -S %0 %* if NOT "%COMSPEC%" == "%SystemRoot%\system32\cmd.exe" goto endofperl if %errorlevel% == 9009 echo You do not have Perl in your PATH. if errorlevel 1 goto script_failed_so_exit_with_non_zero_val 2>nul goto endofperl @rem '; #!/usr/bin/perl -w #line 15 #use Inline C => Config => # BUILD_NOISY => 1; # See compilation output use Inline C => <<'EOC'; SV * wrap_GetCommandLine() { return newSVpv(GetCommandLine(), 0); } EOC my $string = wrap_GetCommandLine(); print "string[".length($string)."] = '$string'\n"; # ------ padding ----------------------------------------------------- +--------------------------------- __END__ :endofperl
    The first time you run that there's some compilation takes place.Subsequent runs of the batch file will not require any compilation unless the actual Inline::C code is changed, or the name of the batch file is changed. Running that batch file (as try.bat), I get:
    C:\_32\pscrpt\inline>try.bat string[20] = 'perl -x -S try.bat '
    This time there's no GPF - I don't know why try.bat reports one less trailing space than yrt.bat.

    Hope this helps.

    Cheers,
    Rob
    Update:Or, if you want to modularise it (which would make better sense) here's a very minimalistic approach:

      At the core of Win32::API are several small piece of assembler code:

      #if (defined(__BORLANDC__) && __BORLANDC__ >= 452) #define ASM_LOAD_EAX(param,type) { \ __asm { \ mov eax, type param ; \ push eax ; \ } /* MSVC compilers */ #elif defined _MSC_VER /* Disable warning about one missing macro parameter. TODO: How we define a macro with an optional (empty) parameter? + */ #pragma warning( disable : 4003 ) #define ASM_LOAD_EAX(param,type) { \ __asm { mov eax, type param }; \ __asm { push eax }; \ } /* GCC-MinGW Compiler */ #elif (defined(__GNUC__)) #define ASM_LOAD_EAX(param,...) asm ("push %0" :: "g" (param)); #endif ... ASM_LOAD_EAX(pParam, dword ptr); ## many similar ... #if (defined(_MSC_VER) || defined(BORLANDC)) __asm { mov eax, dword ptr [dParam + 4] ; push eax ; mov eax, dword ptr [dParam] ; push eax ; }; #elif (defined(__GNUC__)) /* probably uglier than necessary, but works */ asm ("pushl %0":: "g" (((unsigned int*)&dParam)[1])); asm ("pushl %0":: "g" (((unsigned int*)&dParam)[0])); /* { int idc; printf ("dParam = "); for (idc = 0; idc < sizeof(dParam); idc++) { printf(" %2.2x",((unsigned char*)&dParam)[idc]); } printf(" %f\n", dParam); } */ #endif

      One has to suspect, if the OP confirms he is using a mingw built version of Perl that the problem lies in there somewhere. Does mingw build using similar calling conventions to MSC? That is the PASCAL calling convention for system apis.


      Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
      "Science is about questioning the status quo. Questioning authority".
      In the absence of evidence, opinion is indistinguishable from prejudice.
        One has to suspect, if the OP confirms he is using a mingw built version of Perl that the problem lies in there somewhere

        There's a couple of different possibilities:
        1)It's MinGW-built perl using MinGW-built Win32::API;
        2)It's MinGW-built perl using VC-built Win32::API;

        I'm in the second category (with Win32-API-0.46). I also have Win32-API-0.46 on ActiveState perl (build 819) - it is exactly the same binary as I'm running on my MinGW-built perl, but there's no GPF with ActivePerl ... which leads me to the conclusion that the problem might lie outside Win32::API.

        I still get the same length discrepancy on ActivePerl - ie Inline's 20 vs Win32::API's 21.

        Cheers,
        Rob

      Thanks for the suggestion of "Inline::C". I hadn't seen that module before now. Unfortunately it's seems to be brittle on my system (failing because it didn't quote one of the Win32 directories with embedded spaces for gcc). However, it does produce a rough XS that I'll probably use quite a bit in the future.

      I'll probably end up using XS. And so, I should thank you again for showing me the basic XS template for this function.

        failing because it didn't quote one of the Win32 directories with embedded spaces for gcc

        Yes, there are problems with Inline::C and directory names that contain spaces. The problems are not insurmountable, but the best fix is to not install anything into a directory whose name contains any spaces.

        For the generation of the XS file and the Makefile.PL I just used InlineX::C2XS - ie, I placed a file named GetCommandLine.c (which contained just the solitary Inline::C function) in the ./src folder and ran
        perl -MInlineX::C2XS -e "InlineX::C2XS::c2xs('Win32::GetCommandLine',' +Win32::GetCommandline',{'WRITE_MAKEFILE_PL'=>1,'VERSION'=>0.01})"
        That creates the Makefile.PL and GetCommandLine.xs in the cwd. I then spent the next 15 minutes writing by hand GetCommandLine.pm (which I kept stuffing up) ... so I've now modified InlineX::C2XS::c2xs() to accept a 'WRITE_PM'=>1 argument that writes that .pm stub file as well. (This will be in the next CPAN release.)

        Note that there's more than one way to write an XS file (oO ... deja vu :-). The rendition that I posted is the way that Inline::C (which is used by InlineX::C2XS) autogenerated it.

        Cheers,
        Rob

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://625151]
Approved by GrandFather
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others browsing the Monastery: (1)
As of 2021-10-19 05:30 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    My first memorable Perl project was:







    Results (76 votes). Check out past polls.

    Notices?