-
Notifications
You must be signed in to change notification settings - Fork 47
/
Copy pathCallExt.pm
242 lines (170 loc) · 7.29 KB
/
CallExt.pm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
package PDL::CallExt;
use strict;
use warnings;
use Config;
use PDL::Core;
use PDL::Exporter;
use DynaLoader;
use Carp;
our @ISA = qw( PDL::Exporter DynaLoader );
our @EXPORT_OK = qw( callext callext_cc );
our %EXPORT_TAGS = (Func=>\@EXPORT_OK);
our @EXPORT = @EXPORT_OK;
bootstrap PDL::CallExt;
=head1 NAME
PDL::CallExt - call functions in external shared libraries
=head1 SYNOPSIS
use PDL::CallExt;
callext('file.so', 'foofunc', $x, $y); # pass ndarrays to foofunc()
% perl -MPDL::CallExt -e callext_cc file.c
=head1 DESCRIPTION
callext() loads in a shareable object (i.e. compiled code) using
Perl's dynamic loader, calls the named function and passes a list of
ndarray arguments to it.
It provides a reasonably portable way of doing this, including
compiling the code with the right flags, though it requires simple
perl and C wrapper routines to be written. You may prefer to use PP,
which is much more portable. See L<PDL::PP>. You should definitely use
the latter for a 'proper' PDL module, or if you run in to the
limitations of this module.
=head1 API
callext_cc() allows one to compile the shared objects using Perl's knowledge
of compiler flags.
The named function (e.g. 'foofunc') must take a list of ndarray structures as
arguments, there is now way of doing portable general argument construction
hence this limitation.
In detail the code in the original file.c would look like this:
#include "pdlsimple.h" /* Declare simple ndarray structs - note this .h file
contains NO perl/PDL dependencies so can be used
standalone */
int foofunc(int nargs, pdlsimple **args); /* foofunc prototype */
i.e. foofunc() takes an array of pointers to pdlsimple structs. The use is
similar to that of C<main(int nargs, char **argv)> in UNIX C applications.
pdlsimple.h defines a simple N-dimensional data structure which looks like this:
struct pdlsimple {
int datatype; /* whether byte/int/float etc. */
void *data; /* Generic pointer to the data block */
PDL_Indx nvals; /* Number of data values */
PDL_Indx *dims; /* Array of data dimensions */
PDL_Indx ndims; /* Number of data dimensions */
};
(PDL_Indx is 32- or 64-bit depending on architecture and is defined in pdlsimple.h)
This is a simplification of the internal representation of ndarrays in PDL which is
more complicated because of broadcasting, dataflow, etc. It will usually be found
somewhere like /usr/local/lib/perl5/site_perl/PDL/pdlsimple.h
Thus to actually use this to call real functions one would need to write a wrapper.
e.g. to call a 2D image processing routine:
void myimage_processer(double* image, int nx, int ny);
int foofunc(int nargs, pdlsimple **args) {
pdlsimple* image = pdlsimple[0];
myimage_processer( image->data, *(image->dims), *(image->dims+1) );
...
}
Obviously a real wrapper would include more error and argument checking.
This might be compiled (e.g. Linux):
cc -shared -o mycode.so mycode.c
In general Perl knows how to do this, so you should be able to get
away with:
perl -MPDL::CallExt -e callext_cc file.c
callext_cc() is a function defined in PDL::CallExt to generate the
correct compilation flags for shared objects.
If their are problems you will need to refer to you C compiler manual to find
out how to generate shared libraries.
See t/callext.t in the distribution for a working example.
It is up to the caller to ensure datatypes of ndarrays are correct - if not
peculiar results or SEGVs will result.
=head1 FUNCTIONS
=head2 callext
=for ref
Call a function in an external library using Perl dynamic loading
=for usage
callext('file.so', 'foofunc', $x, $y); # pass ndarrays to foofunc()
The file must be compiled with dynamic loading options
(see C<callext_cc>). See the module docs C<PDL::Callext>
for a description of the API.
=head2 callext_cc
=for ref
Compile external C code for dynamic loading
=for usage
Usage:
% perl -MPDL::CallExt -e callext_cc file.c -o file.so
This works portably because when Perl has built in knowledge of how to do
dynamic loading on the system on which it was installed.
See the module docs C<PDL::Callext> for a description of
the API.
=cut
sub callext{
die "Usage: callext(\$file,\$symbol, \@pdl_args)" if scalar(@_)<2;
my($file,$symbol, @pdl_args) = @_;
my $libref = DynaLoader::dl_load_file($file);
my $err = DynaLoader::dl_error(); barf $err if !defined $libref;
my $symref = DynaLoader::dl_find_symbol($libref, $symbol);
$err = DynaLoader::dl_error(); barf $err if !defined $symref;
_callext_int($symref, @pdl_args);
1;}
# Compile external C program correctly
#
# callext_cc
#
# The old version of this routine was taking unstructured arguments and
# happily passed this though the C compiler. Unfortunately, on platforms
# like HP-UX, we need to make separate cc and ld runs in order to create the
# shared objects.
#
# The signature of the function was therefore changed starting at PDL 2.0.
# It is now:
#
# ($src, $ccflags, $ldflags, $output)
#
# In its simplest invocation, it can be just $src, and the output will be
# derived from the source file. Otherwise, $ccflags add extra C flags, $ldflags
# adds extra ld flags, and $output specifies the final target output file name.
# If left blank, it will be in the same directory where $src lied.
#
sub callext_cc {
my @args = @_>0 ? @_ : @ARGV;
my ($src, $ccflags, $ldflags, $output) = @args;
my $cc_obj;
($cc_obj = $src) =~ s/\.c$/$Config{_o}/;
my $ld_obj = $output;
($ld_obj = $cc_obj) =~ s/\.o$/\.$Config{dlext}/ unless defined $output;
# Output flags for compiler depend on os.
# -o on cc and gcc, or /Fo" " on MS Visual Studio
# Need a start and end string
my $do = ( $Config{cc} eq 'cl' ? '/Fo"' : '-o ');
my $eo = ( $^O =~ /MSWin/i ? '"' : '' );
# Compiler command
# Placing $ccflags *before* installsitearch/PDL/Core enables us to include
# the blib 'pdlsimple.h' during 'make test'.
my $cc_cmd = join(' ', map { $Config{$_} } qw(cc ccflags cccdlflags)) .
qq{ $ccflags "-I$Config{installsitearch}/PDL/Core" -c $src $do$cc_obj$eo};
# The linker output flag is -o on cc and gcc, and -out: on MS Visual Studio
my $o = ( $Config{cc} eq 'cl' ? '-out:' : '-o ');
# Setup the LD command. Do not want the env var on Windows
my $ld_cmd = ( $^O =~ /MSWin|android/i ? ' ' : 'LD_RUN_PATH="" ');
my $libs = $^O =~ /MSWin/i ?
$Config{libs} :
''; # used to be $Config{libs} but that bombs
# on recent debian platforms
$ld_cmd .=
join(' ', map { $Config{$_} } qw(ld lddlflags)) .
" $libs $ldflags $o$ld_obj $cc_obj";
# Run the command in two steps so that we can check status
# of each and also so that we dont have to rely on ';' command
# separator
system $cc_cmd and croak "Error compiling $src ($cc_cmd)";
# Fix up ActiveState-built perl. Is this a reliable fix ?
$ld_cmd =~ s/\-nodefaultlib//g if $Config{cc} eq 'cl';
system $ld_cmd and croak "Error linking $cc_obj ($ld_cmd)";
return 1;
}
=head1 AUTHORS
Copyright (C) Karl Glazebrook 1997.
All rights reserved. There is no warranty. You are allowed
to redistribute this software / documentation under certain
conditions. For details, see the file COPYING in the PDL
distribution. If this file is separated from the PDL distribution,
the copyright notice should be included in the file.
=cut
# Exit with OK status
1;