|Perl: the Markov chain saw|
Re: Follow the Interface? (XS)by tye (Sage)
|on Jul 24, 2003 at 14:03 UTC||Need Help??|
I've been repeatedly frustrated by Perl interfaces to external libraries where someone tried to design some new "Perl-like" interface to the library, wrote a ton of XS code, and too often produced what I'd frankly categorize as, well, "<insert curse word>". (:
They usually produce something that solves the specific problem that they have in mind but that doesn't support lots of other potential uses for the library. They also write way too much XS code and so end up with awkward interface quirks and several outright bugs.
I strongly feel that the best approach is to write the XS code to be *very* C-centric. Using Inline::C over traditional XS can help you do this. I find that a very thin layer that expects data to be provided already in a C-friendly format and returning it in a C-friendly format is less likely to contain bugs and more likely to give the advanced user the full power of the library.
Such also (contrary to the suggestions of the XS manuals) allows more efficiency by letting you make multiple calls into the library without doing any converting of data (and allows you to combine parts of the library in powerful ways).
This should be your first step. Simply wrap the library very thinly so that you can get at the full power of it from Perl (though probably not very easily).
If you have complex C data structures, then your second step is to write C or Perl code to combine/extract data to/from your data structures. IMO, there often aren't any great ways to do this. I'd probably end up using pack/unpack in routines written in Perl if possible (that is, if I could write portable code that way). My second choice would be XS routines that take one argument that is a Perl string to contain/containing the complex structure and a bunch of scalars to contain/containing the parts (because this way XS can do all of the conversion for you and your C code simply needs to combine or extract).
You should avoid trying to manipulate (or even access) Perl aggregates (arrays/hashes/objects) from C code or doing any manipulation of the call stack (let Inline::C do that part and only use simple parameters). Such code won't handle magic nor tie and probably won't get autovification right. And it is very likely to contain bugs.
Your third step is to write Perl wrapper subroutines that accept data in Perl-friendly format, convert it to C-friendly format if needed, call the library, convert the C-friendly format to Perl-friendly format, if needed. If the C-friendly data structures aren't very complicated, then you can skip the second step above and just do the conversion directly in these Perl wrappers.
Your fourth step is to write a Perl-friendly interface (that is much easier to use but might not provide the full power of the interface). It is nice to make the 'easy' interface useful even when one is bypassing parts of it to get to advanced functionality, but this can be a difficult design problem.
TieRegistry is 100% Perl and uses Win32API::Registry to get at the C calls. You have the full power of the registry calls via Win32API::Registry (even to the point of supporting the arguments to functions that are reserved for future use and should be NULL or 0 with current versions of the library and providing calls that you usually don't need or even that require complex data structures that dealing with are outside the scope of this module).
Win32::TieRegistry provides a very Perl-like interface (IMHO). It has a lot of DWIM features and places a lot of importance on the convenience of the module user. This interface would have been impossible to come close to writing XS code.
Win32API::File provides full-power access to several file-related API calls. There are surely several that should still be added, but the ones that are there can be used in every way that they can be used in C (except that there might be a few ways that you could produce a 'core dump' in C that I don't support in the Perl interface). On top of this, there are several Perl-friendly wrappers.
Read the module documentation and compare CreateFile() vs. createFile(). CreateFile() is the full-power, raw (and rather ugly) access to the underlying C API. createFile() is the Perl version that provides intelligent defaults, named arguments, and much simpler ways to specify things like access permissions and sharing levels.- tye