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

I have a C function that looks like:
int myfunction(char* error_string)

This function returns an error description in error_string if there is an error. I want to call this function from my Perl code but I'm not sure how to get the string back correctly. The following test code seems to work (sort of):

my $error = " "; mylib::myfunction($error); print $error;

This works as long as the string being returned is shorter than the number of spaces in the initialization of $error (10 spaces in this case), however any additional spaces in $error will still be printed after the error. For example, if the error returned is "Error 1", "Error 1   " will be printed with 3 spaces at the end. If the error returned is longer than 10 characters, the additional characters are truncated. I'm assuming this is becuase Perl counts the characters in a string instead of using null termination. Is there any way I can get Perl to "recount" the length of the string after returning from the function? I suppose I could search for the end of the string but it seems like there should be a better way to do this. Also, is there a better way to define the string I want to pass in to the function? The actual string needs to be 400 characters long and I'm not sure how to specify that I want a string with 400 characters. I suppose I could use a loop or the x operator to fill the string with spaces or some other character, but is there a better way to just allocate space for 400 characters?

Replies are listed 'Best First'.
Re: Passing a string from C to Perl
by Zaxo (Archbishop) on Sep 07, 2001 at 04:14 UTC

    In your petition you touch on one concern without recognising the more serious side of it. There is a design problem with your C function. It has a buffer overflow waiting to happen. Whether it's exploitable depends on the rest of your code, but a too-long error message may (depending on details of your system malloc() and cc ) cause segfaults, memory corruption, stack smashes, or trouble at work.

    If the C function is declared as : int myfunction(char** errorstring);you can assign *errorstring within the function after mallocing to fit and copying your string to the new buffer.

    I regret that I don't yet know enough xs and perlguts to advise you on what C constructions are most convenient for perl.

    After Compline,
    Zaxo

Re: Passing a string from C to Perl
by stefp (Vicar) on Sep 07, 2001 at 04:32 UTC
    As you noted yourself, your method is a recipe for disaster by buffer overflow.

    You should use the package Inline. The following example is straight from the Inline::C-Cookbook. It shows how it is easy it is to pass strings back and forth between C and Perl. The part of the example that interests you is getting a string of variable size from C to perl. This is done by newSVpvf() that copies a C-string formatted a la sprintf into a mortal perl-string. This means you don't have to care about allocation and deallocation.

    
    print greeting('Ingy')
    
    use Inline C => <<'END_OF_C_CODE';
    
          SV* greeting(SV* sv_name) {
             return (newSVpvf("Hello %s!\n", SvPV(sv_name, PL_na)));
          }
    
    END_OF_C_CODE
    

    -- stefp

Re: Passing a string from C to Perl
by John M. Dlugosz (Monsignor) on Sep 07, 2001 at 03:40 UTC
    Yes, I have the same issue when getting strings back using the Win32::API module. I allocate room, pass it as a parameter, and then the secret is to chop off the string at the first \0 character. Perl doesn't see it as special so it prints the whole length. The C code marked the actual end with a \0. So try something like s/\0.*$//. Most cases I get the actual length back too and just truncate to that known length.

    The x to make a long buffer works for me. —John

Re: Passing a string from C to Perl
by jlongino (Parson) on Sep 07, 2001 at 03:29 UTC
    I haven't done much in the way of c/perl, but couldn't you just use

    $error = " " x 400;

    Although I'm sure there must be something more elegant.

    @a=split??,'just lose the ego and get involved!';
    for(split??,'afqtw{|~'){print $a[ord($_)-97]}
Re: Passing a string from C to Perl
by Anonymous Monk on Sep 07, 2001 at 20:02 UTC
    One thing I forgot to mention is that the C function I am calling is part of a C library that was written by others. I do have some ability to request changes in the interface, however. If I were to use inline C Code is there any way to link that in with the rest of my C library? I realize that buffer overflow could be a problem, however I'm not sure what can be done about it from the Perl code. I tried passing a pointer to a pointer to the C code as suggested, but had problem getting the .xs file to accept that. I think the easiest way to handle the problem is to make sure to check the length of the error string before copying it into the buffer.