No longer able to call Blueprint Events on CDOs

As of 4.16, if you have a simple class like such:

UCLASS(Blueprintable)
class UMyBlueprintObject : public UObject
{
   GENERATED_BODY()
public:
   UMyBlueprintObject();

   UFUCTION(BlueprintImplementableEvent, Category = "Foo")
   void Bar() const;
};

Create a blueprint version that adds logic to the Bar event (even a simple PrintString), and then simply call the event on the CDO (either through another blueprint or simply grabbing it through C++) of that new Object:

TSubClassOf<UMyBlueprintObj> MyDerivedClass; // Set this in the editor or what not.
MyDerivedClass->GetDefaultObject->Bar();

You’ll get the following error:

Ensure condition failed: PointerToUberGraphFrame->RawPointer [File:D:_perforce\Vendor\UE\UnrealEngine-4.16.0-release\Engine\Source\Runtime\Engine\Private\BlueprintGeneratedClass.cpp] [Line: 1094]

Which comes from this method:

uint8* UBlueprintGeneratedClass::GetPersistentUberGraphFrame(UObject* Obj, UFunction* FuncToCheck) const
{
	if (Obj && UsePersistentUberGraphFrame() && UberGraphFramePointerProperty && UberGraphFunction)
	{
		if (UberGraphFunction == FuncToCheck)
		{
			FPointerToUberGraphFrame* PointerToUberGraphFrame = UberGraphFramePointerProperty->ContainerPtrToValuePtr<FPointerToUberGraphFrame>(Obj);
			checkSlow(PointerToUberGraphFrame);
			ensure(PointerToUberGraphFrame->RawPointer); // <- Crash due to assert
			return PointerToUberGraphFrame->RawPointer;
		}
	}
	UClass* ParentClass = GetSuperClass();
	checkSlow(ParentClass);
	return ParentClass->GetPersistentUberGraphFrame(Obj, FuncToCheck);
}

That seems to be related to this change that came in from Fortnite:

void UBlueprintGeneratedClass::CreatePersistentUberGraphFrame(UObject* Obj, bool bCreateOnlyIfEmpty, bool bSkipSuperClass, UClass* OldClass) const
{
	if (Obj && Obj->HasAnyFlags(RF_ClassDefaultObject | RF_ArchetypeObject)) // <- Added in 4.16
	{
		return; // <- No Persistent Uber Graph Frame is created for CDOs.
	}

Change 3238782 on 2016/12/16 by Ben.Zeigler (Integrated into mainline with 4.16)

#jira FORT-34825 Fix issue where Macro CDOs had corrupted persistent ubergraph frames during blueprint compile on load, by changing it so no CDOs have persistent frames.
This also saves memory as using persistent frames is incorrect for CDOs, things like latent functions do not make sense.
Fix from  O'Connor

Removing that added logic fixes the functionality that existed pre-4.16. Is there another way to manually allow a CDO to call overridden script methods?

CDOs are a convenient way to basically enforce a Singleton-esque pattern and keep down memory usage. If blueprint events are no longer allowed on CDOs - then that’s going to increase memory usage as all objects with blueprint events will now have to be instantiated so they can get their PersistantUberGraphFrame.

Cheers,

1 Like

Sorry, for the confusion.

You can add the MyDerivedClass field to some other class that you can access from the editor ,(i.e. ThirdPersonCharacter or some such):

// In the ThirdPersonCharacter code

//.h

UPROPERTY(EditAnywhere, Category = "Foobar")
TSubClassOf<UMyBlueprintObj> MyBlueprintObject;

// .cpp, in some place like OnBeginPlay

MyBlueprintObject->GetDefaultObject()->Bar();

Then simply drop in a character, and set the property on the character.

There’s nothing else in the source file or such.

@

Where you able to repro this? Do you need anything else from me?

When I try to use the line MyBlueprintObject->GetDefaultObject()->Bar(); I receive a compile error that ‘class “UObject” has no member “Bar”’. It appears that MyBlueprintObject->GetDefaultObject() is returning UObject rather than UMyObject, so it doesn’t see the Bar() function from my class. Please provide a small repro project to help investigate the crash you’re seeing

Just toss a cast in there:

Cast<UMyBlueprintObj>(MyBlueprintObject->GetDefaultObject())->Bar();

If that doesn’t work, I’ll get you a small repro project tonight.

After adding in the cast I was able to compile successfully and when I played in the editor the print string from my blueprinted Bar function appeared as expected.

Alrighty, I’ll try and get a repro project to you tonight.

Alright @,

Here is a test project that shows the issue. There’s two Blueprint objects in the ThirdPersonCPP/Blueprints folder. CrashBP will cause the crash, SafeBP will not. There is a field named “Foobar” on the Character in the level that is currently set to CrashBP, hitting Play will cause the assert/crash.

The only difference between how these two classes are created is CrashBP was created with Blueprint Editor option “Spawn Default Event Nodes” turned on (and Bar made into a default event through the config files), and SafeBP had that option disabled.

Default spawned event nodes take a different compiler path than non-default spawned events (they end up getting merged into the new blueprint), which is causing them to call FKismetCompilerContext::CreateFunctionStubForEvent and that in turn eventually calls UBlueprintGeneratedClass::CreatePersistentUberGraphFrame which had its logic changed by the Changelist I reference in the original post.

The work around is to simply disable the “Spawn Default Event Nodes” option in the Blueprint Editor, but that’s less than ideal.

Hey ExtraLifeMatt-

Thank you for the sample project. I have reproduced this issue and logged a report for it here Unreal Engine Issues and Bug Tracker (UE-46534) . You can track the report’s status as the issue is reviewed by our development staff. Please be aware that this issue may not be prioritized or fixed soon.

Cheers

Thanks, sounds good Ben!

Hey ExtraLifeMatt-

I’m having some difficulity following your code. Where are you adding the lines

TSubClassOf MyDerivedClass; MyDerivedClass->GetDefaultObject->Bar();?

Is this part of UMyBlueprintObject class or are you adding this to another class you created? If so, which function are you calling MyDerivedClass->GetDefaultObject->Bar(); from? Is there anything else in the source file of your UObject class?

After discussion, we decided to bring back the persistent ubergraph frames for CDOs other than Macro Libraries. This fix should be in a future hotfix, to fix this problem locally change the return condition at the top of CreatePersistentUberGraphFrame to this:

UBlueprint* Blueprint = Cast<UBlueprint>(ClassGeneratedBy);
if (Blueprint && Blueprint->BlueprintType == BPTYPE_MacroLibrary)
{
	return;
}