Envision, Create, Share

Welcome to HBGames, a leading amateur game development forum and Discord server. All are welcome, and amongst our ranks you will find experts in their field from all aspects of video game design and development.

Creating a C wrapper for a C++ DLL

A lot of libraries these days are written in C++, but APIs such as the Win32 API for Ruby 1.8 require C-style function interfaces.
So here's a little tutorial on creating a C wrapper for a C++ library.

Of course, this is Windows only as it's Win32 DLLs we're handling.

I'm going to start with a C++ library

So we make a library with a header file as normal:
Header
C++:
<span style="color: #339900;">#ifndef __NAME_H__

<span style="color: #339900;">#define __NAME_H__

 

<span style="color: #339900;">#include <string>

 

class __declspec( dllexport ) xiName {

public:

                    xiName();

                    ~xiName();

    void            Set( const char * const _name );

    const char *    Get() const;

    void            Print() const;

private:

    std::string name;

};

 

<span style="color: #339900;">#endif
Implementation
C++:
<span style="color: #339900;">#include "Name.h"

 

<span style="color: #339900;">#include <iostream>

 

using namespace std;

 

xiName::xiName() {

    Set( <span style="color: #666666;">"" );

}

 

xiName::~xiName() {

    Set( <span style="color: #666666;">"" ); // Don't really need to do this as name is part of the structure

}

 

void xiName::Set( const char * const _name ) {

    name = string( _name );

}

 

const char * xiName::Get() const {

    return name.c_str();

}

 

void xiName::Print() const {

    <span style="color: #0000dd;">cout << name << endl; // Cursed cout streamer!

}

Excuse the lack of comments, this is simply a C++ class with a constructor and a deconstructor (Which is rather pointless in this class' case) with 3 methods; Set, Get and Print.

Probably the most pointless class in the world as simply having an std::string name would suffice, but it's an example.

So the first step is to create another C++ file to bind our C stuff to, this will be a C++ source file but the code in it will be written in C.

I'll call my files API.h and API.cpp
Header
C:
<div class="c" id="{CB}" style="font-family: monospace;"><ol><span style="color: #339933;">#ifndef __API_H

<span style="color: #339933;">#define __API_H

 

// TODO: write stuff

 

<span style="color: #339933;">#endif
Implementation
C:
<div class="c" id="{CB}" style="font-family: monospace;"><ol><span style="color: #339933;">#include "API.h"

 

<span style="color: #339933;">#include "Name.h" // Include the C++ class header we're binding

 

// TODO: write stuff

Alright, so we're ready to analyse what we need.

First thing, get your methods:
  • Constructor
  • Destructor
  • Set
  • Get
  • Print
So we will have 5 C functions

These are what we will be binding into C exports.
The way to translate these into C is to have the class object parsed as an argument in each function, however we don't want to reference the class in the C API because the class isn't accessible outside C++, so we forward-declare some prototype C structures to replace the class.

Modify the header file of API.h so it looks like this:
Header
C++:
<span style="color: #339900;">#ifndef __API_H

<span style="color: #339900;">#define __API_H

 

typedef struct xiName_s xiName_t; // Forward declare class

 

// In C++ declare the following functions to be exported C-style (No mangled names)

<span style="color: #339900;">#ifdef __cplusplus

extern <span style="color: #666666;">"C" {

<span style="color: #339900;">#endif

 

    __declspec( dllexport ) xiName_t *      Name_New(); // Constructor

    __declspec( dllexport ) void            Name_Delete( xiName_t * const self ); // Destructor

    __declspec( dllexport ) void            Name_Set( xiName_t * const self, const char * const _name );

    __declspec( dllexport ) const char *    Name_Get( const xiName_t * const self );

    __declspec( dllexport ) void            Name_Print( const xiName_t * const self );

 

<span style="color: #339900;">#ifdef __cplusplus

}

<span style="color: #339900;">#endif

 

<span style="color: #339900;">#endif
Look at the typedef, it has no implementation defined yet, it is a forward-declared structure so only POINTERS to this structure are acceptable in this header file, this is what we need to avoid including the class's header file.

The extern "C" is C++ specific, it tells the compiler to not mangle the names of the exported functions, so in the DLL file they can be easily identified by any API looking for them. Wrap the extern "C" part in #ifdef __cplusplus if you plan to use this header file in any C programs.

You can see that the dllexport is now needed for every function we want to export and now every function has the class name, "Name", infront of the original method name, this helps anyone using the API understand that the functions all belong to the same collection of operations.

The self argument is the reference we need to the "class" instance, this is core to OOP-style C coding.

So onto the implementation, where things go a bit crazy. Remember this is C++ so, uh, anything goes?
C++:
<span style="color: #339900;">#include "API.h"

 

<span style="color: #339900;">#include "Name.h" // Include the C++ class header we're binding

 

// Override the xiName typedef with an implementation

// As this is C++, the structure can inherit the class, gaining the full size of the parent class and it's public/protected methods

typedef struct xiName_s : public xiName {

} xiName_t;

 

xiName_t * Name_New(); {

    return <span style="color: #0000dd;">new xiName_t; // As the struct inherits the base class, xiName's default constructor will be called

}

 

void Name_Delete( xiName_t * const self ) {

    <span style="color: #0000dd;">delete( self ); // This will also call delete for xiClass

}

 

void Name_Set( xiName_t * const self, const char * const _name ) {

    self->Set( _name );

}

 

const char * Name_Get( const xiName_t * const self ) {

    return self->Get();

}

 

void Name_Print( const xiName_t * const self ) {

    self->Print();

}

And there we go.

If you were to check sizeof( xiName_t ) in C with sizeof( xiName ) in C++ you'll see that they are exactly the same size because xiName_t simply inherits all of xiName.

Now this library (When compiled) is fully C compatible and can be used as normal.
If C compatibility isn't a concern, you can certainly change that typedef struct xiName_s xiName_t; to a forward-declare of the C++ class: class xiName; but as classes don't exist in C, the header file loses compatibility.

So if we were to go into an engine that can use C bound DLL functions, such as RGSS, we can call this C++ class through the new C functions, just remember to string/array pack/unpack that structure in Ruby and instantiate it's bytes Ruby-side, where you'll have to modify the constructor to take said bytes as an argument:
C++:
void Name_NewRuby( xiName_t * const bytes ); {

    xiName_t * const instance = <span style="color: #0000dd;">new xiName_t;

    

    <span style="color: #0000dd;">memcpy( ( void * )bytes, ( void * )instance, <span style="color: #0000dd;">sizeof( xiName_t ) ); // Copy the bytes from our real instance to the Ruby byte array

    

    <span style="color: #0000dd;">delete( instance ); // Clean up the instance (Must not deconstruct any pointers contained in the class!!!)

}
And you won't need to deconstruct it as Ruby's garbage collect will take care of it. Of course, you may need to create a function to clean up any memory that will be leaked on the C-side, such as any pointers contained within the class, which is where things get messy as you'll have to promote these pointers to public to be able to delete them.

C++:
void Name_DeleteRuby( xiName_t * const bytes ); {

    <span style="color: #0000dd;">delete( bytes->pointerToAnotherObject ); // needs to be public

    <span style="color: #0000dd;">delete( bytes->anotherPointer ); // needs to be public

    // Must not call delete( bytes ) or Ruby's garbage collector will panic when it hits that memory block!

}


I hope this has inspired people to mess around with this stuff.
 
So...essentially does this let me write c++ programs that will run in a ruby enviornment? If so, why not just write it in say, RGSS, to begin with?
 
Bit of a vague answer but there are many things you can't do in RGSS that you can do in C++. I've never got much into it as I pretty much dropped RPG Maker when I started learning C/C++.
 
Necrile":3o2ajypk said:
So...essentially does this let me write c++ programs that will run in a ruby enviornment? If so, why not just write it in say, RGSS, to begin with?
You've dodged the point a bit, the thing with C++ DLLs is that there is some compatibility lost with C programs, you can only use a C++ DLL with a C++ program, however if you bind it with some pure-C code you can get that happening in applications that only support C-style binding, Ruby's Win32API expects DLL functions to have a C style binding, it's just an example.

Implementing large features in RGSS will result in slow-down with the Ruby virtual machine having to churn through each byte code generated from the large class. The virtual machine has to read the byte code generated in RGSS, grab all the resources associated with the instruction, decode the instruction into native calls (Which can multiply the size of the byte code), run the instruction, push the result into the destination resources, so adding code to RGSS can become inefficient and slow-downs will be expected. This is less of a problem with RGSS3 where it supports real multi-threading and less strain is put on the graphics rendering, but it will eventually happen. (It can be compared to the "Java Problem", where we can't improve Java any further, the only thing we can do is throw more powerful machines at it to make it go faster, Ruby will eventually hit the same bottleneck).

As for use-cases; gamepad input, mouse input, keyboard input modules all require Win32 extensions to RGSS as the run-time only supports rebinding keys, rather than the full keyboard, and has no support for mouse and no support for gamepads (Analogues, haptic feedback).
If you were to gather all these modules, bind them together in a C/C++ program and write a clever C binding to RGSS then you could make a tidy package with a small amount of RGSS code to add features that aren't possible in raw RGSS.

EDIT: For the last part about retaining a class instance in both Ruby and C++, the trick I use is I return the pointer value as a ptrdiff_t to Ruby, where I ship that around as my native-object handle and parse that as an argument for the destructor, there is some security risk in this (As it gives the RGSS code the ability to deconstruct any pointer), but if you make a validation map to check against this can be avoided.
 

Thank you for viewing

HBGames is a leading amateur video game development forum and Discord server open to all ability levels. Feel free to have a nosey around!

Discord

Join our growing and active Discord server to discuss all aspects of game making in a relaxed environment. Join Us

Content

  • Our Games
  • Games in Development
  • Emoji by Twemoji.
    Top