Registering a Callback routine

Discussions related to the code libraries supplied with BB4W & BBCSDL
User avatar
hellomike
Posts: 194
Joined: Sat 09 Jun 2018, 09:47
Location: Amsterdam

Registering a Callback routine

Post by hellomike »

Hi,

For BB4W the CALLBACK library is developed to be used for (OS) SYS calls that require a Callback routine address as one of their paramaters.
The examples from the documentation work flawless.

In many situations there also is the need to only register a callback routine with a SYS so that subsequent (SYS) calls then use. An example is with using the CURL library.
The SYS to register a callback routine is:

Code: Select all

        SYS `curl_easy_setopt, Curl, CURLOPT_WRITEFUNCTION, write_callback
This is the documentation: CURL API

I tried this:

Code: Select all

SYS `curl_easy_setopt, Curl, CURLOPT_WRITEFUNCTION, FN_callback(FNwrite_callback(), 4)
but that freezes the program.

Can the provided library also be used to just register a callback function?

Thanks

Mike
Richard Russell
Posts: 540
Joined: Tue 18 Jun 2024, 09:32

Re: Registering a Callback routine

Post by Richard Russell »

hellomike wrote: Fri 07 Feb 2025, 15:12 Can the provided library also be used to just register a callback function?
If you're asking whether there is a difference between a callback made from a standard Windows DLL and from a third-party DLL, no there isn't, usually. The documentation of the CALLBACK library states: "Some API functions, in Windows or third-party DLLs, require you to specify a callback routine" (my emphasis). So in principle what you are attempting to do should work.

However one possible issue is that the CALLBACK library assumes that the callback will be made using the _stdcall ABI, which all the standard Windows DLLs use and I would expect most third-party DLLs to use too. However that isn't mandated, as far as I know, and it would be possible for a third-party DLL to use a different calling convention. If so the CALLBACK library is unlikely to work.

Another situation when it wouldn't surprise me if the CALLBACK library might fail is if re-entrant callbacks are attempted, that is a callback is made whilst a previous callback is still being processed. Can CURL do that?
User avatar
hellomike
Posts: 194
Joined: Sat 09 Jun 2018, 09:47
Location: Amsterdam

Re: Registering a Callback routine

Post by hellomike »

Richard,

Thanks for the fast and helpful reply.
I understood that in theory the library supports third-party DLLs. Since I couldn't find examples I had to guess a little how to tackle this. You confirming that the syntax used in the SYS to register the callback function is therefor valuable.

The library documentation states that the 'easy' interface for it is synchronous so I assume re-entrance isn't a thing then.
With this code:

Code: Select all

      SYS "LoadLibrary", @dir$ + "libcurl.dll"
      SYS "GetModuleHandle", @dir$ + "libcurl.dll" TO Dll%
      IF Dll% == 0 ERROR 100, "Could not load libcurl.dll"

      SYS "GetProcAddress", Dll%, "curl_easy_init"     TO `curl_easy_init
      SYS "GetProcAddress", Dll%, "curl_easy_cleanup"  TO `curl_easy_cleanup
      SYS "GetProcAddress", Dll%, "curl_easy_perform"  TO `curl_easy_perform
      SYS "GetProcAddress", Dll%, "curl_easy_setopt"   TO `curl_easy_setopt
      SYS "GetProcAddress", Dll%, "curl_easy_strerror" TO `curl_easy_strerror
      CURLE_OK = 0
      CURLOPT_URL = 10002
      CURL_WRITEFUNC_ERROR = 23
      CURLOPT_WRITEFUNCTION = 20011

      INSTALL @lib$ + "CALLBACK"

      URL$="http://www.hellomike.nl/"
      SYS `curl_easy_init TO Curl
      IF Curl THEN
        PRINT "CURL initialization done"
        SYS `curl_easy_setopt, Curl, CURLOPT_URL, URL$
        SYS `curl_easy_setopt, Curl, CURLOPT_WRITEFUNCTION, 0
        REMSYS `curl_easy_setopt, Curl, CURLOPT_WRITEFUNCTION, FN_callback(FNwrite_callback(), 4)
        SYS `curl_easy_perform, Curl TO Res%
        IF Res% <> CURLE_OK THEN
          PRINT "curl_easy_perform failed"
          SYS `curl_easy_strerror, Res% TO Error%
          PRINT $$Error% " (#";Res% ")"
        ELSE
          PRINT "curl_easy_perform successful"
        ENDIF
  
        SYS `curl_easy_cleanup, Curl
      ELSE
        PRINT "CURL initialization failed"
      ENDIF
      END

      REM ----------------------------------------------------------------------
      DEF FNwrite_callback(ptr%, size%, nmemb%, userdata%)=CURL_WRITEFUNC_ERROR
The perform seems to work.

Code: Select all

CURL initialization done
curl_easy_perform successful
But when I remove the REM and run, the interpreter freezes.
Maybe you can detect a flaw.
Instead I can make a little piece of assembly that POPs the 4 dwords from the stack and return in order to see if at least that works.

Thanks

Mike
Richard Russell
Posts: 540
Joined: Tue 18 Jun 2024, 09:32

Re: Registering a Callback routine

Post by Richard Russell »

hellomike wrote: Sat 08 Feb 2025, 10:26 Maybe you can detect a flaw.
Indeed I can! Because the callback happens inside the curl_easy_perform() function you must use the FN_syscalln mechanism described in the BB4W documentation to call that function. Otherwise you end up with a deadlock because BBC BASIC is blocked waiting for SYS `curl_easy_perform to finish, and the callback cannot execute FNwrite_callback() while the interpreter is blocked!

You must change your code as follows to prevent the deadlock:

Code: Select all

        SYS FN_syscalln(`curl_easy_perform), Curl TO !FN_systo(Res%)
I can make a little piece of assembly that POPs the 4 dwords from the stack and return in order to see if at least that works.
The other way round, in fact. The syscall ABI is callee-cleanup but the cdecl ABI is caller-cleanup, so the change you need to make is not to pop the 4 dwords from the stack in FN_callback(). Experimentally it seems that change is necessary with libcurl.

With both changes made I can successfully read your web page; FN_writecallback() gets called twice, although I haven't checked that the returned data is correct.
User avatar
hellomike
Posts: 194
Joined: Sat 09 Jun 2018, 09:47
Location: Amsterdam

Re: Registering a Callback routine

Post by hellomike »

Wow, that's an eye-opener for me. And yes, of course stack cleaning done either by the caller of callee is vital to understand and make it work.

For sure I would have stopped investing energy in making libcurl work in a BBC BASIC program without your valuable insight.

Thanks a lot.

Mike