Commit d7a364b882be44c93dc4a843bcca2ae63e805c2c
1 parent
924ebf9f
Allow regular C++ functions to interoperate with the C API
Showing
13 changed files
with
280 additions
and
8 deletions
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 | 8 | 2023-12-10 Jay Berkenbilt <ejb@ql.org> |
| 2 | 9 | |
| 3 | 10 | * 11.6.4: release | ... | ... |
examples/CMakeLists.txt
| ... | ... | @@ -34,6 +34,11 @@ foreach(PROG ${EXAMPLE_C_PROGRAMS}) |
| 34 | 34 | endforeach() |
| 35 | 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 | 42 | add_test( |
| 38 | 43 | NAME examples |
| 39 | 44 | COMMAND ${RUN_QTEST} |
| ... | ... | @@ -47,7 +52,7 @@ add_test( |
| 47 | 52 | --tc "${qpdf_SOURCE_DIR}/examples/*.cc" |
| 48 | 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 | 56 | if(INSTALL_EXAMPLES) |
| 52 | 57 | install(FILES ${EXAMPLES_SRC} |
| 53 | 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
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 | #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 | 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 | 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 | 31 | * There are several things to keep in mind when using the C API. |
| 31 | 32 | * |
| 32 | 33 | * Error handling is tricky because the underlying C++ API uses exception handling. See "ERROR |
| 33 | 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 | 42 | * These functions store their state in a qpdf_data object. Individual instances of qpdf_data |
| 41 | 43 | * are not thread-safe: although you may access different qpdf_data objects from different |
| ... | ... | @@ -990,6 +992,23 @@ extern "C" { |
| 990 | 992 | QPDF_ERROR_CODE qpdf_remove_page(qpdf_data qpdf, qpdf_oh page); |
| 991 | 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 | 1012 | #endif |
| 994 | 1013 | |
| 995 | 1014 | #endif /* QPDF_C_H */ | ... | ... |
libqpdf/qpdf-c.cc
| ... | ... | @@ -1949,3 +1949,15 @@ qpdf_write_json( |
| 1949 | 1949 | }); |
| 1950 | 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 | 38 | |
| 39 | 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 | 48 | 11.6.4: December 10, 2023 |
| 42 | 49 | - Bug fixes: |
| 43 | 50 | ... | ... |