Beefy Boxes and Bandwidth Generously Provided by pair Networks
Come for the quick hacks, stay for the epiphanies.
 
PerlMonks  

Re^3: Perl XS binding to a struct with an array of chars*

by syphilis (Archbishop)
on Nov 24, 2022 at 01:10 UTC ( [id://11148345]=note: print w/replies, xml ) Need Help??


in reply to Re^2: Perl XS binding to a struct with an array of chars*
in thread Perl XS binding to a struct with an array of chars*

Normally a type has a fixed size that is known at compile time and sizeof() works just fine. That is not true in this case.

Well, I think that the struct, as presented in the original post, does have a definite size (of 16 bytes).
AFAIK specifying char *str[1] is equivalent to char * str.
I just went with the spec provided, even though it looked rather odd.

It did occur to me that the OP might have intended char ** string_array (as you've suggested), and I probably should have pressed MaxPerl about that.
But, either way, the struct has a definite size - and we can assign memory to it based on that size (which is 16 bytes, on my Windows 11 64-bit system).

I've no experience with structs that might require varying amounts of memory that can't be known until runtime. (I don't assume that such cases never arise.)

I expect that the OP's strings have been created separately.
Therefore, the number and size of them has no impact on the struct's memory allocation - because the struct just takes a pointer to the array of strings, no matter how large that array is.

Cheers,
Rob

Replies are listed 'Best First'.
Re^4: Perl XS binding to a struct with an array of chars*
by GrandFather (Saint) on Nov 24, 2022 at 01:45 UTC
    structs that might require varying amounts of memory

    There is a somewhat common use case where a 0 length or length 1 array is used at the end of a struct so that a variable amount of payload can be carried around by the struct. The technique is hardly ever needed in C++ land, there are better ways to handle the problem.

    One interesting use case in C land is to provide data hiding. Use a common header struct the provides high level management information, then a variable sized "tail" that gets cast to the struct type that represents the hidden data. That makes the payload data opaque to client code and avoids making two allocations when creating an instance of the struct. Kinda a poor man's C++ really!

    Optimising for fewest key strokes only makes sense transmitting to Pluto or beyond
Re^4: Perl XS binding to a struct with an array of chars*
by Marshall (Canon) on Nov 24, 2022 at 03:31 UTC
    Yes, this is a weird duck. Quack. Quack.
    If I play further with this, I am tempted to see if char* str[]; is a valid syntax or not - having the blank dimension would certainly be nice as a "heads up". But yeah, from the OP's code, he expects the dynamic array to extend past the "starter header" as a continuation of the single element defined in the structure.

    Some OP code:

    for (index = 0; index <= count; index++) { tmp = *av_fetch(val_arr,index,0); string = SvPVutf8(tmp,len); message->str[index] = savepvn(string,len); }
    Of course since the whole purpose of XS is to write C code, something like this would be more appropriate (untested):
    int n = count; char* p = &m.str; while (n--) { ....blah... *p++ = savepvn(string,len); }
    Using an index is a mess because this often will result in code that calculates index*sizeOfelement+arrayBase to get to the address. Having a pointer eliminates this calculation. Incrementing the pointer (p++) by sizeOfelement is very fast because that is a constant which is actually part of the machine instruction (not some number that is read from data memory). This instruction is typically called something like "add immediate to register". while (n--) runs very fast because there are special op codes that increment or decrement by one (this is much faster than incrementing the pointer because the instruction is shorter). There are also special op codes that check for zero or non-zero.

    So far, I've learned some things that were new to me about XS. So this has been interesting. However, it is not at all clear that besides being a good intellectual exercise that this will result achieving a significant end goal. This is a lot of complication to make a clone of a Perl Array of Strings in a different format. For all I know, it could be "good enough" to leave the Perl data structures "as is" and write good C code to access the data for the intended but as of yet unstated purpose/application.

    To proceed any further, we'd need to know more about what this is being used for.
    Cheers,
    Marshall

      I am tempted to see if char* str[]; is a valid syntax or not

      I think it's valid, but it does make things tricky ... and maybe it's that trickiness that inspired this thread in the first place.
      It gets a lot easier if we're allowed to declare the struct as:
      struct _Edje_Message_String_Set { int count; AV * str; };
      Here's a demo using that very struct declaration.
      # struct.pl # use strict; use warnings; use Inline C => Config => BUILD_NOISY => 1, CLEAN_AFTER_BUILD => 0, USING => 'ParseRegExp', ; use Inline C => <<'EOC'; struct _Edje_Message_String_Set { int count; AV * str; }; typedef struct _Edje_Message_String_Set EdjeMessageStringSet; void struct_size(void) { printf("Size of _Edje_Message_String_Set struct: %d\n", sizeof(EdjeMessageStringSet) ); } EdjeMessageStringSet * _new(AV * val_arr) { EdjeMessageStringSet *message; /* int i; */ /* SV ** elem; */ Newx(message, 1, EdjeMessageStringSet); if(message == NULL) croak("Failed to allocate memory in _new function"); message->count = av_len(val_arr) + 1; message->str = val_arr; return message; } void _iterate(EdjeMessageStringSet * strs) { int i; SV ** elem; for(i = 0; i < strs->count; i++) { elem = av_fetch(strs->str, i, 0); printf("%s\n", SvPV_nolen(*elem)); } } void DESTROY(EdjeMessageStringSet * x) { Safefree(x); printf("destroyed _new EdjeMessageStringSet*\n"); } void foo(AV * arref) { EdjeMessageStringSet *m; m = _new(arref); _iterate(m); DESTROY(m); } EOC struct_size(); my @in = ("hello foo", "hello bar","hello world", "goodbye"); # The XSub foo() will create a new EdjeMessageStringSet object # using _new(), then pass that object to _iterate() which # prints out all of the strings contained in the object. # Finally, foo() calls DESTROY() which frees the memory that # was assigned to create the EdjeMessageStringSet object. foo(\@in);
      For me, that outputs:
      Size of _Edje_Message_String_Set struct: 16 hello foo hello bar hello world goodbye destroyed _new EdjeMessageStringSet*
      Cheers,
      Rob
        Once I start, I can't stop ;-)
        I still don't know how to deal with:
        struct _Edje_Message_String_Set { int count; char * str[]; };
        Is it possible to handle that ?

        However, for:
        struct _Edje_Message_String_Set { int count; char ** str; };
        I have:
        # struct_char.pl # use strict; use warnings; use Inline C => Config => BUILD_NOISY => 1, CLEAN_AFTER_BUILD => 0, USING => 'ParseRegExp', ; use Inline C => <<'EOC'; struct _Edje_Message_String_Set { int count; char ** str; }; typedef struct _Edje_Message_String_Set EdjeMessageStringSet; void struct_size(void) { printf("Size of _Edje_Message_String_Set struct: %d\n", sizeof(EdjeMessageStringSet) ); } EdjeMessageStringSet * _new(AV * val_arr) { EdjeMessageStringSet *message; int i; SV ** elem; Newx(message, 1, EdjeMessageStringSet); if(message == NULL) croak("Failed to allocate memory in _new function"); message->count = av_len(val_arr) + 1; Newx(message->str, message->count, char*); for(i = 0; i < message->count; i++) { elem = av_fetch(val_arr, i, 0); message->str[i] = SvPV_nolen(*elem); } return message; } void _iterate(EdjeMessageStringSet * strs) { int i; for(i = 0; i < strs->count; i++) { printf("%s\n", strs->str[i]); } } void DESTROY(EdjeMessageStringSet * x) { Safefree(x->str); printf("Safefreed EdjeMessageStringSet object->str\n"); Safefree(x); printf("Safefreed EdjeMessageStringSet object\n"); } void foo(AV * arref) { EdjeMessageStringSet *m; m = _new(arref); _iterate(m); DESTROY(m); } EOC struct_size(); my @in = ("hello foo", "hello bar","hello world", "goodbye", '1', '2', + '3'); # The XSub foo() will create a new EdjeMessageStringSet object # using _new(), then pass that object to _iterate() which # prints out all of the strings contained in the object. # Finally, foo() calls DESTROY() which frees the memory that # was assigned to create the EdjeMessageStringSet object. foo(\@in);
        Which (after compiling) outputs:
        Size of _Edje_Message_String_Set struct: 16 hello foo hello bar hello world goodbye 1 2 3 Safefreed EdjeMessageStringSet object->str Safefreed EdjeMessageStringSet object
        Cheers,
        Rob

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others admiring the Monastery: (5)
As of 2024-03-28 12:18 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found