39.3 C++ API

We offer an object-oriented reflection of the standard C API into C++. While C++ programs can certainly call the C API directly, we offer the C++ bindings not just because they offer an environment that experienced C++ codes may find more familiar, but also because they can hide some of the complexities involved.

This book does not go into detail on the C++ API, but in general, it is derived fairly simply (conceptually at least) from the C level one. To best illustrate the differences between the C and C++ APIs, let us consider an example. In C, we would load a page by calling:

fz_page *page = fz_load_page(ctx, doc, page_num);

The C++ equivalent would be:

mupdf::Page page = doc.load_page(page_num);

This simple example highlights some important differences.

Firstly, where (almost) every C level call takes an fz_context argument as the first parameter, this is handled for users automatically at the C++ level.

Secondly, where C API functions operate on a particular object (in this example, a page), this will typically be passed as the second parameter (after the context). In the C++ translation, these objects are instances of classes, and functions become methods on these classes. That is to say that while the C API is not explicitly object-oriented (as the language does not natively support that concept) the generated C++ API is.

Next, where the C version would need to be wrapped in a fz_try/fz_catch construct to handle any exceptions thrown, the C++ version only needs to handle standard C++ exceptions. This can reduce code as the C++ program may already be handling such exceptions.

Finally, by passing objects by value, the C++ version handles reference counting automatically. Where programmers using the C version are required to track references manually using calls like fz_keep_page and fz_drop_page, C++ programmers never need to call these as this is taken care of behind the scenes.

This C++ API is generated using a script that uses the Clang/llvm parser to analyse the C API so it is largely automatic. Often, changes/additions to the C API will convert seamlessly to C++ with no additional changes. For more complex cases, some tweaking of the scripts may be required. This automatic conversion happens based upon the API exposed in the public C headers; by default any new functions or types defined there will be reflected into the C++.

A few other conveniences arise with the C++ wrappers. Where appropriate generated classes have support for iteration - for instance, the contents of an StextPage can be navigated using familiar C++ operations:

void show_stext(mupdf::StextPage &page) 
{ 
   for (mupdf::StextPage::iterator it_page: page) 
   { 
      mupdf::StextBlock block = *it_page; 
      for (mupdf::StextBlock::iterator it_block: block) 
      { 
         mupdf::StextLine line = *it_block; 
         for (mupdf::StextLine::iterator it_line: line) 
         { 
            mupdf::StextChar c = *it_line; 
            fz_stext_char *c = stextchar.m_internal; 
            std::cout << "StextChar(" 
                  << "c=" << c->c 
                  << " color=" << c->color 
                  << " origin=" << c->origin 
                  << " quad=" << c->quad 
                  << " size=" << c->size 
                  << " font_name=" << c->font->name 
                  << "\n"; 
         } 
      } 
   } 
}

This example also serves to illustrate another convenience; simple classes (‘POD’ classes in C++ parlance) are given a to_string method and an << operator that converts them to a printable form. This enables structures to quickly be printed as seen with the origin and quad fields above.