Introduction To Apache Portable Runtime (APR) for C++

So this is going to be an introduction to working with apr using c++ for command line argument processing.

This assumes that you have some basic knowledge of C++. If you want to just jump into it, then click here I also put all of my code on GitHub if you really want to just see how to do it quickly.

Introduction

Let’s jump into it with an example C++ program:

#include <iostream>
using namespace std;

int main(int argc, char *argv[])
{
  cout << "Hello World\n";
}

Now, this should be a very simple program to understand. We simply print "Hello World" to the screen.

I'm sure you're familiar with adding the int argc and char *argv[] in your main implementation. You probably know that argc stands for the total amount of command line arguments (including the program name). Likewise, argv is a two dimensional array containing the corresponding values.

Let's go ahead and print those:

#include <iostream>
using namespace std;

int main(int argc; char *argv[])
{
  for(int x = 0; x < argc; x++)
  {
    cout << argv[x] << endl;
  }
}

Now if we compile and run our program, like so, we should see output as follows:

./a.out 1 2 3 4 5
./a.out
1
2
3
4
5

Of course, you could use some if statements to parse and handle your command line options, but that is complex and hard.

Let's use a library! Now, if you're on Linux, or some other OS that has POSIX libraries, then you have getopt.h.

This is an older tool, but it's super useful. Let's say that we have a program that needs the following:

  1. -h -- will display help info
  2. -v -- will display version info
  3. -n VALUE -- will take in a name and requires a value
  4. -o [VALUE] -- will take in a file name and is optional

Let's write the code first and break it down:

#include <unistd.h>
#include <iostream>
#include <string>

#define VERSION_INFO "0.0.1"

using namespace std;

void show_help(char *prog_name)
{
  cout << prog_name << endl;
  cout << "-h\tDisplay this help message and exit\n";
  cout << "-v\tDisplay version info and exit\n";
  cout << "-o[VALUE]\tAn output file\n";
  cout << "-n VALUE\tA name\n"; 
  exit(EXIT_SUCCESS);
}

void show_version()
{
  cout << VERSION_INFO << endl;
  exit(EXIT_SUCCESS);
}

int main(int argc, char *argv[])
{
 char opt;
 
 bool have_o = false, have_n = false;
 string o_value, n_value;

 while ((opt = getopt(argc, argv, "hvn:o::")) != -1)
 {
     switch (opt) 
     {
       case 'h':
         show_help(argv[0]);
         break;
       case 'v':
         show_version();
         break;
       case 'o':
         have_o = true;
         if(optarg)
         {
           o_value = optarg;
         }
         break;
       case 'n':
         if(optarg)
         {
           have_n = true;
           n_value = optarg;
         }
         else
         {
           cerr << "No argument for -n!";
           exit(EXIT_FAILURE);
         }
         break;
     }
 }

 if(have_o)
 {
   cout << "O's value " << o_value << endl;
 }
 if(have_n)
 {
   cout << "N's value " << n_value << endl;
 }
 return EXIT_SUCCESS;
}

Let's run it!

./a.out -h
./a.out
-h	Display this help message and exit
-v	Display version info and exit
-o[VALUE]	An output file
-n VALUE	A name
./a.out -v
0.0.1
./a.out -n ceneblock
N's value ceneblock
./a.out -o
O's value 
./a.out -ofile
O's value file

Okay, so we can see that it works! Let's look at the most important part of the code:

 char opt;
 
 bool have_o = false, have_n = false;
 string o_value, n_value;

 while ((opt = getopt(argc, argv, "hvn:o::")) != -1)

getopt() takes in int argc, char **argv, and char *optstring.

It's the optstring that's important. It's a sequence of characters that for each letter, opt is set accordingly.

You'll also see that the n has a : after it. This means that there is a required argument. See what happens if you run the program without it!

After that, you'll see o has two : after it. This means that it's optional.

This is fine and great, but what if you want to write your program and have it compile on Windows or an OS without POSIX? Well, you could port POSIX (use Cygwin or MinGW for Windows) to your OS or you could use Apache's Portable Runtime!

Installing

I'm on OpenSuSE Linux and it came installed by default, but for other distro's searching for apr-devel .

For Windows, here is Apache's guide.

I'm going to assume that you can figure out how to install apr and link it. This is article is mostly to show how easy it is to code.

API

Here is the API. Now just giving it to you wouldn't be too helpful, but it's important to know where to get info.

If we click on any of the functions or data types, then we can see more info about them. Let's take a look at apr_status_t apr_getopt (apr_getopt_t *os, const char *opts, char *option_ch, const char **option_arg)

We can see that we need to provide an apr_getopt_t. Well, what exactly is that?

From that link we can see it is:

Structure to store command line argument information.

Luckily, we don't need to fill this out manually, but we do need it to have valid data, from that link, we need to call:

apr_status_t apr_getopt_init(apr_getopt_t **os, apr_pool_t *cont, int argc,const char argv)

Now here, the apt_getopt_t is going to be our return type. So we'll want to declare it:

apt_getopt_t *opt;

Next what is this apt_pool_t? Well, it's a fundamental data type for apr. You can read about related functions from that link, but really, we need to do the following:

apr_status_t rv; ///the return value datatype.
apr_pool_t *pool;
apr_pool_create(&pool, NULL);

And just like that, we've created a new memory pool. Don't worry too much about it.

Something about APR is that it needs a special function call before we do anything. This call is apr_status_t apr_initialize(void).

So right after the bracket in main, let's call it (don't worry, I'll summarize with final code at the end).

int main(int argc, char *argv[])
{
  apt_initialize();

Likewise, we need to call a special terminator before quitting:

 apr_terminate();
 return EXIT_SUCCESS;
}

Okay, now that is over, let's jump back to apr_getopt_init, the next options are obvious, argc and argv.

So at this point, we've doing our initalization, memory pool allocation, and getopt_t initalization. Now we can get to our actual apt_getopt call.

If we look at it, it is actually very similar to our POSIX getopt:

opts A string of characters that are acceptable options to the program. Characters followed by ":" are required to have an option associated

So let's copy our POSIX optstring in and see what happens!

Here is our entire code:

#include <apr_general.h>
#include <apr_getopt.h>
#include <iostream>
#include <string>

#define VERSION_INFO "0.0.1"

using namespace std;

void show_help(char *prog_name)
{
  cout << prog_name << endl;
  cout << "-h\tDisplay this help message and exit\n";
  cout << "-v\tDisplay version info and exit\n";
  cout << "-o[VALUE]\tAn output file\n";
  cout << "-n VALUE\tA name\n"; 
  exit(EXIT_SUCCESS);
}

void show_version()
{
  cout << VERSION_INFO << endl;
  exit(EXIT_SUCCESS);
}

int main(int argc, char *argv[])
{
    apr_initialize();

    apr_status_t rv;
    apr_getopt_t *opt;
    apr_pool_t *pool;
   
    char optch;
    const char *optarg;
 
    bool have_o = false, have_n = false;
    string o_value, n_value;
 
    rv = apr_pool_create(&pool, NULL);
    if(rv != APR_SUCCESS)
    {
      cerr << "Error in apr_pool_create\n";
    }

    rv = apr_getopt_init(&opt, pool, argc, argv);
    if(rv != APR_SUCCESS)
    {
      cerr << "Error in apt_getopt_init\n";
    }

    while ((rv = apr_getopt(opt, "hvn:o::", &optch, &optarg)) == APR_SUCCESS) 
    {
     switch (optch) {
       case 'h':
         show_help(argv[0]);
         break;
       case 'v':
         show_version();
         break;
       case 'o':
         have_o = true;
         if(optarg)
         {
           o_value = optarg;
         }
         break;
       case 'n':
         if(optarg)
         {
           have_n = true;
           n_value = optarg;
         }
         else
         {
           cerr << "No argument for -n!";
           exit(EXIT_FAILURE);
         }
         break;
      }
    }
    if (rv != APR_EOF) {
        printf("bad options\n");
    }

    if(have_o)
   {
     cout << "O's value " << o_value << endl;
   }
   if(have_n)
   {
     cout << "N's value " << n_value << endl;
   }

    apr_terminate();
    return EXIT_SUCCESS;
}

Now how in the world do we compile this? To be honest, I don't know! However, there is a helper program to help us out!

apr-1-config 
Usage: apr-1-config [OPTION]

Known values for OPTION are:
  --prefix[=DIR]    change prefix to DIR
  --bindir          print location where binaries are installed
  --includedir      print location where headers are installed
  --cc              print C compiler name
  --cpp             print C preprocessor name and any required options
  --cflags          print C compiler flags
  --cppflags        print C preprocessor flags
  --includes        print include information
  --ldflags         print linker flags
  --libs            print additional libraries to link against
  --srcdir          print APR source directory
  --installbuilddir print APR build helper directory
  --link-ld         print link switch(es) for linking to APR
  --link-libtool    print the libtool inputs for linking to APR
  --shlib-path-var  print the name of the shared library path env var
  --apr-la-file     print the path to the .la file, if available
  --apr-so-ext      print the extensions of shared objects on this platform
  --apr-lib-target  print the libtool target information
  --apr-libtool     print the path to APR's libtool
  --version         print the APR's version as a dotted triple
  --help            print this help

When linking with libtool, an application should do something like:
  APR_LIBS="`apr-1-config --link-libtool --libs`"
or when linking directly:
  APR_LIBS="`apr-1-config --link-ld --libs`"

An application should use the results of --cflags, --cppflags, --includes,
and --ldflags in their build process.

As it ays at the bottom, we need:

  • --cppflags
  • --include
  • --ldflags

For me, the results are as follows:

apr-1-config --cppflags --includes --ldflags
 -DLINUX -D_REENTRANT -D_GNU_SOURCE -I/usr/include/apr-1

So I'll compile with those options:

g++ apr.cpp -DLINUX -D_REENTRANT -D_GNU_SOURCE -I/usr/include/apr-1

Now, let's test it like we did before:

./a.out -h
./a.out
-h	Display this help message and exit
-v	Display version info and exit
-o[VALUE]	An output file
-n VALUE	A name
./a.out -v
0.0.1
./a.out -n ceneblock
N's value ceneblock
./a.out -ofile
O's value file
./a.out -o
a.out: option requires an argument -- o
bad options

Uh oh! Despite our o::, we aren't able to have our optional argument like our original requirements are!

Is apr not a good solution?

Well....no. apr is a good solution, it's that I lied to you earlier. the o:: is actually a GNU extension. If we really want optional arguments, then we'll need to use getopt_long.

That, for right now, is a story for another day. This should be enough to at least get you started with apr. 😉

All code and a makefile is here

This entry was posted in Computer Science and tagged , , , , , , . Bookmark the permalink.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.