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.
The route
Jump straight to the step you need, or tick off Done means at the end.
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
- Confirmed the executable.
h2phis the expected Perl installation's own binary. - Used an explicit destination.
-dkept generated files in a reviewed, writable location. - Support file present. The destination contains
_h2ph_pre.phand the expected.phhierarchy. - Loading proven. A small generated file loads successfully with a local
-Ipath. - Scope chosen deliberately. Recursive or automatic conversion was a deliberate choice, not applied blind to an unreviewed system tree.
- Sources untouched. The original headers remain intact, and no system directory was changed without review.