C Callbacks

BlitzMax Forums/BlitzMax Programming/C Callbacks

verfum(Posted 2007) [#1]
Hi,

A while ago I bookmarked a link to a Blitz wiki page that is no longer active.

http://www.blitzwiki.org/index.php/C_CallBacks

It explained how to pass a pointer of a BlitzMax function to a function in a 'C' DLL, and then create a callback function in the 'C' DLL that when called would run the BlitzMax function.

I didn't explain that very well, did I?

Anyhow, can anyone shed some light as to how this can be achieved? Did this wiki page originate from the BlitzBasic site (I can't find it if it did).

Cheers in advance for any help


verfum(Posted 2007) [#2]
I just found this link that may solve the BlitzMax side of the problem (not entirely sure it would work). Can anyone suggest what the DLL import/export side of things would look like? And what the callback prototype would look like. And perhaps an example.

I'm asking for a lot :P

http://www.blitzbasic.com/Community/posts.php?topic=56978#633444


Dirk Krause(Posted 2007) [#3]
That's what the google cache gave me:
C CallBacks
From BlitzWiki

Installation 

To compile C source in BlitzMax, you need the GNU C compiler. If you're using OS X or Linux, than you should already have everything you need. On Windows, however, you'll need to install a compiler separately. MinGW (http://mingw.org/) is the recommended way of installing a C compiler on Windows. You could install Bloodshed (http://bloodshed.net/) instead if you want some of the extra tools it provides for MinGW. 
[edit]
Introduction 

There are some times when (unfortunately) you may have to use C as part of your project. Fortunately, it is quite easy to call a C routine from Max, and also to call a Max function from C. I'm going to go into as much detail as possible so hopefully even the most inexperienced can grasp what's going on. Calling Max functions from C is a little complicated by the fact that you have to "tell" C where the Max function is. If you've found out how to get round this, please do tell! 

Anyway let's dive straight into some source code. Here we're going to pass an integer back and forth, adding 1 to it each time so we can see that somthing has happened.
max code 
Strict
Import "./callb.c"

Extern "C"
	Function RegisterCallback ( fp:Byte Ptr )
	Function CallMe (a:Int)
End Extern


RegisterCallback( Test )
Local r:Int
r=callme(123)
Print "r="+r 


Function Test:Int( a:Int )
	Print "call back a="+a
	Return a+1
End Function

C code 
int (*callback)(int *);

void RegisterCallback ( int *infunc )
{
	(int)callback = infunc;
}

int CallMe( int a){
	a=callback((int*)a);
	printf("in C a=%i\n",a);
	return a+1;
}

the output 
Building callback
Compiling:callb.c
Compiling:callback.bmx
flat assembler  version 1.51
3 passes, 2239 bytes.
Linking:callback.exe
Executing:callback.exe
call back a=123
in C a=124
r=125

Process complete
[edit]
Explaining the Code 

1. We'll start with the hateful stuff first (can you tell I'm not a C fan!) Think of this as a variable that holds not only the address of a Max function, but its parameters, too. 
int (*callback)(int *);



2. This is the C routine we call from Max to set the value to the address of the Max function we want to call from C. As an aside, you could potentially call this at different points to make a function call go to different places at different times. Although this could be very powerful, it can lead to very hard to follow code! 
void RegisterCallback ( int *infunc )
{
	(int)callback = infunc;
}



3. This is where it gets a bit hairy... CallMe is the routine we call from Max. The first thing it does is call a Max function. The (int*)a hieroglyphics is basically passing an address to the memory location that contains the value we wish to pass. 
int CallMe( int a){
	a=callback((int*)a);
	printf("in C a=%i\n",a);
	return a+1;
}



4. Having sent the value to Max for modification, we add 1 to it and return the value to Max. 
	return a+1;



5. First, a word about strict mode: use it! It is your friend. You may think it's a pain to have Max nag you, but believe me, it will save you hours of frustrating bug hunting. Import adds in the C code to our application. Note the ./ is not needed, but you could do something like "../cstuff/callb.c" to go back a directory and into the cstuff directory. "./" just means the current directory.
Extern "C" tells Max that there are some external functions going to be used that are called with the C calling convention. 
Strict
Import "./callb.c"

Extern "C"
	Function RegisterCallback ( fp:Byte Ptr )
	Function CallMe (a:Int)
End Extern



6. RegisterCallback sends the address of the Test function to C so it will know where it should go later. "r=callme(123)" calls a C routine and puts the returned value into the r variable. The Test function takes an int value, adds 1 to it and returns that value. 
RegisterCallback( Test )
Local r:Int
r=callme(123)
Print "r="+r 



Phew! Well, that's about it. Hopefully now you understand how to call C functions and pass values back and forth.

Just one note currently the official blitz ide doesn't recognise C files and will capitalise certain words, kiss of death for C files so use another editor like context for your editing of C files. 

Tutorial by Chris C thanks to Todd for editing.