Welcome Guest ( Log In | Register )

[ Big| Medium| Small] -



Post new topic Reply to topic  [ 4 posts ] 
    Xilef
  Sun Nov 24, 2013 12:10 am
User avatar
Staff

Big Dumb Guy

Location: UK
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
Expand to see the code.

Implementation
Expand to see the code.


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
Expand to see the code.

Implementation
Expand to see the code.


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
Expand to see the code.

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?
Expand to see the code.


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:
Expand to see the code.

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.

Expand to see the code.



I hope this has inspired people to mess around with this stuff.


Top Top
Profile      
 

    Necrile
  Mon Nov 25, 2013 3:07 am
User avatar
Sponsor

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?


Top Top
Profile      
 

    Amy
  Mon Nov 25, 2013 11:55 am
User avatar
Staff

Big Dumb Guy
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++.


Top Top
Profile      
 

    Xilef
  Mon Nov 25, 2013 12:37 pm
User avatar
Staff

Big Dumb Guy

Location: UK
Necrile wrote:
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.


Top Top
Profile      
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 4 posts ] 


Who is online

Users browsing this forum: No users and 2 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

We are an independent, not-for-profit game making community.
Homepage
Board Index
About Us
Downloadable Games
Free Browser Games
Games in Development
RPG Maker Support
Game Maker Support
Construct 2 Support
HBGames the eZine
Advanced RPG Maker
Site Announcements
Powered by phpBB © phpBB Group