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

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

I want to run a script at computer, where I'd rather not expose its (this script's) C guts (embarrassing situation... sorry). Consider SSCCE of myscript.pl:

use strict; use warnings; use feature 'say'; use Inline C => << 'END'; int add (int x, int y) { return x + y; } END say add( 5, 7 );

I know nothing about XS; and (another alternative) though I think I can compile C to a DLL and then use FFI, I suspect it would be less efficient: script is run many times -- as e.g. $^X myscript.pl -- by larger continuously running Perl application. So I'd prefer to stay with Inline::C, but 'hide' the algo realisation.

There's FAQ topic, but it's (1) not exactly about what I need, and thus not achieves what I want, (2) I found it's not exact in what it describes -- no need to split Perl and C into separate files, no need to bother with MD5.

Nevertheless, using FAQ as base and example Math::Simple bundled with Inline:

mkdir MyC, place 2 files there:

Makefile.PL:

use Inline::MakeMaker; WriteMakefile( NAME => 'myC', VERSION_FROM => 'myC.pm' );

myC.pm:

package myC; our $VERSION = 1; use Inline C => << 'END', VERSION => 1, NAME => 'myC'; int add (int x, int y) { return x + y; } END 1;

then run perl Makefile.PL, gmake test. The blib directory is created, where I can delete everything but 2 files:

blib/arch/auto/myC/myC.xs.dll

blib/lib/myC.pm

the latter I modify as ('END' can be anything, no need to write MD5):

package myC; our $VERSION = 1; use Inline C => 'END', VERSION => 1, NAME => 'myC'; 1;

Then I modify original myscript.pl as:

use strict; use warnings; use feature 'say'; use blib; use myC; say myC::add( 5, 7 );

Then move it to some other location for testing, bundled with blib folder (which itself is 4 folders, 2 files). Looks like goal achieved, but I wonder if there is 'more correct' way. In particular, I'd like to avoid creating dummy module and package (myC), or at least have it declared inside myscript.pl itself. 2nd point, I'd like the dll to be placed e.g. into "standard" _Inline directory with "standard" Inline hierarchy and naming conventions, since this folder already exists anyway (for parts which I don't bother to 'hide'. Like I said the whole situation is complicated and embarrassing), so there's no need to use blib;. Maybe there are Inline configuration options for that?

Replies are listed 'Best First'.
Re: Better way to force Inline to use compiled binary instead of C source?
by swl (Parson) on Jun 12, 2022 at 07:22 UTC

    It might be worth looking at Inline::Module. You can keep your code using the Inline setup, and let Inline::Module handle the XS wrapping. After that you can treat it as a normal module.

Re: Better way to force Inline to use compiled binary instead of C source?
by bliako (Monsignor) on Jun 15, 2022 at 12:34 UTC

    I hope I do not misunderstand your case, but I believe my experience with Image::DecodeQR::WeChat can help you (re: https://metacpan.org/pod/Image::DecodeQR::WeChat#IMPLEMENTATION-DETAILS). In that, I am linking against opencv library via a "normal" C program (C++ actually, but with C it is way simpler) wherein all my logic happens and calls to opencv's functions are placed. Then I create a very vanilla XS (you can copy-paste "mine" which already receives some input parameters and returns an arrayref of results back to perl code). Then place that intermediate C file in basedir of module. MM::EU will compile it. For distributing, just set the LIBS, CFLAGS, LDFLAGS in Makefile.PL to link to the compiled (DLL) C file and include that DLL somewhere in your distribution.

    To recap: 0) forget Inline::C (though it is a *great* module). 1) create a vanilla XS file where you receive some params and return back results. That's documented and I climbed that learning curve ok-ish. In this XS, do not place any other program logic. Instead, 2) place all your program logic in a separate C file to be called via some functions. The benefit of this is that a) this C file will not be compiled along with Perl headers and all the problems usually occuring with macro-name clashes can be avoided, it is a perl-independent C file and b) in the C file, you are not constrained by XS idioms. 3) In XS just call the functions in your C file (and including appropriate C header file). A slight complication would be getting the XS function's (the one your Perl-code will call) input parameters and adjusting them for your C function calls. In my case I had to allocate memory and copy input parameters data into these and pass these to the C function, but that was my case. Also note that I arrived to above setup because including opencv function calls from within Inline::C or XS failed because opencv's headers contain macros and functions which clash with Perl's own name-sakes(!). If you don't have this constraint then your solution can be simpler. Bottomline is: vanilla XS with just a simple call to your C function which is implemented in its own C file in order to avoid any XS idioms and/or Perl header name clashes.

    bw, bliako

      Bottomline is: vanilla XS with just a simple call to your C function which is implemented in its own C file in order to avoid any XS idioms and/or Perl header name clashes

      Yes - that's fairly straightforward in the usual XS environment.
      Has the same capability not been ported to Inline::C ? (I certainly don't know how to do it in Inline::C, but I've never even wondered about it before.)
      I wondered whether Inline::C's "autowrap" feature might be useful here, but it seems geared towards accessing C libraries rather than C source code.

      Anyway ... is that the issue that has prompted this thread ?
      I couldn't really work out what the issue was, and I'm still not too sure.

      Cheers,
      Rob
        Has the same capability not been ported to Inline::C ?

        I've had a look ... and the capability in question has NOT been ported to Inline::C, but it seems do-able.
        Firstly, the patch to Inline/C.pm:
        --- C.pm_orig 2021-12-20 22:11:48 +1100 +++ C.pm 2022-06-27 19:27:27 +1000 @@ -177,6 +177,10 @@ $o->{ILSM}{XS}{PREFIX} = $value; next; } + if($key eq 'OBJECT') { + $o->add_string($o->{ILSM}{MAKEFILE}, $key, $value); + next; + } if ($key eq 'FILTERS') { next if $value eq '1' or $value eq '0'; # ignore ENABLE, +DISABLE $value = [$value] unless ref($value) eq 'ARRAY';
        And the Inline::C demo:
        use warnings; use Inline C => Config => BUILD_NOISY => 1, OBJECT => '$(O_FILES)', ; use Inline C =><<'EOC'; #include "bar.h" #include "baz.h" void foo(int i) { printf("bar: %d\n", bar(i)); printf("baz: %d\n", baz(i)); } EOC foo(42);
        bar.h prototypes the bar function:  int bar (int);
        baz.h prototpyes the baz function:  int baz (int);
        bar.c defines the bar() function:
        #include "bar.h" int bar (int in) { return in; }
        and baz.c defines the baz() function:
        #include "baz.h" int baz (int in) { return in + 1; }
        It's probably important that each of those 4 files terminate with at least one empty line.

        But there's a snag I haven't quite got around yet: The first time I run my Inline::C script, it fails to compile because of undefined references to 'bar' and 'baz'. I then manually copy bar.h, baz.h, bar.c and baz.c to the Inline build directory that was created by that first run ... then I re-run the script, and all goes fine. And I end up with the expected output of:
        bar: 42 baz: 43
        So ... I wonder if there's some way that I can make bar.h, baz.h, bar.c and baz.c visible to the build process without having to move them to the build directory ?
        Essentially, I think the problem is that, with the current OBJECT => '$(O_FILES)' arg, the .c snd .h files need to be in the same directory as the auto-generated Makefile.PL (ie the script's build directory).
        I'm hoping there's some way to get the message across that we want to use the .c and.h files from a different location.
        Otherwise, we face the task of getting Inline to move those files across to the build directory *before* the auto-generated Makefile.PL is run.

        Any ideas ? (I'll keep fiddling with it and see if I can come up with the right solution.)
        Having to manually copy files and re-run commands strikes me as being a little less than optimal ;-)

        Cheers,
        Rob
        I wondered whether Inline::C's "autowrap" feature might be useful here

        thanks, this seems like the "Inline option" I was looking for: function declaration only in C embedded in Perl, and no need in wrapper *.pm. However, I didn't expect that to compile my real code (not trivial add) -- which itself links to a few libs -- to a dll would require such effort to "construct" the correct command line (to type it, at least), it's a bit out of my league, I'm feeling like monkey copying text fragments without understanding the basics. All the more reason to appreciate what Inline::C is doing transparently, I think I'll stay with "blib" solution outlined in OP. Thank you everyone for answers.

Re: Better way to force Inline to use compiled binary instead of C source?
by vr (Curate) on Jun 12, 2022 at 22:53 UTC

    Here's an issue I bumped into accidentally, while looking for solution to parent node, but I hesitate to ask a new SOPW question. For Strawberry Perl 5.24 (version to deploy to), even if (and that's confusing part) ExtUtils::MakeMaker is updated to latest CPAN version, the following happens.

    Makefile.PL:

    use ExtUtils::MakeMaker; WriteMakefile(NAME => 'Test');

    Test.pm:

    package Test; 1;

    running perl Makefile.PL, gmake test results in error:

    to undefined at C:/berrybrew/5.24.0.1_64_PDL/perl/site/lib/ExtUtils/In +stall.pm line 1199. makefile:862: recipe for target 'pm_to_blib' failed gmake: *** [pm_to_blib] Error 2

    Offending section in Makefile is

    # --- MakeMaker pm_to_blib section: pm_to_blib : $(FIRST_MAKEFILE) $(TO_INST_PM) $(NOECHO) $(ABSPERLRUN) -MExtUtils::Install -e "pm_to_blib({{@ARGV +}}, '$(INST_LIB)\auto', q[$(PM_FILTER)], '$(PERM_DIR)')" -- \ Test.pm $(INST_LIB)\Test.pm $(NOECHO) $(TOUCH) pm_to_blib

    line #862 contains {{@ARGV}}, deleting a pair of braces (changing double to single) fixes the issue (I peeked at Makefile which is produced under Perl 5.26). It's very confusing, why updating to latest ExtUtils::MakeMaker doesn't help, and how 5.24 is functional at all.

      running perl Makefile.PL, gmake test results in error

      This might (untested) be because EU::MM expects the make utility to be "dmake" not "gmake".
      Strawberry made the switch to "gmake" in perl-5.26.0, and have stayed with it since then.

      You might get better mileage with something like:
      use ExtUtils::MakeMaker; WriteMakefile(NAME => 'Test', MAKE => 'gmake' +);
      or just use "dmake" on 5.24.

      Cheers,
      Rob

        Thanks, you are right, either of the 2 solutions fixes the problem.