Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add buffer and string classes #46

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -262,9 +262,12 @@ So-called "pass-by-value" is not and will not be supported. For
- [FFI::C](https://metacpan.org/pod/FFI::C)
- [FFI::C::Array](https://metacpan.org/pod/FFI::C::Array)
- [FFI::C::ArrayDef](https://metacpan.org/pod/FFI::C::ArrayDef)
- [FFI::C::ASCIIString](https://metacpan.org/pod/FFI::C::ASCIIString)
- [FFI::C::Buffer](https://metacpan.org/pod/FFI::C::Buffer)
- [FFI::C::Def](https://metacpan.org/pod/FFI::C::Def)
- [FFI::C::File](https://metacpan.org/pod/FFI::C::File)
- [FFI::C::PosixFile](https://metacpan.org/pod/FFI::C::PosixFile)
- [FFI::C::String](https://metacpan.org/pod/FFI::C::String)
- [FFI::C::Struct](https://metacpan.org/pod/FFI::C::Struct)
- [FFI::C::StructDef](https://metacpan.org/pod/FFI::C::StructDef)
- [FFI::C::Union](https://metacpan.org/pod/FFI::C::Union)
Expand Down
20 changes: 20 additions & 0 deletions examples/synopsis/ascii_string.pl
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use strict;
use warnings;
use FFI::Platypus;
use FFI::C::ASCIIString;

my $ffi = FFI::Platypus->new( api => 1, lib => [undef]);

$ffi->attach( puts => ['opaque'] => 'int' );

my $str = FFI::C::ASCIIString->new(1024);
$str->from_perl("Hello: ");

print "length = ", $str->strlen, "\n"; # prints 7

puts($str->ptr); # prints Hello:

$str->strcat("World!");

puts($str->ptr); # prints Hello: World!

34 changes: 34 additions & 0 deletions examples/synopsis/buffer.pl
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
use strict;
use warnings;
use FFI::Platypus;
use FFI::C::Buffer;

my $ffi = FFI::Platypus->new( api => 1, lib => [undef]);
my $open = $ffi->function( 'open' => [ 'string', 'int', 'mode_t' ] => 'int' );
my $read = $ffi->function( 'read' => [ 'int','opaque','size_t' ] => 'ssize_t' );
my $write = $ffi->function( 'write' => [ 'int','opaque','size_t' ] => 'ssize_t' );

my $buf1 = FFI::C::Buffer->new(\"Hello World!\n");

# send a buffer to C land as a const char * for it to read from
$write->call(1, $buf1->ptr, $buf1->buffer_size);

# open this script for read
my $fd = $open->call(__FILE__, 0, 0); # O_RDONLY

# allocate an uninitzlized buffer of 1024 bytes.
# we can reuse this over and over to avoid having
# to reallocate the memory.
my $buf2 = FFI::C::Buffer->new(1024);

while(1)
{
# send a buffer to C land as a const char * fro it to write to
my $count = $read->call($fd, $buf2->ptr, $buf2->buffer_size);

die "error reading into buffer" if $count < 0;

last if $count == 0;

$write->call(1, $buf2->ptr, $count);
}
6 changes: 6 additions & 0 deletions lib/FFI/C.pm
Original file line number Diff line number Diff line change
Expand Up @@ -261,12 +261,18 @@ So-called "pass-by-value" is not and will not be supported. For

=item L<FFI::C::ArrayDef>

=item L<FFI::C::ASCIIString>

=item L<FFI::C::Buffer>

=item L<FFI::C::Def>

=item L<FFI::C::File>

=item L<FFI::C::PosixFile>

=item L<FFI::C::String>

=item L<FFI::C::Struct>

=item L<FFI::C::StructDef>
Expand Down
216 changes: 216 additions & 0 deletions lib/FFI/C/ASCIIString.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
package FFI::C::ASCIIString;

use strict;
use warnings;
use Ref::Util qw( is_plain_scalarref is_ref );
use FFI::Platypus::Buffer 1.28 ();
use base qw( FFI::C::String );

# ABSTRACT: C string class for ASCII
# VERSION

=head1 SYNOPSIS

# EXAMPLE: examples/synopsis/ascii_string.pl

=head1 DESCRIPTION

This class represents a NULL terminated C ASCII string, which is common to many C APIs.
It inherits from L<FFI::C::String> and L<FFI::C::Buffer>, so you can use all of the
methods that those classes implement.

In particular, the amount of memory allocated for the string B<can> be more than initially
needed, which allows appending (C<strcat> below) to the end of the string. By default just
enough space is allocated to store the string, including its NULL termination.

This class endeavors to ensure the string contain only ASCII characters. If non-ASCII
characters are seen passing to or from C space then this class will throw an exception.

Typically the C<string> built-in type that comes with L<FFI::Platypus> will work just
fine for ASCII strings, without needing this class. Where this class may come in handy
is when you have to keep a C string around for multiple calls into C space, or passing
a string from one C API to another.

=head1 CONSTRUCTOR

=head2 new

my $str = FFI::C::ASCIIString->new($buffer_size);
my $str = FFI::C::ASCIIString->new(\$perl_string);

Creates a new NULL terminated string C string object.

The first form creates a new NULL terminated string C<""> with a buffer capacity of C<$buffer_size>.

The second form computes the buffer size from the provided C<$perl_string> and copies it to the
new C string. If the Perl string doesn't include the NULL termination it will be added to the
new C string. If there are non-ASCII characters in the C<$perl_string> then it will throw an exception.

=cut

sub new
{
my $class = shift;

if(@_ == 1)
{
if(is_plain_scalarref $_[0] && !is_ref ${$_[0]})
{
Carp::croak("Non ASCII characters found in string") if ${$_[0]} =~ /[^[:ascii:]]/;

return ${$_[0]} =~ /\0/
? $class->SUPER::new($_[0])
: $class->SUPER::new(\"${$_[0]}\0");
}
elsif(!is_ref $_[0])
{
my $self = $class->SUPER::new(@_);
$self->from_perl("\0");
return $self;
}
else
{
return $class->SUPER::new(@_);
}
}
else
{
return $class->SUPER::new(@_);
}
}

=head1 ATTRIBUTES

=head2 encoding_name

my $name = FFI::C::ASCIIString->encoding_name;
my $name = $str->encoding_name;

Returns the name of the string encoding. For this class it should always be C<ascii>.

=cut

sub encoding_name { 'ascii' }

=head2 encoding_width

my $width = FFI::C::ASCIIString->encoding_width;
my $width = $str->encoding_width;

Returns the size of a character, if the encoding has fixed width characters. For this
class it should always be C<1>.

=cut

sub encoding_width { 1 }

=head1 METHODS

=head2 to_perl

my $perl_string = $str->to_perl;

Copies the NULL terminated C string to a Perl string.
If the string contains non-ASCII characters it will
throw an exception.

=cut

sub to_perl
{
my $self = shift;
my $win;
$self->window($win);
Carp::croak("Non ASCII characters found in string") if $win =~ /[^[:ascii:]]/;
my $copy = "$win";
$copy =~ s/\0.*$//;
$copy;
}

=head2 from_perl

$str->from_perl($perl_string);
$str->from_perl($perl_string, $size);

=cut

sub from_perl
{
my $self = shift;
Carp::croak("Argument is undef") unless @_ >= 1 && defined $_[0];
Carp::croak("Non ASCII characters found in string") if $_[0] =~ /[^[:ascii:]]/;
if($_[0] !~ /\0/)
{
my $str = shift @_;
unshift @_, "$str\0";
}
$self->SUPER::from_perl(@_);
}

=head2 strlen

my $len = $str->strlen;

Returns the length of the string in characters.

=cut

$FFI::C::FFI::ffi->attach( [ strnlen => 'strlen' ] => ['opaque','size_t'] => 'size_t' => sub {
my($xsub, $self) = @_;
$xsub->($self->ptr, $self->buffer_size);
});

=head2 strcat

$str->strcat($perl_string);

Append the content of the Perl string to the end of the C string.

=cut

$FFI::C::FFI::ffi->attach( [ 'strncat' => 'strcat' ] => ['opaque','string','size_t'] => sub {
my $xsub = shift;
my $self = shift;
Carp::croak("Non ASCII characters found in string") if $_[0] =~ /[^[:ascii:]]/;
$xsub->($self->ptr, $_[0], $self->buffer_size);
});

1;

=head1 SEE ALSO

=over 4

=item L<FFI::C>

=item L<FFI::C::Array>

=item L<FFI::C::ArrayDef>

=item L<FFI::C::ASCIIString>

=item L<FFI::C::Buffer>

=item L<FFI::C::Def>

=item L<FFI::C::File>

=item L<FFI::C::PosixFile>

=item L<FFI::C::String>

=item L<FFI::C::Struct>

=item L<FFI::C::StructDef>

=item L<FFI::C::Union>

=item L<FFI::C::UnionDef>

=item L<FFI::C::Util>

=item L<FFI::Platypus::Record>

=back

=cut
6 changes: 6 additions & 0 deletions lib/FFI/C/Array.pm
Original file line number Diff line number Diff line change
Expand Up @@ -133,12 +133,18 @@ sub CLEAR

=item L<FFI::C::ArrayDef>

=item L<FFI::C::ASCIIString>

=item L<FFI::C::Buffer>

=item L<FFI::C::Def>

=item L<FFI::C::File>

=item L<FFI::C::PosixFile>

=item L<FFI::C::String>

=item L<FFI::C::Struct>

=item L<FFI::C::StructDef>
Expand Down
6 changes: 6 additions & 0 deletions lib/FFI/C/ArrayDef.pm
Original file line number Diff line number Diff line change
Expand Up @@ -213,12 +213,18 @@ sub create

=item L<FFI::C::ArrayDef>

=item L<FFI::C::ASCIIString>

=item L<FFI::C::Buffer>

=item L<FFI::C::Def>

=item L<FFI::C::File>

=item L<FFI::C::PosixFile>

=item L<FFI::C::String>

=item L<FFI::C::Struct>

=item L<FFI::C::StructDef>
Expand Down
Loading