BlueprintImplementableEvent called within another function outside class scope

I’m about to tear my hair out over this simple thing in c++… Can someone please walk me through how I’m supposed to run a UFUNCTION(BlueprintImplementableEvent) function from within a callback function? See the snip below:
Header File:

UCLASS()
class CABI_API ACabiListener : public AActor
{
    GENERATED_BODY()
    
public:    
    // Sets default values for this actor's properties
    ACabiListener();

    UFUNCTION(BlueprintImplementableEvent)
    void PlayButtonPressed();

protected:
    // Called when the game starts or when spawned
    virtual void BeginPlay() override;
    

public:    
    // Called every frame
    virtual void Tick(float DeltaTime) override;

};

CPP File Demonstration 1 (with error):

//this is called from third party software. When I remove the PlayButtonPressed(), it prints to the screen properly when callback is triggered.
void callback()
{
    PlayButtonPressed();
    GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Orange, FString::Printf(TEXT("A Button was pressed!")));
}

CPP File Demonstration 2 (crashes engine on play due to what I believe to be a null pointer):
//this is called from third party software. When I remove the PlayButtonPressed(), it prints to the screen properly when callback is triggered.
ACabiListener* CabiListener;
void callback()
{
    CabiListener->PlayButtonPressed();
    GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Orange, FString::Printf(TEXT("A Button was pressed!")));
}

I’m not sure what you are trying to do here. Your class “CabiListener” don’t have any PlayButtonPressed() function declared. This won’t even compile.

Couldn’t you just expose a delegate in one class and bind it in another and from the bound function call the BlueprintImplementableEvent?

Can you show where you are binding to the callback ?

I apologize, I didn’t paste everything in there as to not delude the question with other variables that don’t have to do with the problem, but I missed the

UFUNCTION(BlueprintImplementableEvent)
void PlayButtonPressed();

I have edited the original and it now gives the error I had originally.


@3dRaven

Hmmmm, I’m not sure. Could you elaborate with an example please? I haven’t used delegates in c++ at all yet. I need to run the

UFUNCTION(BlueprintImplementableEvent)
void PlayButtonPressed();

within the callback function. But I get errors… Seems I need to get a reference to the ACabiListener even though we’re calling the callback from WITHIN it. Unless there’s a better way.


@Chatouille

Sure! It’s right here at begin play:

void ACabiListener::BeginPlay()
{
Super::BeginPlay();
libsetting.int_callback_fun = (int_callback_fun_t)callback;
}

It’s a c-style cast to the int_callback_fun_t which is coming from a struct written in C from a thirdparty library. I figured that wouldn’t matter because it’s just passing the function into this ACabiListener class but I have to be missing something here…

Delegates are a pretty in depth subject all on it’s own. Better to look through some youtube tutorials or write ups. It’s a little too much to explain in a single reply.

Found a c++ tutorial on the forum

  • A video explaining a straightforward implementation of a delegate:

Alright I figured out some of the main issues! It came down to this sentence that I read about mixing C and C++ that I found online, “if you are stuck with a C-style callback, there is no direct way to call a member function” So, I had to create a reference to the UObject I was working with (ACabiListener) in the header file and then declare it at begin play in the .cpp. Here’s the code that worked:

Header file (CabiListener.h):


UCLASS()
class CABI_API ACabiListener : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	ACabiListener();
	static ACabiListener* Listener;
	static void callback(UINT cb_type, UINT cb_src, UINT cb_srcbit, UINT cb_data, void* context);
	UFUNCTION(BlueprintImplementableEvent)
	void PlayButtonPressed();

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;
	

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

};

CPP file (CabiListener.cpp)


#include "CabiListener.h"
#include "Engine/Engine.h"

ACabiListener* ACabiListener::Listener;

void ACabiListener::callback(UINT cb_type, UINT cb_src, UINT cb_srcbit, UINT cb_data, void* context)
{
		Listener->PlayButtonPressed();

		GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Emerald, FString::Printf(TEXT("A Button was pressed!")));
}

void ACabiListener::BeginPlay()
{
	Super::BeginPlay();

	Listener = this;

	libsetting.int_callback_fun = (int_callback_fun_t)callback;
}

I’m calling it within a child Blueprint of ACabiListener like so:

Now I can compile and play, but when I package the game and run the play function, it crashes with an Assertion error that looks like this:

Assertion failed: FPlatformTLS::GetCurrentThreadId() == GGameThreadId [File:D:/Build/++UE4/Sync/Engine/Source/Runtime/Engine/Private/AudioThread.cpp] [Line: 291]

OTDemo1!FDebug::AssertFailed()
OTDemo1!FDebug::CheckVerifyFailedImpl()
OTDemo1!FAudioThread::RunCommandOnAudioThread()
OTDemo1!FAudioDevice::AddNewActiveSoundInternal()
OTDemo1!UGameplayStatics::PlaySound2D()
OTDemo1!UGameplayStatics::execPlaySound2D()
OTDemo1!UFunction::Invoke()
OTDemo1!UObject::CallFunction()
OTDemo1!UObject::ProcessContextOpcode()
OTDemo1!ProcessLocalScriptFunction()
OTDemo1!ProcessScriptFunction<void (__cdecl*)(UObject * __ptr64,FFrame & __ptr64,void * __ptr64)>()
OTDemo1!ProcessLocalFunction()
OTDemo1!ProcessLocalScriptFunction()
OTDemo1!ProcessScriptFunction<void (__cdecl*)(UObject * __ptr64,FFrame & __ptr64,void * __ptr64)>()
OTDemo1!ProcessLocalFunction()
OTDemo1!UObject::ProcessContextOpcode()
OTDemo1!ProcessLocalScriptFunction()
OTDemo1!ProcessScriptFunction<void (__cdecl*)(UObject * __ptr64,FFrame & __ptr64,void * __ptr64)>()
OTDemo1!ProcessLocalFunction()
OTDemo1!ProcessLocalScriptFunction()
OTDemo1!UObject::ProcessInternal()
OTDemo1!UFunction::Invoke()
OTDemo1!UObject::ProcessEvent()
OTDemo1!AActor::ProcessEvent()
OTDemo1!ACabiListener::callback() [F:\GameDevelopment\ProjectOhioTakover\Game Projects\OTDemo1\Plugins\Cabi\Source\Cabi\Private\CabiListener.cpp:31]
GPCLIB
kernel32
ntdll

I’m soooo close to getting this to work completely. Am I missing something with the reference? Any ideas on what’s causing the Assertion error? Thanks for the replies thus far guys, I really appreciate your help.

Not sure if this has to do with anything, but it’s throwing this warning on build as well:

  winnt.h(620): [C4005] 'TEXT': macro redefinition
  Platform.h(1085): [C4005] see previous definition of 'TEXT'

This started happening after I renamed a cpp file a while ago. Since then, I’ve deleted it and completely restarted, but it’s still throwing the warning. When I double click the warning it takes me to the winnt.h file and shows me a #define that looks like this:

#define TEXT(quote) __TEXT(quote)   // r_winnt

Pretty sure I’m redefining the TEXT macro somewhere but I don’t know how or where to find it…

I’m going to watch the video and read all of the tutorial forum. Thank you so much for your insight!

So you kinda solved the instance problem on your own, but there’s still the thread problem.

Other lib most likely runs on its own thread, and callback is triggered there, but there’s lot of things the engine won’t allow outside of main game thread.

Use something like this to route back to main thread :

#include "Async.h"

void callback()
{
    AsyncTask(ENamedThreads::GameThread, []() {
        if (Listener)
            Listener->PlayButtonPressed();
    });
}

Also, since you are using a static variable pointing to a game actor, which is kinda very dangerous, try to be as safe as possible :

  • Check Listener pointer before using it.
  • Override event Destroyed to set Listener = nullptr; to avoid crashes if your lib triggers between map loads.

Understood, I will! What makes using a static variable pointing to a game actor very dangerous? Is there something I can do as an alternative? So, the fact that I wasn’t passing threads back asynchronously was causing the Assertion error and the crash you think? Seemed to be conflicting with the Play Sound 2D node I have running on Initialize Play. Maybe they were contesting over a thread? I’m new to this lol sorry. I’m thinking a delegate off a game instance subsystem might be a better way to do it based on what others have said and what I’ve seen online.
Kinda like this:


I would subscribe the event initialize Play to the callback Play or something. Does that sound right like a better plan?

Well, as it shows in the trace, crash was triggered by RunCommandOnAudioThread.

image

I don’t know exactly why the engine needs to be in game thread in order to dispatch to the audio thread, but there must be a reason.

Static pointers to engine objects/actors are less safe because they are not automatically managed. When you have an UObject* variable decorated with UPROPERTY(), the engine keeps track of it, and if the pointed object is destroyed, the variable value is automatically set to nullptr(0). When checking if your object is valid via if (Object) or even if (IsValid(Object)) you are essentially checking if the pointer differs from zero (there’s more to it but it doesn’t really matter). You cannot use UPROPERTY() on static variables though, so your pointer is not managed by the engine. So let’s say you point your pointer to the memory address of an object. When that object is destroyed, your pointer still points to that address, because nothing sets it automatically to nullptr. And when you check its validity it might appear as valid, but in reality could point to rubbish. I mentioned game actors rather than objects for a reason though. In lots of case we use objects because they are persistent across levels, like GameInstance and whatnot. In this case it might not matter since your object might never be destroyed. Actors however are all cleaned up with every map change, and in multiplayer there’s also relevancy considerations, so that’s why it tends to be even more dangerous.

I think what you did, with the addition of Destroyed handler, is fine.

If you want to be on the extra safe side, you can use a weak object pointer instead. Those come with extra check functions to verify the memory address points to an actual UObject and not rubbish.

static TWeakObjectPtr<ACabiListener> Listener;

if (Listener.IsValid())
    Listener->PlayButtonPressed();