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

This commit is contained in:
Jay Berkenbilt 2023-12-16 09:46:44 -05:00
parent 924ebf9f6a
commit d7a364b882
13 changed files with 280 additions and 8 deletions

View File

@ -1,3 +1,10 @@
2023-12-16 Jay Berkenbilt <ejb@ql.org>
* Add new C++ functions "qpdf_c_get_qpdf" and "qpdf_c_wrap" to
qpdf-c.h that make it possible to write your own extern "C"
functions in C++ that interoperate with the C API. See
examples/extend-c-api for more information.
2023-12-10 Jay Berkenbilt <ejb@ql.org>
* 11.6.4: release

View File

@ -34,6 +34,11 @@ foreach(PROG ${EXAMPLE_C_PROGRAMS})
endforeach()
target_include_directories(pdf-create PRIVATE ${JPEG_INCLUDE})
# extend-c-api contains a mixture of C and C++ files.
add_executable(extend-c-api extend-c-api-impl.cc extend-c-api.c)
set_property(TARGET extend-c-api PROPERTY LINKER_LANGUAGE CXX)
target_link_libraries(extend-c-api libqpdf)
add_test(
NAME examples
COMMAND ${RUN_QTEST}
@ -47,7 +52,7 @@ add_test(
--tc "${qpdf_SOURCE_DIR}/examples/*.cc"
--tc "${qpdf_SOURCE_DIR}/examples/*.c")
file(GLOB EXAMPLES_SRC "*.c" "*.cc")
file(GLOB EXAMPLES_SRC "*.c" "*.cc" "*.h")
if(INSTALL_EXAMPLES)
install(FILES ${EXAMPLES_SRC}
DESTINATION ${CMAKE_INSTALL_DOCDIR}/examples

View File

@ -0,0 +1,29 @@
// This is an example of how to write C++ functions and make them usable with the qpdf C API. It
// consists of three files:
// - extend-c-api.h -- a plain C header file
// - extend-c-api.c -- a C program that calls the function
// - extend-c-api.cc -- a C++ file that implements the function
#include "extend-c-api.h"
// Here, we add a function to get the number of pages in a PDF file and make it callable through the
// C API.
// This is a normal C++ function that works with QPDF in a normal way. It doesn't do anything
// special to be callable from C.
int
numPages(std::shared_ptr<QPDF> qpdf)
{
return qpdf->getRoot().getKey("/Pages").getKey("/Count").getIntValueAsInt();
}
// Now we define the glue that makes our function callable using the C API.
// This is the C++ implementation of the C function.
QPDF_ERROR_CODE
num_pages(qpdf_data qc, int* npages)
{
// Call qpdf_c_wrap to convert any exception our function might through to a QPDF_ERROR_CODE
// and attach it to the qpdf_data object in the same way as other functions in the C API.
return qpdf_c_wrap(qc, [&qc, &npages]() { *npages = numPages(qpdf_c_get_qpdf(qc)); });
}

67
examples/extend-c-api.c Normal file
View File

@ -0,0 +1,67 @@
/*
* This is an example of how to write C++ functions and make them usable with the qpdf C API. It
* consists of three files:
* - extend-c-api.h -- a plain C header file
* - extend-c-api.c -- a C program that calls the function
* - extend-c-api.cc -- a C++ file that implements the function
*/
#include "extend-c-api.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static char const* whoami = 0;
static void
usage()
{
fprintf(stderr, "Usage: %s infile\n", whoami);
exit(2);
}
int
main(int argc, char* argv[])
{
char* infile = NULL;
qpdf_data qpdf = qpdf_init();
int warnings = 0;
int errors = 0;
char* p = NULL;
if ((p = strrchr(argv[0], '/')) != NULL) {
whoami = p + 1;
} else if ((p = strrchr(argv[0], '\\')) != NULL) {
whoami = p + 1;
} else {
whoami = argv[0];
}
if (argc != 2) {
usage();
}
infile = argv[1];
if ((qpdf_read(qpdf, infile, NULL) & QPDF_ERRORS) == 0) {
int npages;
if ((num_pages(qpdf, &npages) & QPDF_ERRORS) == 0) {
printf("num pages = %d\n", npages);
}
}
if (qpdf_more_warnings(qpdf)) {
warnings = 1;
}
if (qpdf_has_error(qpdf)) {
errors = 1;
printf("error: %s\n", qpdf_get_error_full_text(qpdf, qpdf_get_error(qpdf)));
}
qpdf_cleanup(&qpdf);
if (errors) {
return 2;
} else if (warnings) {
return 3;
}
return 0;
}

25
examples/extend-c-api.h Normal file
View File

@ -0,0 +1,25 @@
#ifndef EXAMPLE_C_EXTEND_H
#define EXAMPLE_C_EXTEND_H
/*
* This is an example of how to write C++ functions and make them usable with the qpdf C API. It
* consists of three files:
* - extend-c-api.h -- a plain C header file
* - extend-c-api.c -- a C program that calls the function
* - extend-c-api.cc -- a C++ file that implements the function
*/
#include <qpdf/qpdf-c.h>
/* Declare your custom function to return QPDF_ERROR_CODE and take qpdf_data and anything else you
* need. Any errors are retrievable through the qpdf C APIs normal error-handling mechanism.
*/
#ifdef __cplusplus
extern "C" {
#endif
QPDF_ERROR_CODE num_pages(qpdf_data qc, int* npages);
#ifdef __cplusplus
}
#endif
#endif /* EXAMPLE_C_EXTEND_H */

View File

@ -0,0 +1,30 @@
#!/usr/bin/env perl
require 5.008;
use warnings;
use strict;
chdir("extend-c-api") or die "chdir testdir failed: $!\n";
require TestDriver;
cleanup();
my $td = new TestDriver('extend-c-api');
$td->runtest("extend C API (good)",
{$td->COMMAND => "extend-c-api good.pdf"},
{$td->FILE => "good.out", $td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
$td->runtest("extend C API (bad)",
{$td->COMMAND => "extend-c-api bad.pdf"},
{$td->FILE => "bad.out", $td->EXIT_STATUS => 2},
$td->NORMALIZE_NEWLINES);
cleanup();
$td->report(2);
sub cleanup
{
unlink "a.pdf";
}

View File

@ -0,0 +1,5 @@
WARNING: bad.pdf: can't find PDF header
WARNING: bad.pdf: file is damaged
WARNING: bad.pdf: can't find startxref
WARNING: bad.pdf: Attempting to reconstruct cross-reference table
error: bad.pdf: unable to find trailer dictionary while recovering damaged file

View File

@ -0,0 +1 @@
not even a pdf file

View File

@ -0,0 +1 @@
num pages = 1

View File

@ -0,0 +1,64 @@
%PDF-2.0
1 0 obj
<<
/Pages 2 0 R
/Type /Catalog
>>
endobj
2 0 obj
<<
/Count 1
/Kids [
3 0 R
]
/Type /Pages
>>
endobj
3 0 obj
<<
/Contents 4 0 R
/MediaBox [ 0 0 612 792 ]
/Parent 2 0 R
/Resources <<
/Font << /F1 5 0 R >>
>>
/Type /Page
>>
endobj
4 0 obj
<<
/Length 44
>>
stream
BT
/F1 24 Tf
72 720 Td
(Potato) Tj
ET
endstream
endobj
5 0 obj
<<
/BaseFont /Helvetica
/Encoding /WinAnsiEncoding
/Subtype /Type1
/Type /Font
>>
endobj
xref
0 6
0000000000 65535 f
0000000009 00000 n
0000000062 00000 n
0000000133 00000 n
0000000277 00000 n
0000000372 00000 n
trailer <<
/Root 1 0 R
/Size 6
/ID [<42841c13bbf709d79a200fa1691836f8><b1d8b5838eeafe16125317aa78e666aa>]
>>
startxref
478
%%EOF

View File

@ -21,21 +21,23 @@
#define QPDF_C_H
/*
* This file defines a basic "C" API for qpdf. It provides access to a subset of the QPDF library's
* This file defines a basic "C" API for qpdf. It provides access to a subset of the QPDF library's
* capabilities to make them accessible to callers who can't handle calling C++ functions or working
* with C++ classes. This may be especially useful to Windows users who are accessing the qpdf DLL
* with C++ classes. This may be especially useful to Windows users who are accessing the qpdf DLL
* directly or to other people programming in non-C/C++ languages that can call C code but not C++
* code.
* code. Starting with qpdf 11.7, it is possible to write your own `extern "C"` functions that
* interoperate with the C API.
*
* There are several things to keep in mind when using the C API.
*
* Error handling is tricky because the underlying C++ API uses exception handling. See "ERROR
* HANDLING" below for a detailed explanation.
*
* The C API is not as rich as the C++ API. For any operations that involve actually
* manipulating PDF objects, you must use the C++ API. The C API is primarily useful for doing
* basic transformations on PDF files similar to what you might do with the qpdf command-line
* tool.
* The C API is not as rich as the C++ API. For many operations, you must use the C++ API. The C
* API is primarily useful for doing basic transformations on PDF files similar to what you
* might do with the qpdf command-line tool. You can write your own `extern "C"` functions in
* C++ that interoperate with the C API by using qpdf_c_get_qpdf and qpdf_c_wrap which were
* introduced in qpdf 11.7.0.
*
* These functions store their state in a qpdf_data object. Individual instances of qpdf_data
* are not thread-safe: although you may access different qpdf_data objects from different
@ -990,6 +992,23 @@ extern "C" {
QPDF_ERROR_CODE qpdf_remove_page(qpdf_data qpdf, qpdf_oh page);
#ifdef __cplusplus
}
// These C++ functions make it easier to write C++ code that interoperates with the C API.
// See examples/extend-c-api.
# include <functional>
# include <memory>
# include <qpdf/QPDF.hh>
// Retrieve the real QPDF object attached to this qpdf_data.
QPDF_DLL
std::shared_ptr<QPDF> qpdf_c_get_qpdf(qpdf_data qpdf);
// Wrap a C++ function that may throw an exception to translate the exception for retrieval using
// the normal QPDF C API methods.
QPDF_DLL
QPDF_ERROR_CODE qpdf_c_wrap(qpdf_data qpdf, std::function<void()> fn);
#endif
#endif /* QPDF_C_H */

View File

@ -1949,3 +1949,15 @@ qpdf_write_json(
});
return status;
}
std::shared_ptr<QPDF>
qpdf_c_get_qpdf(qpdf_data qpdf)
{
return qpdf->qpdf;
}
QPDF_ERROR_CODE
qpdf_c_wrap(qpdf_data qpdf, std::function<void()> fn)
{
return trap_errors(qpdf, [&fn](qpdf_data) { fn(); });
}

View File

@ -38,6 +38,13 @@ Planned changes for future 12.x (subject to change):
.. x.y.z: not yet released
11.7.0: not yet released
- Library Enhancements:
- Add C++ functions ``qpdf_c_wrap`` and ``qpdf_c_get_qpdf`` to the
C API to enable custom C++ code to interoperate more easily with
the the C API. See ``examples/extend-c-api``.
11.6.4: December 10, 2023
- Bug fixes: