Xojo C Async Function Callback Issue

Hello everyone,

I’m facing an issue in my Xojo project, and I need some help from the community. Let me describe the problem and provide the corresponding C and Xojo code.

In C language, I have written a set of functions to process WLD files and generate PNG images. The core functionality is encapsulated in an asynchronous function encode_world_to_png_async_start. This function takes the world_path, reads a binary file, and then draws an image based on certain rules.

Here’s the relevant C code:

typedef void (*Callback)(int result);

typedef struct {
    const char *world_path;
    const char *img_path;
    bool is_mark_important;
    int result;
    Callback callback;
} EncodeWorldToPngArgs;

void *encode_world_to_png_async(void *args_ptr) {
    EncodeWorldToPngArgs *args = (EncodeWorldToPngArgs *) args_ptr;
    int ret = encode_world_to_png(args->world_path, args->img_path, args->is_mark_important);
    args->result = ret;
    if (args->callback != NULL) {
        args->callback(ret);
    }
    free(args);
    return NULL;
}

int encode_world_to_png_async_start(const char *world_path, const char *img_path, bool is_mark_important, Callback callback) {
    pthread_t thread;
    EncodeWorldToPngArgs *args = malloc(sizeof(EncodeWorldToPngArgs));
    args->world_path = world_path;
    args->img_path = img_path;
    args->is_mark_important = is_mark_important;
    args->result = -1;
    args->callback = callback;
    int ret = pthread_create(&thread, NULL, encode_world_to_png_async, args);
    if (ret != 0) {
        printf("Error creating thread\n");
        free(args);
        return -1;
    }
    ret = pthread_detach(thread);
    if (ret != 0) {
        perror("Failed to detach async thread");
        free(args);
        return -1;
    }
    return 0;
}

I compiled the C code into a DLL file.

In my Xojo project, I imported the DLL using the Declare statement and called the asynchronous function encode_world_to_png_async_start within the Open event of a button. Here’s the relevant Xojo code:

Declare Function encode_world_to_png_async_start Lib "libTerraDll" (world_path As CString, img_path As CString, is_mark_important As Boolean, callback As Ptr) As Integer

Dim file As FolderItem = OpenFileDialogSeleteTerraWorld
If file <> Nil And file.Exists Then
  worldFileName = file.Name.TrimRight(".wld",".bak",".wld.bak")
  Dim imageFullName As CString = "E:\wld\" + worldFileName + ".png"
  Dim worldFullName As CString = file.NativePath
  Dim callbackPtr As Ptr = AddressOf WorldToImageCallbackImpl
  Dim ret As Integer = encode_world_to_png_async_start(worldFullName, imageFullName, False, callbackPtr)
End If

The WorldToImageCallbackImpl function, which is used as the callback, initially only displayed a message box based on the result:

Sub WorldToImageCallbackImpl(result As Integer)
  If result = 0 Then
    MsgBox("Asynchronous operation success")
  Else
    MsgBox("Asynchronous operational execution failed")
  End If
End Sub

This worked fine, and I received the “Asynchronous operation success” message.

However, when I added additional code to the WorldToImageCallbackImpl function to display the generated image in a canvas, the program would terminate abruptly without any error messages or debugging interface. Here’s the modified code:

Sub WorldToImageCallbackImpl(result As Integer)
  If result = 0 Then
    MsgBox("Asynchronous operation success")
    Dim file As FolderItem
    Try
      file = New FolderItem("E:\wld\" + "new.png")
      If file <> Nil And file.Exists Then
        img = Picture.Open(file)
        imgHeight = img.Height
        imgWidth = img.Width
        hScrollBar.MaximumValue = imgWidth - imgCanvas.Width
        vScrollBar.MaximumValue = imgHeight - imgCanvas.Height
        imgCanvas.Refresh
      Else
        MsgBox("Error")
      End If
    Catch e As IOException
      MsgBox("Error: " + e.Message)
    End Try
  Else
    MsgBox("Asynchronous operational execution failed")
  End If
End Sub

Surprisingly, the additional code caused the program to terminate without any clear indication of what went wrong.

I further tested the image display code separately in the Pressed event of another button (Button3), and it worked fine, displaying the image in the canvas as expected:

Sub Button3.Pressed
  Dim file As FolderItem
  Try
    file = New FolderItem("E:\wld\" + "new.png")
    If file <> Nil And file.Exists Then
      img = Picture.Open(file)
      imgHeight = img.Height
      imgWidth = img.Width
      hScrollBar.MaximumValue = imgWidth - imgCanvas.Width
      vScrollBar.MaximumValue = imgHeight - imgCanvas.Height
      imgCanvas.Refresh
    Else
      MsgBox("Error")
    End If
  Catch e As IOException
    MsgBox("Error: " + e.Message)
  End Try
End Sub

To summarize, I have written C functions to process WLD files and generate PNG images. I want to call these functions correctly from Xojo without blocking the main thread and then display the generated PNG image in a canvas.

If anyone has any insights into what might be causing the unexpected program termination and how to properly display the image in the canvas after generating it, I would greatly appreciate your help.

Thank you all!

1 Like

The problem is Xojo code can only be run on Xojo’s own main thread. You will need to rearchitect your implementation so you can pull the results from Xojo instead of using a callback.

There are officially unsupported ways to make some minor things work in callback context. You have to disable background tasks and overflow checking
Note that implementing this way is considered a hack by Xojo and may break in the future. But it does work and here is one example:

This sounds like a classic case of calling convention mismatch. Basically, the Xojo compiler and the C compiler disagree on how to pass parameters/return value to/from the callback, so accessing the parameters/return value means accessing undefined memory–so the app goes kaboom.

Xojo uses the CDecl calling convention by default, therefore the C code has to use that convention when invoking the callback.

Xojo methods can also use the StdCall convention, which is quite commonly used for callbacks on Windows. Add the line #pragma X86CallingConvention Stdcall to the callback’s source code to change its convention.

But also be aware that Xojo doesn’t officially support async callbacks. It may work, but you’re playing with fire.

May I suggest looking into our declare classes in MBS Xojo Util Plugin?

There is a DeclareCallBackMBS class to catch callbacks, which may happen on other threads. It also supports various calling conventions.

Thank you for your help. I followed your advice and used this plugin, but I still encountered an issue.
To provide some context, here’s a summary of the situation:

I initially faced an issue while trying to call an asynchronous C function from Xojo. A community member suggested using a particular plugin, and I proceeded to write the test code as per their guidance. Here’s the snippet of code that I used:

Dim d As New DeclareLibraryMBS("E:\Code\CTerra\my-c-project\build\libTerraDll.dll")


Dim lines() As String = d.SymbolNames


Dim worldPath As CString = "E:\Code\CTerra\my-c-project\wld\123.wld"
Dim imagePath As CString = "E:\Code\CTerra\my-c-project\wld\123.png"
Dim isMarkImportant As Boolean = True
Dim c As New MyCallback("l)v")
c.AllowAsync = True
c.Result = 0
'Dim p As ptr = d.Symbol("encode_world_to_png_async_start")
'Dim f As New DeclareFunctionMBS("(ZZBp)l", p)
Dim p As ptr = d.Symbol("encode_world_to_png")
Dim f As New DeclareFunctionMBS("(ZZB)l", p)

f.ParameterString(0) = worldPath
f.ParameterString(1) = imagePath
f.ParameterBoolean(2) = isMarkImportant
'f.ParameterPointer(3) = c.FunctionPtr
Dim r As Variant = f.Invoke

If r <> c.Result Then
  MsgBox "Result is not matching for " 
  Break // failed
Else
  MsgBox "OKKKK: "+Str(r)
End If

While the encode_world_to_png function works as expected, I encountered a new issue when I uncommented and attempted to use the encode_world_to_png_async_start function. At this point, the program crashes without any error messages or output. The program simply exits abruptly.

@Jason_King @Andrew_Lambert Thank you for your assistance, but I still cannot fully comprehend your advice. I am developing on the Windows platform. If you could provide some examples, I will try to study and understand them. Thank you.

Since this is asynchron, you should not use local variables to hold the references, but properties of a window or module.
The callback will come later and if you use local variables, the objects are already gone.

2 Likes

Thank you, I have resolved this issue based on your assistance.

1 Like

I must say, this is one of the best posts asking for assistance I’ve ever seen. Commendably complete and well-explained.

4 Likes