Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I use my C++ DLLs with a C application?

The environment is Windows, MS Visual Studio 2012. How can I use my DLL libraries written in C++ in a C (not C++) application? In my DLL I used namespaces, classes, etc. For example I wrote two simple projects:

  • DLL project
  • EXE project (uses the DLL)

Both are written in C++. Can anybody show me a similar application written in C that uses C++ libraries?

Thank you.

like image 728
Andrey Bushman Avatar asked Jun 30 '13 17:06

Andrey Bushman


2 Answers

The solution to this is to write a C interface to your DLL in C++ (either as part of the DLL, or as a separate DLL), and then use that C interface from your C code.

Be sure that your C interface consists of functions which are all extern "C".

Edit: As SigTerm noted in the comments, it is not a good idea to make the functions stdcall (they are not by default, but some people copy that from WinAPI functions), because that causes trouble when trying to use the functions from other languages.

Here are the challenges, and how they can be solved:

Your class is in a namespace

Normally, you can export C++ classes in C by just declaring an opaque struct ClassName*. However this is not possible for classes in namespaces since C doesn't know the concept of a namespace. There are several ways to solve it:

  • The simple way: Just have your C interface hand out and accept void pointers, maybe thinly disguised by typedefs to the actual types.

    Advantage: Trivial to implement. All you need is a type cast at each entry point.

    Disadvantage: The C interface will not be type safe. It is easy to pass the wrong pointer to your interface and mess things up.

  • The intrusive way: In your C++ library, define global scope base classes for each C++ class you want to expose, and derive the actual class from that. Note that the global scope base classes may be empty because you'll static_cast to the actual type anyway before using it.

    Advantage: Easy to implement, typesafe.

    Disadvantage: A lot of work if you have many classes. Also, it may not be possible if you can't change all the classes which you want top expose that way. In addition, being base classes, you cannot separate them out so that only C clients need them. Every C++ client will get the global classes, even if it doesn't otherwise include the C interface.

  • Using handles: Use an integer handle, which actually contains the pointer cast to int. If your compiler supports it, use intptr_t from stdint.h because it is guaranteed to be large enough no matter what platform you're on. Otherwise, it's probably best to use a long to be on the safe side.

    Advantage: Easy to implement, slightly more typesafe than void*, a familiar interface style for any C programmer who has ever dealt with low level functions like OS file handling.

    Disadvantage: Not very typesafe; you still can pass the wrong type of handle to your function.

  • Using "encapsulated" handles: For each type you want to expose, define at global scope a C struct containing as only member either a void* or an integer handle.

    Advantage: Relatively easy implementation (although not as easy as void* or raw handle), typesafe (as long as the C code doesn't mess with the struct member).

    Disadvantage: Possibly makes it harder for the C compiler to optimize. But that depends very much on the C compiler.

My suggestion would be to go with the encapsulated handles, unless it turns out to have performance issues, in which case I'd use integer handles.

The class interface accepts and returns std::string

There's no way for C code to work with std::string, and you certainly don't want to do the work of wrapping the interface to that in C (and your C interface users would not like you for that anyway). Therefore it is mandatory to use char* / char const* in your C interface.

Passing strings to your library

The case of passing strings to your library is easy: You simply pass a char const* in and let the constructor of std::string do the rest.

Returning strings from your library

This is the complicated case. You can't just return the result of c_str because it will leave you with a dangling pointer when the temporary std::string gets destructed. So you have basically the following options:

  • Store a static std::string (so it won't be deallocated on return), copy the result of your function call into that, and return c_str).

    Advantage: Simple to implement, guaranteed to give you the full string.

    Disadvantage: It is not thread safe, also the user has to remember to immediately copy the content to somewhere, or the next call to the function will invalidate the data, or even worse, the pointer.

  • Allocate a character array of appropriate size in your wrapper function, copy the contents of the string into that array, and return a pointer to it.

    Advantage: You are guaranteed to return the full string (assuming memory allocation didn't fail).

    Disadvantage: It is a well-known problem that deallocating memory in another DLL than the one which allocated it doesn't work correctly. Therefore you'd have to provide a deallocation interface, and the user of your C interface would have to remember to always use that instead of simply freeing the memory.

  • Let your user pass a character array and a length, and use strncpy to copy the string data to the character array.

    Advantage: You don't have to do memory management for the string; that's the respoonsibility of the user. Also, if the user wants the string to be written in a specific place, he can simply pass the address to the wrapper instead of making another copy.

    Disadvantage: If the array the user supplied is not long enbough, you'll get a truncated string.

My suggestion: Given the problems with cross-DLL memory allocation, I'd say to use the third way and accept the risk of to truncated strings (but make sure you have a way to report the error to your users).

You have a member enum in one of your classes

That one is easy: Provide an identical (except for the name) global-scope enum definition fin your C interface. Since identical enum definitios give identical values for the identifiers, simply casting the values from your in-class enum to the global one and back should work perfectly. The only problem is that whenever you update the in-class enum, you have to remember to also update the out-of-class enum.

You have public members of type std::vector

Well, to start with, that's a bad interface decision anyway. The way to handle it is basically the same as you actually should handle those vectors in C++ as well: Provide an iterator interface to them.

Now C++ iterators don't work well in C, but then, in C there's also no reason to adhere at the C++ iterator style.

Given that the interface members are std::vector, another option would be to give out pointers to the first element of the vector. However those would get invalidated the next time your vector is resized, so that would be an option only for very restricted cases.

You have a print function that takes an std::ostream and a reference to an object.

Of course std::ostream is not available to C code. In C, files are accessed using FILE*. I think the best option here is to use an iostream class which wraps around a FILE* and allows you to construct from one. I don't know if that is provided by MSVC++, but if not, Boost has AFAIK such a stream.

Your print wrapper would then take a FILE* and an object handle (or void pointer, or whatever option you chose for representing your objects in C), construct a temporary ostream object from the FILE pointer, extract the pointer to the object from the handle, and then call print on the pointed-to object.

Additional consideration: Exceptions

Don't forget to catch exceptions and turn it into errors C can handle. The best option usually is to return an error code.

Example header for the C interface

The following is a possible C interface header for your linked example library

/* somelib_c_interface.h */
#ifndef SOMELIB_C_INTERFACE_H
#define SOMELIB_C_INTERFACE_H

#ifdef __cplusplus
extern "C" {
#endif

#include <stdint.h>
#include <stdlib.h>

/* typedef for C convenience */
typedef struct
{
  intptr_t handle;
} some_namespace_info;

typedef struct
{
  intptr_t handle;
} some_namespace_employee;

/* error codes for C interface */
typedef int errcode;
#define E_SOMELIB_OK 0              /* no error occurred */
#define E_SOMELIB_TRUNCATE -1       /* a string was created */
#define E_SOMELIB_OUT_OF_MEMORY -2  /* some operation was aborted due to lack of memory */
#define E_SOMELIB_UNKNOWN -100      /* an unknown error occurred */

/* construct an info object (new)
errcode some_namespace_info_new(some_namespace_info* info, char const* name, char const* number);

/* get rid of an info object (delete) */
errcode some_namespace_info_delete(some_namespace_info info);

/* Some_namespace::Info member functions */
errcode some_namespace_info_get_name(some_namespace_info info, char* buffer, size_t length);
errcode some_namespace_info_set_name(some_namespace_info info, char const* name);

errcode some_namespace_info_get_number(some_namespace_info info, char* buffer, size_t length);
errcode some_namespace_info_set_number(some_namespace_info info, char const* name);

/* the employee class */

/* replicated enum from employee */
enum some_namespace_employee_sex { male, female };

errcode some_namespace_employee_new(some_namespace_employee* employee,
                                    char const* name, char const* surname, int age,
                                    some_namespace_employee_sex sex);

errcode some_namespace_employee_delete(some_namespace_employee employee);

errcode some_namespace_employee_get_name(some_namespace_employee employee, char* buffer, size_t length);
errcode some_namespace_employee_set_name(some_namespace_employee employee, char const* name);

errcode some_namespace_employee_get_surname(some_namespace_employee employee, char* buffer, size_t length);
errcode some_namespace_employee_set_surname(some_namespace_employee employee, char const* name);

/* since ages cannot be negative, we can use an int return here
   and define negative results as error codes, positive results as valid ages
   (for consistency reason, it would probably be a better idea to not do this here,
   but I just want to show another option which sometimes is useful */
int some_namespace_employee_get_age(some_namespace_employee employee);

errcode some_namespace_employee_set_age(some_namespace_employee employee, int age);

errcode some_namespace_employee_get_set(some_namespace_employee employee,
                                        enum some_namespace_employee_sex* sex);
errcode some_namespace_employee_set_sex(some_namespace_employee employee,
                                        enum some_namespace_employee_sex sex);

typedef struct
{
  intptr_t handle;
} info_iter;

info_iter_delete(info_iter iterator);

some_namespace_info info_iter_get(info_iter iterator);
void info_iter_next(info_iter iterator);
bool info_iter_finished(info_iter iterator);

info_iter some_namespace_employee_phones(some_namespace_employee employee);
info_iter some_namespace_employee_emails(some_namespace_employee employee);
info_iter some_namespace_employee_sites(some_namespace_employee employee);

errcode some_namespace_print(FILE* dest, some_namespace_employee employee);

#ifdef __cplusplus
}
#endif

#endif // defined(SOMELIB_C_INTERFACE_H)
like image 55
celtschk Avatar answered Sep 28 '22 11:09

celtschk


You will have to create C wrapper functions which can pass opaque pointers to your class instances.
Here is what seems like a reasonable example: http://www.daniweb.com/software-development/cpp/threads/355990/strategy-for-consuming-c-dlls-in-c

Quote from the link:

typedef Foo* FOO_HDL;
extern "C" { 
__declspec(dllexport) FOO_HDL FooCreate() {
  return new Foo();
};
__declspec(dllexport) void FooDestroy(FOO_HDL obj) {
  delete obj;
};
__declspec(dllexport) double FooGetValue(FOO_HDL obj) {
  return obj->GetValue();
};
__declspec(dllexport) void FooSetValue(FOO_HDL obj, double NewValue) {
  obj->SetValue(NewValue);
};
};
like image 39
MK. Avatar answered Sep 28 '22 10:09

MK.