Convert C Headers to Usable Perl .ph Files with h2ph

Old Perl code that still says require 'foo.ph' needs h2ph to turn a C header into something Perl can actually load. This guide converts one or more C header files into Perl .ph files, places them under a destination you control, and checks that the generated definitions load. Allow about ten minutes for a small header set. Examples use Perl 5.38.2 from Ubuntu package perl 5.38.2-3.2ubuntu0.6; the installed h2ph reports itself as version 4 when it writes a generated support file.

1. Check the installed command

Start by confirming which executable will run, and which Perl library directory this installation treats as its site architecture directory:

$ command -v h2ph
/usr/bin/h2ph
$ dpkg-query -W -f='${Package} ${Version}\n' perl
perl 5.38.2-3.2ubuntu0.6
$ perl -V:installsitearch
installsitearch='/usr/local/lib/x86_64-linux-gnu/perl/5.38.2';

The manual says the default output lands beneath Perl's architecture-dependent library directory. On this machine that directory does not exist, so an invocation without -d is not a useful first test. Use an explicit, writable destination for project work, and review the output before copying anything into a shared Perl installation.

Checkpoint: command -v h2ph returns the executable you expected, and you have a destination directory separate from the source tree.

2. Convert one header into a separate destination

Create a small working tree for the example. The header is ordinary input data; h2ph does not edit it. This runs entirely under /tmp, so it needs no elevated privileges:

$ work=$(mktemp -d /tmp/h2ph-demo.XXXXXX)
$ mkdir -p "$work/include" "$work/out"
$ printf '%s\n' '#define DEMO_VALUE 42' '#define DEMO_NAME "hello"' > "$work/include/demo.h"
$ cd "$work"
$ h2ph -d "$work/out" include/demo.h
include/demo.h -> include/demo.ph

-d changes the root of the generated hierarchy. With a relative input path, this run creates out/include/demo.ph and also creates out/_h2ph_pre.ph, a support file this Perl release requires alongside generated output:

$ find "$work/out" -type f -print
/tmp/h2ph-demo.XXXXXX/out/_h2ph_pre.ph
/tmp/h2ph-demo.XXXXXX/out/include/demo.ph
$ sed -n '1,12p' "$work/out/include/demo.ph"
require '_h2ph_pre.ph';

no warnings qw(redefine misc);

eval 'sub DEMO_VALUE () {42;}' unless defined(&DEMO_VALUE);
eval 'sub DEMO_NAME () {"hello";}' unless defined(&DEMO_NAME);
1;

Your temporary directory name will differ. The checks that matter are the conversion line, the support file, the matching directory structure, and a final 1; in the generated file.

3. Load the generated definitions

Test the result from the destination directory before moving it anywhere permanent. Perl's -I adds a directory to its module search path, and require loads the generated file by its relative path:

$ perl -I "$work/out" -e 'require "include/demo.ph"; print DEMO_VALUE, " ", DEMO_NAME, "\n"'
42 hello

This confirms the generated file can find _h2ph_pre.ph and that the two simple macros became callable Perl constants. It does not prove every C construct in a large system header translates correctly.

Do not add the generated directory to a system-wide Perl path just to make a test pass. Keep -I local to the command, or use the project-specific mechanism that already controls its Perl search path.

4. Convert a directory deliberately

For a directory tree, -r tells h2ph to process files in that directory and its subdirectories. Run it from the directory whose layout you want to preserve:

$ work=$(mktemp -d /tmp/h2ph-tree.XXXXXX)
$ mkdir -p "$work/headers/net" "$work/ph"
$ printf '%s\n' '#define NET_PORT 8080' > "$work/headers/net/config.h"
$ cd "$work"
$ h2ph -r -d "$work/ph" headers
$ find "$work/ph" -type f -print
/tmp/h2ph-tree.XXXXXX/ph/_h2ph_pre.ph
/tmp/h2ph-tree.XXXXXX/ph/headers/net/config.ph

Recursive conversion can process far more files than expected, especially against a system include tree. Inspect the directory first with find headers -type f -print. -r and -a are mutually exclusive: choose recursion for a known tree, or automatic conversion when you specifically want included .h files resolved through the C compiler's usual include directories.

5. Choose links and diagnostics consciously

Symbolic links are skipped unless you pass -l. Add it only when preserving the link structure is part of the output you need; otherwise skipping links reduces the chance of converting the same header through several names.

Use -e for a batch where one conversion error should remove that output file and let the rest continue with a warning. Without it, an error stops the conversion immediately. Use -Q when the normal per-file progress messages would interfere with a script's output. -h adds source-file hints to generated code, making a later syntax error easier to locate, but nearly doubles the size of the .ph files. -D includes the original C code as comments and is mainly a debugging aid.

None of these options make unsupported C constructs safe. The manual describes h2ph as a rough tool: it tries to isolate definitions inside eval blocks, but it does not handle every expression or construct, and it does not build the %sizeof array for you.

6. Avoid the common failure modes

If the command cannot write its destination, check the directory and permissions before trying sudo:

$ test -d "$work/out" && test -w "$work/out" && echo destination-writable
destination-writable

Running as root can hide an ownership mistake and leave generated files your normal account cannot update. Use elevated privileges only when the final destination genuinely belongs to a system-managed Perl installation, and review the generated tree first.

If a generated file fails to load, rerun the smallest failing header with -h, then inspect the reported source filename and the surrounding generated eval. Keep the original header and the failed output while diagnosing. If a partial conversion changes a destination you own, remove only that generated tree after checking its path, then rerun into a clean directory. Never point a cleanup command at /usr/include or a shared Perl directory.

With no header arguments, the manual documents filter mode: reading C-like text from standard input and writing Perl header text to standard output. Supply -d if the command also needs to create its support file in a known location:

$ printf '%s\n' '#define STDIN_VALUE 7' | h2ph -d "$work/out"
require '_h2ph_pre.ph';

no warnings qw(redefine misc);

eval 'sub STDIN_VALUE () {7;}' unless defined(&STDIN_VALUE);
1;

Done means