Search code examples
rakunativecall

NativeCall Struct which contains Pointer


I have the following Struct :

typedef struct _info{
  DWORD myInfo;
  BYTE  *pInfo;
  LPWSTR ExtData;

} Info;

I represented this struct using NativeCall thus:

class Info is repr('CStruct') {
    has int32 $.myInfo;
    has Pointer[int8] $.pInfo ; 
    has Pointer[int16] $.ExtData;
}

Is this representation okay? How can I access and set the data which is pointed to by $.pInfo?


Solution

  • The representation seems ok to me... Given this invented C library: (please don't pick on my C foo - its beside the point)

    #include <stdlib.h>
    #include <stdio.h>
    #include <string.h>
    
    typedef unsigned long int DWORD;
    typedef unsigned char BYTE;
    typedef char * LPWSTR;
    
    typedef struct _info{
      DWORD   myInfo;
      BYTE   *pInfo;
      LPWSTR  ExtData;
    } Info;
    
    
    Info* create_info();
    void  display_info();
    void  double_info(Info*);
    
    Info*
    create_info() {
        DWORD init_myinfo    = 2016;
        BYTE  init_pinfo     = 37;
        char  init_ExtData[] = "Hello World";
    
        Info *new_info = malloc(sizeof(Info));
        if (new_info == NULL) {
            printf( "Memory alloc failed\n" );
            exit(1);
        }
    
        new_info->myInfo = init_myinfo;
    
        BYTE *pinfo = malloc(sizeof(BYTE));
        *pinfo = init_pinfo;
        new_info->pInfo = pinfo;
    
        char *ext_data = malloc(sizeof(init_ExtData));
        strcpy(ext_data, init_ExtData);
        new_info->ExtData = ext_data;
    
        return new_info;
    }
    
    void
    display_info(Info *inf) {
        printf("myInfo: %lu\n", inf->myInfo);
        printf("pInfo:  %i\n", *inf->pInfo);
        printf("ExtData: %s\n", inf->ExtData);
    }
    
    void
    double_info(Info *inf) {
    
        inf->myInfo *=  2;
    
        if ( *(inf->pInfo) < 128 )
            *(inf->pInfo) *= 2;
    
        int extdata_len = strlen(inf->ExtData);
        char *tmp_extdata = malloc(extdata_len * 2 + 2);
        strncpy(tmp_extdata, inf->ExtData, extdata_len);
        tmp_extdata[extdata_len] = '+';
        strcpy(&tmp_extdata[extdata_len+1], inf->ExtData);
        inf->ExtData = tmp_extdata;
    }
    

    then your Raku struct definition using NativeCall more or less works:

    #!/usr/bin/env raku
    use NativeCall;
    
    class Info is repr('CStruct') {
        has int32          $.myInfo;
        has Pointer[int8]  $.pInfo ;
        has Pointer[Str]   $.ExtData;
    
        method Str {
          qq:to/END HERE/;
          myInfo: $!myInfo
          pInfo: { $!pInfo.deref }
          ExtData: { $!ExtData.deref }
          END HERE
        }
    }
    
    our sub create_info()  returns Info is native('pinfo') { * }
    our sub display_info(Info) is native('pinfo') { * }
    our sub double_info(Info is rw) is native('pinfo') { * }
    
    my Info $inf = create_info();
    say 'Displaying $inf after calling create_info()';
    display_info $inf;
    
    double_info $inf;
    say 'Displaying $inf after calling double_info()';
    display_info $inf;
    
    say 'Displaying $inf by calling attribute methods on Raku object';
    say "myInfo: $inf.myInfo()";
    say "pInfo: $inf.pInfo.deref()";
    say "ExtData: $inf.ExtData.deref()";
    
    say 'Displaying $inf by stringifying Raku object';
    say "$inf";
    exit 0;
    

    A run of this produces;

    Displaying $inf after calling create_info()
    myInfo: 2016
    pInfo:  37
    ExtData: Hello World
    Displaying $inf after calling double_info()
    myInfo: 4032
    pInfo:  74
    ExtData: Hello World+Hello World
    Displaying $inf by calling attribute methods on Raku object
    myInfo: 4032
    pInfo: 74
    ExtData: Hello World+Hello World
    Displaying $inf by stringifying Raku object
    myInfo: 4032
    pInfo: 74
    ExtData: Hello World+Hello World
    

    Alternatively, one can hide more of the 'glue' inside the Raku class;

    #!/usr/bin/env raku
    use NativeCall;
    
    class Info is repr('CStruct') {
        has int32          $.myInfo is rw ;
        has Pointer[int8]  $!pInfo ;
        has Pointer[Str]   $!ExtData ;
    
        my sub create_info() returns Info is native('pinfo') { * }
        my sub display_info(Info)         is native('pinfo') { * }
        my sub double_info(Info is rw)    is native('pinfo') { * }
    
        method new     {  create_info()      }
        method display {  display_info(self) }
        method double  {  double_info(self)  }
    
        method pInfo   {  $!pInfo.deref      }
        method ExtData {  $!ExtData.deref    }
    
        method Str {
            qq:to/END HERE/;
            myInfo: {  self.myInfo  }
            pInfo: {   self.pInfo   }
            ExtData: { self.ExtData }
            END HERE
        }
    }
    
    my Info $inf .= new;
    say 'Displaying $inf after calling .new';
    $inf.display ;
    
    $inf.double ;
    say 'Displaying $inf after calling .double';
    $inf.display ;
    
    say 'Displaying $inf by calling attribute methods on Raku object';
    say "myInfo: $inf.myInfo()";
    say "pInfo: $inf.pInfo()";
    say "ExtData: $inf.ExtData()";
    
    $inf.myInfo = 12046 ;
    say 'Displaying $inf by stringifying Raku object';
    say "$inf";
    exit 0;
    

    ... a much cleaner look. It similarly produces;

    Displaying $inf after calling .new
    myInfo: 2016
    pInfo:  37
    ExtData: Hello World
    Displaying $inf after calling .double
    myInfo: 4032
    pInfo:  74
    ExtData: Hello World+Hello World
    Displaying $inf by calling attribute methods on Raku object
    myInfo: 4032
    pInfo: 74
    ExtData: Hello World+Hello World
    Displaying $inf by stringifying Raku object
    myInfo: 12046
    pInfo: 74
    ExtData: Hello World+Hello World
    

    Notes:

    (1) You call the .deref() method on attributes of type Pointer to get the actual data being pointed to.

    (2) The NativeCall documentation has to be read more than once ;-)

    (3) No matter what I tried, I couldn't modify the data being referenced via pointers - I kept getting "Can't modify immutable data".

    (4) YMMV