Interfacing with C++ Classes

BlitzMax Forums/BlitzMax Programming/Interfacing with C++ Classes

Scaremonger(Posted 2008) [#1]
I found the example below on the forums (which I cannot seem to find again), but when I run it, Blitzmax unloads itself. I cannot figure out what is wrong...

class.cpp
class MyClass {
public:
   MyClass();
   void DoSomething();
}

MyClass::MyClass() {}

void MyClass::DoSomething() {}


class.bmx
SuperStrict

Import "class-glue.cpp"

Extern "C"
  Function create_new_myclass:Byte Ptr()
  Function myclass_dosomething(myclass:Byte Ptr)
End Extern

Local b:Byte Ptr = create_new_myclass()

myclass_dosomething(b)


class-glue.cpp
#include "class.cpp";

extern "C" {

  MyClass * create_new_myclass();
  void myclass_dosomething(MyClass * myclass);
}

MyClass * create_new_myclass() {
    return new MyClass();
}

void myclass_dosomething(MyClass * myclass) {
    myclass->DoSomething();
}



Brucey(Posted 2008) [#2]
Your "class.cpp" would normally be instead, "class.h" - a header file.

You also don't need a semi-colon at the end of the #include part.
#include "class.h"


Otherwise, it looks okay.


Scaremonger(Posted 2008) [#3]
Cheers Brucey, but it still bombs out !!


Scaremonger(Posted 2008) [#4]
Fixed it. All it was, was a stray semicolon in class.cpp after the definition of Myclass. Doh! Always something simple eh?

class.cpp
class MyClass {
public:
   MyClass();
   void DoSomething();
};

MyClass::MyClass() {}

void MyClass::DoSomething() {}


Thanks everyone.


Brucey(Posted 2008) [#5]
Ah yes, I'm often missing the semi-colon at the end of a class definition.

btw, you really should make that a header file, rather than a .cpp - for standard coding style sake. But of course, it's entirely up to you :-p


DStastny(Posted 2008) [#6]
Let me share a subtly different approach that I find works better than the C style glue. It is a way you can still have the flavor of the C++ objects. And you can even use it to implement polymorphic behavior.

Here is simple slapped together C++ code. @Brucey ignore its just a single CPP file :)

fun.cpp
#include <iostream>
using namespace std; 
 
class IClassInterface {
public:
  virtual int Add(int x, int y) =0;
};


class ClassImplemenation : public IClassInterface {
 public:
	ClassImplemenation() {cout << "Creating Class\n"; }
	virtual ~ClassImplemenation() { cout << "Destroying\n"; }
	virtual int Add(int x, int y) { cout << "Adding "<<x<<"+"<<y<<"\n"; return x+y; }
};

extern "C" {
   IClassInterface* Create_IClassInterface()
   {
	return new ClassImplemenation(); 
   }	 
   
   void Free_IClassInterface(IClassInterface* instance)
   {
      delete static_cast<ClassImplemenation*>(instance);
   }
	
}


Now for the Max Code

test.bmx

SuperStrict

Import "fun.cpp"
Extern "C"
  Type IClassInterface 
  	Method Add:int(x:int,y:int)
  End Type
	
  function Create_IClassInterface:IClassInterface()
  function Free_IClassInterface(instance : IClassInterface)
End Extern



Print "Creating Class"
local class:IClassInterface = Create_IClassInterface()

Print "Testing Add"
Print "X+Y="+class.Add(2,3)
Free_IClassInterface(class)
class=NULL

Input "Press Enter to Quit"


Bascially the technique is how you implement interfaces with C++ by declaring a class "IClassInterface" abstract as all the methods are virtual pure. The key is you can not have any implemenations in these methods. That is for the implemenation class.

BlitzMax has supported virtual pure C++ classes since day one and can make wrapping a significantly cleaner than just everything as a Byte Ptr and make your code easier to manage.

Hope someone finds this useful.

DStastny


plash(Posted 2008) [#7]
class ClassImplemenation : public IClassInterface {
 public:
	ClassImplemenation() {cout << "Creating Class\n"; }
	virtual ~ClassImplemenation() { cout << "Destroying\n"; }
	virtual int Add(int x, int y) { cout << "Adding "<<x<<"+"<<y<<"\n"; return x+y; }
	static void hello() {cout << "Hello!!\n";}
};


Extern "C"
  Type IClassInterface 
  	Method Add:Int(x:Int, y:Int)
	Function hello()
  End Type
End Extern

"Expecting field or method declaration but encountered Function" ;(

Still interesting though.. If we could do what I just tried we could have a Class.Create() function/static method!


EDIT: P.S. this is much simpler:
#include <iostream>
using namespace std; 
 
class IClassInterface {
public:
	IClassInterface() {cout << "Creating Class\n"; }
	~IClassInterface() { cout << "Destroying\n"; }
	virtual int Add(int x, int y) { cout << "Adding "<<x<<"+"<<y<<"\n"; return x+y; };
};

extern "C" {
   IClassInterface* Create_IClassInterface()
   {
	return new IClassInterface(); 
   }	 
   
   void Free_IClassInterface(IClassInterface* instance)
   {
      //delete static_cast<ClassImplemenation*>(instance);
	  delete instance;
   }
	
}



DStastny(Posted 2008) [#8]
The reason it failed is the interface methods all have to be virtual pure
on the interface class.

  static void hello() {cout << "Hello!!\n";}


Is a class function and cannot reference this, no way to get those methods through to MAX.

you would have to wrap class methods conventionally. As for the the simpler way that works too but you have to stub out the virtual desctructor and it makes it more difficult to implement polymorphic behavior without having seperate Free methods for each class.

With the interface based approach you can have 6 classes implement the same interface and even support multiple interfaces on one class and still expose that functionality to MAX.

Doug


Scaremonger(Posted 2008) [#9]
@DStastny i've used your example in the Wiki guide to wrapping C++. Hope thats okay.


DStastny(Posted 2008) [#10]
Thats fine.

The technique should be clarified that the methods on the class being imported must be virtual pure. virtual void foo()=0;

DStastny


Scaremonger(Posted 2008) [#11]
Yeah, I've found that in a module I'm converting. I'll make sure it's highlighted in the Wiki Tutorial.