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 | 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
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 "C" { | @@ -990,6 +992,23 @@ extern "C" { | ||
| 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 |