Commit d7a364b882be44c93dc4a843bcca2ae63e805c2c

Authored by Jay Berkenbilt
1 parent 924ebf9f

Allow regular C++ functions to interoperate with the C API

ChangeLog
  1 +2023-12-16 Jay Berkenbilt <ejb@ql.org>
  2 +
  3 + * Add new C++ functions "qpdf_c_get_qpdf" and "qpdf_c_wrap" to
  4 + qpdf-c.h that make it possible to write your own extern "C"
  5 + functions in C++ that interoperate with the C API. See
  6 + examples/extend-c-api for more information.
  7 +
1 2023-12-10 Jay Berkenbilt <ejb@ql.org> 8 2023-12-10 Jay Berkenbilt <ejb@ql.org>
2 9
3 * 11.6.4: release 10 * 11.6.4: release
examples/CMakeLists.txt
@@ -34,6 +34,11 @@ foreach(PROG ${EXAMPLE_C_PROGRAMS}) @@ -34,6 +34,11 @@ foreach(PROG ${EXAMPLE_C_PROGRAMS})
34 endforeach() 34 endforeach()
35 target_include_directories(pdf-create PRIVATE ${JPEG_INCLUDE}) 35 target_include_directories(pdf-create PRIVATE ${JPEG_INCLUDE})
36 36
  37 +# extend-c-api contains a mixture of C and C++ files.
  38 +add_executable(extend-c-api extend-c-api-impl.cc extend-c-api.c)
  39 +set_property(TARGET extend-c-api PROPERTY LINKER_LANGUAGE CXX)
  40 +target_link_libraries(extend-c-api libqpdf)
  41 +
37 add_test( 42 add_test(
38 NAME examples 43 NAME examples
39 COMMAND ${RUN_QTEST} 44 COMMAND ${RUN_QTEST}
@@ -47,7 +52,7 @@ add_test( @@ -47,7 +52,7 @@ add_test(
47 --tc "${qpdf_SOURCE_DIR}/examples/*.cc" 52 --tc "${qpdf_SOURCE_DIR}/examples/*.cc"
48 --tc "${qpdf_SOURCE_DIR}/examples/*.c") 53 --tc "${qpdf_SOURCE_DIR}/examples/*.c")
49 54
50 -file(GLOB EXAMPLES_SRC "*.c" "*.cc") 55 +file(GLOB EXAMPLES_SRC "*.c" "*.cc" "*.h")
51 if(INSTALL_EXAMPLES) 56 if(INSTALL_EXAMPLES)
52 install(FILES ${EXAMPLES_SRC} 57 install(FILES ${EXAMPLES_SRC}
53 DESTINATION ${CMAKE_INSTALL_DOCDIR}/examples 58 DESTINATION ${CMAKE_INSTALL_DOCDIR}/examples
examples/extend-c-api-impl.cc 0 → 100644
  1 +// This is an example of how to write C++ functions and make them usable with the qpdf C API. It
  2 +// consists of three files:
  3 +// - extend-c-api.h -- a plain C header file
  4 +// - extend-c-api.c -- a C program that calls the function
  5 +// - extend-c-api.cc -- a C++ file that implements the function
  6 +
  7 +#include "extend-c-api.h"
  8 +
  9 +// Here, we add a function to get the number of pages in a PDF file and make it callable through the
  10 +// C API.
  11 +
  12 +// This is a normal C++ function that works with QPDF in a normal way. It doesn't do anything
  13 +// special to be callable from C.
  14 +int
  15 +numPages(std::shared_ptr<QPDF> qpdf)
  16 +{
  17 + return qpdf->getRoot().getKey("/Pages").getKey("/Count").getIntValueAsInt();
  18 +}
  19 +
  20 +// Now we define the glue that makes our function callable using the C API.
  21 +
  22 +// This is the C++ implementation of the C function.
  23 +QPDF_ERROR_CODE
  24 +num_pages(qpdf_data qc, int* npages)
  25 +{
  26 + // Call qpdf_c_wrap to convert any exception our function might through to a QPDF_ERROR_CODE
  27 + // and attach it to the qpdf_data object in the same way as other functions in the C API.
  28 + return qpdf_c_wrap(qc, [&qc, &npages]() { *npages = numPages(qpdf_c_get_qpdf(qc)); });
  29 +}
examples/extend-c-api.c 0 → 100644
  1 +/*
  2 + * This is an example of how to write C++ functions and make them usable with the qpdf C API. It
  3 + * consists of three files:
  4 + * - extend-c-api.h -- a plain C header file
  5 + * - extend-c-api.c -- a C program that calls the function
  6 + * - extend-c-api.cc -- a C++ file that implements the function
  7 + */
  8 +
  9 +#include "extend-c-api.h"
  10 +#include <stdio.h>
  11 +#include <stdlib.h>
  12 +#include <string.h>
  13 +
  14 +static char const* whoami = 0;
  15 +
  16 +static void
  17 +usage()
  18 +{
  19 + fprintf(stderr, "Usage: %s infile\n", whoami);
  20 + exit(2);
  21 +}
  22 +
  23 +int
  24 +main(int argc, char* argv[])
  25 +{
  26 + char* infile = NULL;
  27 + qpdf_data qpdf = qpdf_init();
  28 + int warnings = 0;
  29 + int errors = 0;
  30 + char* p = NULL;
  31 +
  32 + if ((p = strrchr(argv[0], '/')) != NULL) {
  33 + whoami = p + 1;
  34 + } else if ((p = strrchr(argv[0], '\\')) != NULL) {
  35 + whoami = p + 1;
  36 + } else {
  37 + whoami = argv[0];
  38 + }
  39 +
  40 + if (argc != 2) {
  41 + usage();
  42 + }
  43 +
  44 + infile = argv[1];
  45 +
  46 + if ((qpdf_read(qpdf, infile, NULL) & QPDF_ERRORS) == 0) {
  47 + int npages;
  48 + if ((num_pages(qpdf, &npages) & QPDF_ERRORS) == 0) {
  49 + printf("num pages = %d\n", npages);
  50 + }
  51 + }
  52 + if (qpdf_more_warnings(qpdf)) {
  53 + warnings = 1;
  54 + }
  55 + if (qpdf_has_error(qpdf)) {
  56 + errors = 1;
  57 + printf("error: %s\n", qpdf_get_error_full_text(qpdf, qpdf_get_error(qpdf)));
  58 + }
  59 + qpdf_cleanup(&qpdf);
  60 + if (errors) {
  61 + return 2;
  62 + } else if (warnings) {
  63 + return 3;
  64 + }
  65 +
  66 + return 0;
  67 +}
examples/extend-c-api.h 0 → 100644
  1 +#ifndef EXAMPLE_C_EXTEND_H
  2 +#define EXAMPLE_C_EXTEND_H
  3 +
  4 +/*
  5 + * This is an example of how to write C++ functions and make them usable with the qpdf C API. It
  6 + * consists of three files:
  7 + * - extend-c-api.h -- a plain C header file
  8 + * - extend-c-api.c -- a C program that calls the function
  9 + * - extend-c-api.cc -- a C++ file that implements the function
  10 + */
  11 +#include <qpdf/qpdf-c.h>
  12 +
  13 +/* Declare your custom function to return QPDF_ERROR_CODE and take qpdf_data and anything else you
  14 + * need. Any errors are retrievable through the qpdf C APIs normal error-handling mechanism.
  15 + */
  16 +
  17 +#ifdef __cplusplus
  18 +extern "C" {
  19 +#endif
  20 + QPDF_ERROR_CODE num_pages(qpdf_data qc, int* npages);
  21 +#ifdef __cplusplus
  22 +}
  23 +#endif
  24 +
  25 +#endif /* EXAMPLE_C_EXTEND_H */
examples/qtest/extend-c-api.test 0 → 100644
  1 +#!/usr/bin/env perl
  2 +require 5.008;
  3 +use warnings;
  4 +use strict;
  5 +
  6 +chdir("extend-c-api") or die "chdir testdir failed: $!\n";
  7 +
  8 +require TestDriver;
  9 +
  10 +cleanup();
  11 +
  12 +my $td = new TestDriver('extend-c-api');
  13 +
  14 +$td->runtest("extend C API (good)",
  15 + {$td->COMMAND => "extend-c-api good.pdf"},
  16 + {$td->FILE => "good.out", $td->EXIT_STATUS => 0},
  17 + $td->NORMALIZE_NEWLINES);
  18 +$td->runtest("extend C API (bad)",
  19 + {$td->COMMAND => "extend-c-api bad.pdf"},
  20 + {$td->FILE => "bad.out", $td->EXIT_STATUS => 2},
  21 + $td->NORMALIZE_NEWLINES);
  22 +
  23 +cleanup();
  24 +
  25 +$td->report(2);
  26 +
  27 +sub cleanup
  28 +{
  29 + unlink "a.pdf";
  30 +}
examples/qtest/extend-c-api/bad.out 0 → 100644
  1 +WARNING: bad.pdf: can't find PDF header
  2 +WARNING: bad.pdf: file is damaged
  3 +WARNING: bad.pdf: can't find startxref
  4 +WARNING: bad.pdf: Attempting to reconstruct cross-reference table
  5 +error: bad.pdf: unable to find trailer dictionary while recovering damaged file
examples/qtest/extend-c-api/bad.pdf 0 → 100644
  1 +not even a pdf file
examples/qtest/extend-c-api/good.out 0 → 100644
  1 +num pages = 1
examples/qtest/extend-c-api/good.pdf 0 → 100644
  1 +%PDF-2.0
  2 +1 0 obj
  3 +<<
  4 + /Pages 2 0 R
  5 + /Type /Catalog
  6 +>>
  7 +endobj
  8 +2 0 obj
  9 +<<
  10 + /Count 1
  11 + /Kids [
  12 + 3 0 R
  13 + ]
  14 + /Type /Pages
  15 +>>
  16 +endobj
  17 +3 0 obj
  18 +<<
  19 + /Contents 4 0 R
  20 + /MediaBox [ 0 0 612 792 ]
  21 + /Parent 2 0 R
  22 + /Resources <<
  23 + /Font << /F1 5 0 R >>
  24 + >>
  25 + /Type /Page
  26 +>>
  27 +endobj
  28 +4 0 obj
  29 +<<
  30 + /Length 44
  31 +>>
  32 +stream
  33 +BT
  34 + /F1 24 Tf
  35 + 72 720 Td
  36 + (Potato) Tj
  37 +ET
  38 +endstream
  39 +endobj
  40 +5 0 obj
  41 +<<
  42 + /BaseFont /Helvetica
  43 + /Encoding /WinAnsiEncoding
  44 + /Subtype /Type1
  45 + /Type /Font
  46 +>>
  47 +endobj
  48 +
  49 +xref
  50 +0 6
  51 +0000000000 65535 f
  52 +0000000009 00000 n
  53 +0000000062 00000 n
  54 +0000000133 00000 n
  55 +0000000277 00000 n
  56 +0000000372 00000 n
  57 +trailer <<
  58 + /Root 1 0 R
  59 + /Size 6
  60 + /ID [<42841c13bbf709d79a200fa1691836f8><b1d8b5838eeafe16125317aa78e666aa>]
  61 +>>
  62 +startxref
  63 +478
  64 +%%EOF
include/qpdf/qpdf-c.h
@@ -21,21 +21,23 @@ @@ -21,21 +21,23 @@
21 #define QPDF_C_H 21 #define QPDF_C_H
22 22
23 /* 23 /*
24 - * This file defines a basic "C" API for qpdf. It provides access to a subset of the QPDF library's 24 + * This file defines a basic "C" API for qpdf. It provides access to a subset of the QPDF library's
25 * capabilities to make them accessible to callers who can't handle calling C++ functions or working 25 * capabilities to make them accessible to callers who can't handle calling C++ functions or working
26 - * with C++ classes. This may be especially useful to Windows users who are accessing the qpdf DLL 26 + * with C++ classes. This may be especially useful to Windows users who are accessing the qpdf DLL
27 * directly or to other people programming in non-C/C++ languages that can call C code but not C++ 27 * directly or to other people programming in non-C/C++ languages that can call C code but not C++
28 - * code. 28 + * code. Starting with qpdf 11.7, it is possible to write your own `extern "C"` functions that
  29 + * interoperate with the C API.
29 * 30 *
30 * There are several things to keep in mind when using the C API. 31 * There are several things to keep in mind when using the C API.
31 * 32 *
32 * Error handling is tricky because the underlying C++ API uses exception handling. See "ERROR 33 * Error handling is tricky because the underlying C++ API uses exception handling. See "ERROR
33 * HANDLING" below for a detailed explanation. 34 * HANDLING" below for a detailed explanation.
34 * 35 *
35 - * The C API is not as rich as the C++ API. For any operations that involve actually  
36 - * manipulating PDF objects, you must use the C++ API. The C API is primarily useful for doing  
37 - * basic transformations on PDF files similar to what you might do with the qpdf command-line  
38 - * tool. 36 + * The C API is not as rich as the C++ API. For many operations, you must use the C++ API. The C
  37 + * API is primarily useful for doing basic transformations on PDF files similar to what you
  38 + * might do with the qpdf command-line tool. You can write your own `extern "C"` functions in
  39 + * C++ that interoperate with the C API by using qpdf_c_get_qpdf and qpdf_c_wrap which were
  40 + * introduced in qpdf 11.7.0.
39 * 41 *
40 * These functions store their state in a qpdf_data object. Individual instances of qpdf_data 42 * These functions store their state in a qpdf_data object. Individual instances of qpdf_data
41 * are not thread-safe: although you may access different qpdf_data objects from different 43 * are not thread-safe: although you may access different qpdf_data objects from different
@@ -990,6 +992,23 @@ extern &quot;C&quot; { @@ -990,6 +992,23 @@ extern &quot;C&quot; {
990 QPDF_ERROR_CODE qpdf_remove_page(qpdf_data qpdf, qpdf_oh page); 992 QPDF_ERROR_CODE qpdf_remove_page(qpdf_data qpdf, qpdf_oh page);
991 #ifdef __cplusplus 993 #ifdef __cplusplus
992 } 994 }
  995 +
  996 +// These C++ functions make it easier to write C++ code that interoperates with the C API.
  997 +// See examples/extend-c-api.
  998 +
  999 +# include <functional>
  1000 +# include <memory>
  1001 +
  1002 +# include <qpdf/QPDF.hh>
  1003 +
  1004 +// Retrieve the real QPDF object attached to this qpdf_data.
  1005 +QPDF_DLL
  1006 +std::shared_ptr<QPDF> qpdf_c_get_qpdf(qpdf_data qpdf);
  1007 +
  1008 +// Wrap a C++ function that may throw an exception to translate the exception for retrieval using
  1009 +// the normal QPDF C API methods.
  1010 +QPDF_DLL
  1011 +QPDF_ERROR_CODE qpdf_c_wrap(qpdf_data qpdf, std::function<void()> fn);
993 #endif 1012 #endif
994 1013
995 #endif /* QPDF_C_H */ 1014 #endif /* QPDF_C_H */
libqpdf/qpdf-c.cc
@@ -1949,3 +1949,15 @@ qpdf_write_json( @@ -1949,3 +1949,15 @@ qpdf_write_json(
1949 }); 1949 });
1950 return status; 1950 return status;
1951 } 1951 }
  1952 +
  1953 +std::shared_ptr<QPDF>
  1954 +qpdf_c_get_qpdf(qpdf_data qpdf)
  1955 +{
  1956 + return qpdf->qpdf;
  1957 +}
  1958 +
  1959 +QPDF_ERROR_CODE
  1960 +qpdf_c_wrap(qpdf_data qpdf, std::function<void()> fn)
  1961 +{
  1962 + return trap_errors(qpdf, [&fn](qpdf_data) { fn(); });
  1963 +}
manual/release-notes.rst
@@ -38,6 +38,13 @@ Planned changes for future 12.x (subject to change): @@ -38,6 +38,13 @@ Planned changes for future 12.x (subject to change):
38 38
39 .. x.y.z: not yet released 39 .. x.y.z: not yet released
40 40
  41 +11.7.0: not yet released
  42 + - Library Enhancements:
  43 +
  44 + - Add C++ functions ``qpdf_c_wrap`` and ``qpdf_c_get_qpdf`` to the
  45 + C API to enable custom C++ code to interoperate more easily with
  46 + the the C API. See ``examples/extend-c-api``.
  47 +
41 11.6.4: December 10, 2023 48 11.6.4: December 10, 2023
42 - Bug fixes: 49 - Bug fixes:
43 50