Writing a C++ wrapper for a C library
Asked Answered
T

7

17

I have a legacy C library, written in an OO type form. Typical functions are like:

LIB *lib_new();
void lib_free(LIB *lib);
int lib_add_option(LIB *lib, int flags);
void lib_change_name(LIB *lib, char *name);

I'd like to use this library in my C++ program, so I'm thinking a C++ wrapper is required. The above would all seem to map to something like:

class LIB
{
    public:
         LIB();
         ~LIB();
         int add_option(int flags);
         void change_name(char *name);
...
};

I've never written a C++ wrapper round C before, and can't find much advice about it. Is this a good/typical/sensible approach to creating a C++/C wrapper?

Thrombin answered 3/5, 2010 at 21:14 Comment(0)
B
9

A C++ wrapper is not required - you can simply call the C functions from your C++ code. IMHO, it's best not to wrap C code - if you want to turn it into C++ code - fine, but do a complete re-write.

Practically, assuming your C functions are declared in a file called myfuncs.h then in your C++ code you will want to include them like this:

extern "C" {
   #include "myfuncs.h"
}

in order to give them C linkage when compiled with the C++ compiler.

Blakeley answered 3/5, 2010 at 21:20 Comment(14)
I disagree (but no downvote). For simple C libs, a wrapper is often unnecessary. However, for more complicated C libs, a lightweight C wrapper can be invaluable. One example springs to mind: Tibco has a very lightweight wrapper around Tibrv which is immensely helpful.Yolandayolande
@Nathan In some ways. MFC is a lightweight wrapper for the C Windows API. Whether it has added to the happiness of mankind is another issue :-)Blakeley
I agree with Nathan - wrapping C libraries of some complexity, especially third-party ones, often makes them more useful to C++ clients. It also enables one to implement features on top of those provided by the C library (caching comes to mind).Eldreeda
@Bojan Cool! Post answer here explaining to the OP how to do it!Blakeley
One major reason to wrap C functions is so that you can throw exceptions when errors arise.Ariannaarianne
@Winston Wrapping C code to use exceptions is almost impossible, in my experience. But if you've done it, please explain how.Blakeley
Good C headers should generally do the extern "C" thing internally when compiled as C++.Protrusive
WFC is a lightweight wrapper around the Windows API. MFC varies from lightweight to quite heavyweight indeed depending on which part you are talking about (for example message maps).Cappello
Wouldn't the C++ provide the advantage that the lib_free() will be called by the destructor when the wrapper object goes out of scope?Dentiform
@Neil In my experience, there aren't many "general" tips for wrapping C libraries. Throwing exceptions where C code returns errors is certainly doable. Exactly what is needed to accomplish that depends on the C library. Without support for exceptions, though, there isn't much point in wrapping.Eldreeda
@Bojan: Adding RAII for resources management around a C library seems very much worthy imho.Allude
@Neil It seem straightforward enough to throw exceptions when a C library indicates that an error has occurred. Of course, if you are called from the C library then its trickier.Ariannaarianne
One major reason to wrap C functions with C++ classes is to enable unit testing. See github.com/mlb5000/CFunctionWrapperGenerator for a way to generate these from C headersAnteater
@Dentiform The wrapper class shouldn't have a destructor unless it's doing deep copies.Hollinger
P
9

I usually only write a simple RAII wrapper instead of wrapping each member function:

class Database: boost::noncopyable {
  public:
    Database(): handle(db_construct()) {
        if (!handle) throw std::runtime_error("...");
    }
    ~Database() { db_destruct(handle); }
    operator db_t*() { return handle; }
  private:
    db_t* handle;
};

With the type conversion operator this can be used with the C functions:

Database db;
db_access(db, ...);  // Calling a C function with db's type conversion operator
Protrusive answered 4/5, 2010 at 0:1 Comment(0)
H
2

I think it only makes sense to write a wrapper if it makes the use of the library simpler. In your case, you're making it unnecessary to pass a LIB* around, and presumably it will be possible to create LIB objects on the stack, so I'd say this is an improvement.

Humpy answered 3/5, 2010 at 21:20 Comment(1)
It's still necessary to pass LIB::LIB objects around. And allocating LIB::LIB objects on stack will give you only auto semantics, as probably new C LIB object will be allocated on heap in construction. But despite this, C++ wrapper is still handy.Clot
A
2

That's generally how I would approach it. I would also not use char* but use std::string.

Alannaalano answered 3/5, 2010 at 21:20 Comment(0)
R
1

A C++ wrapper is not needed per se. There's nothing stopping you from calling the C functions in your code.

Ruminate answered 3/5, 2010 at 21:21 Comment(0)
N
1

I'd also look at renaming LIB to something a bit better, if nothing else "Lib"

Change Name is likely to be a getter setter...

so GetName(char *) SetName(char *)

and then look at changing it to std::string instead of char*, if its SetName(const std::string name) it will accept a char* as a parameter.

ie, slowly move to C++isms

Nicolanicolai answered 3/5, 2010 at 21:23 Comment(0)
H
0

Assuming that the C library's allocation/deallocation instances are create_instance and destroy_instance, and it exposes a function called call_function, and it does not provide an API for deep copying instances, this will work:

class Wrapper
{
public:
    Wrapper(): m_instance(create_instance(), destroy_instance) {}

    explicit operator bool() const
    {
        // null check
        return bool(m_instance);
    }

    void callFunction()
    {
        call_function(m_instance.get());
    }

private:
    std::unique_ptr<instance, decltype(&destroy_instance)> m_instance;
};
Hollinger answered 11/1, 2021 at 21:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.