http://www.perlmonks.org?node_id=574946

creamygoodness has asked for the wisdom of the Perl Monks concerning the following question:

Greets,

I'm trying to build the XS equivalent of the parse-named-params idiom:

sub do_stuff { my ( $self, %args ) = @_; my $foo = $args{foo}; my $bar = $args{bar}; # .... }

Here's how I'd like the XS code to look:

void do_stuff(self, ...) SV *self; PPCODE: { HV* args_hash = build_args_hash(args, to, be, named, later); IV foo = extract_iv(args_hash, "foo", 3); NV bar = extract_nv(args_hash, "bar", 3); /* ... */ }

The extract_XX functions are easy -- they're just casting/error-checking wrappers around hv_fetch:

IV extract_iv(hash, key, key_len) { SV **const sv_ptr = hv_fetch(hash, key, key_len, 0); if (sv_ptr == NULL) { croak("Failed to retrieve hash entry '%s'", key); } return SvIV(*sv_ptr); }

The sticking point is the build_args_hash function. I think I have a solution, but I'm not confident it's the best one.

Here's one version:

HV* build_args_hash() { dXSARGS; /* need to declare XS args so that we can use ST(n) late +r */ HV *args_hash = (HV*)sv_2mortal( (SV*)newHV() ); /* mortalized has +h */ kino_i32_t stack_pos = 1; /* verify that our args come in pairs */ if (items % 2 != 1) { croak("Expecting hash-style named params, got odd number of ar +gs"); } /* build the hash */ while (stack_pos < items) { SV *val_sv; SV *key_sv = ST(stack_pos++); STRLEN key_len; char *key = SvPV(key_sv, key_len); val_sv = ST(stack_pos++); hv_store(args_hash, key, key_len, newSVsv(val_sv), 0); } return args_hash; }

Unfortunately dXSARGS, in the process of declaring items, sp, ax, mark and so on, pops a stack marker from the mark stack. Without getting into the gory details of what that means, the effect is that the XS function has to invoke the PUSHMARK macro before calling build_args_hash.

{ PUSHMARK(SP); HV* args_hash = build_args_hash(); IV foo = extract_iv(args_hash, "foo", 3); NV bar = extract_nv(args_hash, "bar", 3); /* ... */ }

That's not C89-compliant, because PUSHMARK is a statement, not a declaration. :( So we have to declare all of our vars first, and the number of lines of code dedicated to initialization more than doubles.

{ HV *args_hash; IV foo; NV bar; PUSHMARK(SP); args_hash = build_args_hash(); foo = extract_iv(args_hash, "foo", 3); bar = extract_nv(args_hash, "bar", 3); /* ... */ }

The answer, I'm sure, is to find the right combination of perlapi variables to pass to build_args_hash, but finding that combo has proved more elusive than I'd expected. It's easy to hack something up, but harder if we constrain ourselves using only well-documented official functions. One possibility is to pass in the address of the first item on the stack and then number of items:

HV *arg_hash = build_args_hash( &(ST(0)), items );

A version of build_args_hash built that way works on my machine, but I'm concerned because it relies on an implementation detail: the stack gets accessed directly as an SV**, rather than using the ST(n) macros. I think it's safe, but I'd like to submit this to the code contributions section and that doesn't seem like best practice. What do you think?

Some chatterbox denizens have suggested that this sort of thing is best done using a Perl helper sub which marshalls the named args into an ordered list and then submits them to an XS routine. That's a perfectly fine approach. However, I have something like 35 XS classes to deal with, and if I solve this problem once, then I can do away with a lot of extra code.

--
Marvin Humphrey
Rectangular Research ― http://www.rectangular.com

Replies are listed 'Best First'.
Re: XS: parse hash-style named parameters
by sth (Priest) on Sep 27, 2006 at 13:32 UTC

    I would post this to perl-xs@perl.org as well.