Module: Passing a function to a c function

BlitzMax Forums/BlitzMax Programming/Module: Passing a function to a c function

Sub_Zero(Posted 2013) [#1]
Hi!

I'm trying to make a module out of libsmbclient for linux (apt-get install libsmbclient-dev), wich is a shared library.

Some of the functions works ok, except for those that require a (callback?) function as parameter. I've been trying for hours with different approaches but can't get it to work..

The problem is in the Init method (smbc_init) and I've also tried using the smbc_get_auth_data_fn function supplied with the smbclient-dev examples, also with no luck. I figured if i could get that working I'd figure out how to do it...

here is my module code:

dir structure: mod/sub.mod/libsmbclient.mod/

file libsmbclient.bmx:


file glue.cpp:


file include/get_auth_data_fn.h:



and then the test bmx file: testsmb.bmx, in the src/ folder:



col(Posted 2013) [#2]
Hiya,

I've not looked through your code ( I have a hangover :D ) but you usually have the BMax function that will call the c code have a Byte Ptr as the callback parameter and pass the function name as that parameter.

Heres a little working example that hopefully will help figure it out...

BMax code


Import "Callbacks.c"      ' Import the c code below

Extern
	Function Init(Callback:Byte Ptr) ' Pass the function name into here a parameter
EndExtern

Function C_Callback(Value1,Value2)
	Print "C_Callback Value1: " + Value1 + ", Value2: " + Value2
EndFunction



Init(C_Callback) ' Pass the name of C_Callback function into C code


C Code - filename "Callbacks.c"

// There will be a type definition for the callback signature in the headers somewhere, I made one here for this example
typedef void( *BMX_CALLBACK )(int, int);
BMX_CALLBACK BMaxCallback;



void Init(BMX_CALLBACK callbackFn)	// BMax would pass the funcion name into here but as a :Byte Ptr
{	
	BMaxCallback = callbackFn;		// Assign the variable to the function address
	
	callbackFn(10,20);				// call the function in Bmax with parameters
}



col(Posted 2013) [#3]
Just had a cold shower, feel much better now...

Had a little look through the functions that are calling the c code.

You shouldn't pass in the BMax String as a variable type unless you have the same type ( BBSTRING ) in your c code. BMax String is a BMax Object not a standard string of characters as in standard c char strings ( which are just arrays of bytes ). If you need strings to be passed directly into straight into the c/++ code you need to know what size of characters the c/++ code is expecting ( single byte or 'wide byte' ), in this case the c code is expecting single byte char(s), and then...

so instead of

Function smbc_opendir:Int(durl:String)

use

Function smbc_opendir:Int(durl$z) ' BMax will internally pass the string characters as single bytes including adding in the null terminator.

or

Function smbc_opendir:Int(durl$w) ' BMax will internally pass the string as wide chars ( 2 bytes per character ) including adding a null terminator.

EDIT:- For beginners learning this stuff :- Also I noticed youre using cpp and my example is in c so in your cpp code you would wrap the Init function with the standard extern"C"{ [...] } ( as you are already doing ) to prevent cpp name-mangling so the linker can find the function.


Sub_Zero(Posted 2013) [#4]
Thanks alot for the help, I will have a look at it :)


col(Posted 2013) [#5]
Oops theres a typo in the c example I posted.

The Init function should be

void Init(BMX_CALLBACK callbackFn)
{	
	BMaxCallback = callbackFn;
	
	BMaxCallback(10,20);
}


Equates to the same thing really but uses the global BMaxCallback to call back into BMax.

Any probs feel free to ask for more help.


Sub_Zero(Posted 2013) [#6]
Hi

I've had a go or five and now everything compiles ok, except for this line in glue.cpp (it compiles fine without this line):

BMaxCallback(server, share, wrkgrp, 3, user, 3, pass, 3);


I also tried to replace the line with this:
return smbc_init(BMaxCallback, debug);


But that wouldn't compile either..

here's my current libsmbclient.bmx:


and my glue.cpp:


the typedef for the callback function is as follows (taken from libsmbclient.h):



Sub_Zero(Posted 2013) [#7]
Double post!


col(Posted 2013) [#8]
In your glue code try..

int bmx_smbc_init(smbc_get_auth_data_fn * fn, int debug) {
	BMaxCallback = fn;
		
	return smb_init( BMaxCallback,debug );
	//BMaxCallback(server, share, wrkgrp, 3, user, 3, pass, 3);

	}



Then in the module...

Function setauthdatafn(server:Byte Ptr Var, share:Byte Ptr Var, workgroup:Byte Ptr Var, workgrouplen:Int Var, user:Byte Ptr Var, userlen:Int Var, password:Byte Ptr Var, passwordlen:Int Var)
	server = _server.ToCString()
	share = _share.ToCString()
	workgroup = _workgroup
	workgrouplen = _workgrouplen
	user = _user
	userlen = _userlen
	password = _password
	passwordlen = _passwordlen
End Function


EDIT:- OMG Hangover.... I've tidied the above after multi-edits.

The function is used to SET the variables so you will have to use Var on the end of the parameters, and use ( for eg ) server = _server.ToCString() to return the strings as c style strings.


col(Posted 2013) [#9]
oh I see you have tried the

return smbc_init(BMaxCallback,debug);

You should have

smbc_get_auth_data_fn BMaxCallback;


as opposed to using a pointer. Or just pass the fn parameter of your bmx_smbc_init function straight into the smbc_init call if you don't need to save it - it looks like the lib will hold its own reference.


Sub_Zero(Posted 2013) [#10]
I will try that after eating dinner with my family, if my head still gets around :)


Sub_Zero(Posted 2013) [#11]
Thank you so much for the help!

Now it compiles :)

It compiles and runs, but I get a segfault when the callback function is actually being called (during smbc_opendir())

Any clues how to solve it? :)

here's my current libsmbclient.bmx:


my glue.cpp:



col(Posted 2013) [#12]
I would make sure - check the docs very carefully - as to what is expected to happen inside the callback. Even try commenting out the code inside the callback to rule out any problem with it - I'm assuming it is asking for what I think - setting the data - i dont have the lib in question, im on windows, and i came to that conclusion because of the way youre setting the 'max variables to the parameters - are you sure thats what's expected in the callback?

I'm out for night again - back tomorrow afternoon :)


Sub_Zero(Posted 2013) [#13]
First of all thanks for all your help! :)

Edit: I tried commenting out everything inside my setauthdatafn function, and it didn't segfault!! Instead i got a working connection with smbc_opendir using the default username and password supplied by the lib.

Here is two different callback functions that works if I compile some of the .c examples supplied with the lib:



Alernative: Here is another one that doesn't set the server or share name, used for just printing eg. available shares or access as guest user:



Sub_Zero(Posted 2013) [#14]
Double post again!

(I'm on a packet loss line right now)


col(Posted 2013) [#15]
You're welcome for the help.

Aside from the checking of the memory contents pointed at by the variables in the callback ( if the server and share pointer contents are the same as you would set them, and making sure that the memory contents are emtpy via chaking for a null terminator at the start of the string ) it looks like a simple MemCopy would get the job done. In that case remove the Var(s) in the parameters of the BMax callback and use a MemCopy to copy across the strings into the pointers provided to you in the callback function...

in other words change :-
Function setauthdatafn(server:Byte Ptr Var, share:Byte Ptr Var, workgroup:Byte Ptr Var, workgrouplen:Int Var, user:Byte Ptr Var, userlen:Int Var, password:Byte Ptr Var, passwordlen:Int Var)


back to :-

Function setauthdatafn(server:Byte Ptr, share:Byte Ptr, workgroup:Byte Ptr, workgrouplen:Int, user:Byte Ptr, userlen:Int, password:Byte Ptr, passwordlen:Int)

and use a simple MemCopy to copy the strings across, so instead of using server = _server.ToCString()

use:-
MemClear(server,256) ' You don't really need this as the .ToCString() will add a null terminator for you, but they are doing similar in your example above.
MemCopy(server,_server.ToCString(),Len(_server)+1) ' This will copy the data across into the memory address provided for you.

MemClear(share,256)
MemCopy(share,_share.ToCString(),Len(_share)+1) ' +1 to include the null terminator in the length

and so on, keep using the .ToCString() for each string copy making sure that the length of the workgroup,user and password variable lengths never exceed the maximum length coming in via the workgrouplen,userlen and passwordlen parameters by using either Len(workgroup) or workgroup.Length. It looks like a max length of 255 is allowed for each.

Have fun :)


Brucey(Posted 2013) [#16]
_server.ToCString()

... is a memory leak in the way you've used it there.

And you probably want to use ToUTF8String() instead, anyway. (again, not in the way used above)


col(Posted 2013) [#17]

... is a memory leak in the way you've used it there



Indeed it is. I forgot about that, thanks!

Assign it to a variable and free the variable with MemFree afterwards :-

Local serverSring:Byte ptr = _server.ToUTF8String()  ' as Brucey rightly suggests
MemCopy(server,serverString,Len(_server)+1) ' copy the string
MemFree(serverString) ' free the memory used by the string! important!



Sub_Zero(Posted 2013) [#18]
Thanks for all your help...

I've now managed to get the lib to recognize the parameters, it recognizes server, share, workgroup, username and password.. It also connects to the share :)

EDIT: Now it works flawlessly :)

I'll post the code as example:

libsmbclient.bmx:


glue.cpp:


testsmb.bmx:


Now I understand how to wrap a callback function. Thanks guys!


col(Posted 2013) [#19]
Well done!!

Dont forget to MemFree all of the 'string' field variables inside the SMBCCTX.Close method.


Sub_Zero(Posted 2013) [#20]
Ofcourse, I forgot :) Cheers!