PerlFFI / FFI-C

Structured data types for FFI
6 stars 4 forks source link

Add string class FFI::C::String + FFI::C::StringDef #9

Open plicease opened 4 years ago

plicease commented 4 years ago

This would wrap around a pointer to a null terminated C style string. Some integration with opaque member on a struct/union would be helpful.

plicease commented 1 year ago

Update: I still haven't had time to circle back to this, but the fundamental challenge is that I am not sure exactly how to do the ownership of this object type if it is going to be used as a member of an FFI::C::Struct (or FFI::C::Union, which could be quite complicated). The pattern used elsewhere in FFI::C is that if the pointer was allocated in C space it should not be free'd automatically, but it was allocated in Perl space it should be free'd when the object falls out of scope. This is easy to deal with for nested data, but more complicated with pointers. The eventual goal here is to have an FFI::C::String that could be used when defining members of an FFI::C::Struct or FFI::C::Array.

The work around for now is to use an opaque pointer and write a accessor method that converts from/to opaque as appropriate: (caveat example code has not been tested for syntax or anything like that)

package Foo {
  FFI::C->struct([
    _string_ptr   => 'opaque',
  ]);

  # read-only, for data allocated and free'd in C space
  sub string ($self)
  {
    $ffi->cast('opaque', 'string', $self->_string_ptr);
  }
}
package Foo {

  use FFI::Platypus::Memory qw( free strdup );

  FFI::C->struct([
    _string_ptr   => 'opaque',
  ]);

  # read-write, for data allocated and free'd in Perl space
  sub string ($self, $new_value=undef)
  {
    if(defined $new_value) {
      if(defined $self->_string_ptr) {
        free $self->_string_ptr;
      }
      $self->_string_ptr(strdup $new_value);
    }
    $ffi->cast('opaque', 'string', $self->_string_ptr);
  }

  sub string_free ($self)
  {
    if(defined $self->_string_ptr) {
      free $self->_string_ptr;
      $self->_string_ptr(undef);
    }
  }
}

Another option is to use FFI::Platypus::Record, which has both string_ro and string_rw types, where the assumption is that for string_ro C owns the data and is therefore read-only and not free'd by Perl, and that for string_rw Perl owns the data and is therefore read-write and IS free'd by Perl when the record falls out of scope. We could in theory do something similar for FFI::C but that doesn't seem like the right approach for FFI::C

Whatever approach we eventually decide on, we have to consider how to handle pointers to strings in Unions. I don't think there is an obvious automatic way to do it, and we might just have to disallow string pointers inside of a union. If you need that capability you can still define an opaque pointer member and manage the reads by casting and allocation manually.

None of this applies to a fixed length string, which is nested inside of a struct, this sort of data structure is basically the same as a fixed array of char.