Reference UE 5.7Versioned

Casting to an interface misses Blueprint implementations

Cast<IMyInterface>(Obj) returns nullptr when an object's interface implementation exists only in Blueprint, even though ImplementsInterface() returns true for that same object. The engine skips entries marked bImplementedByK2, so casting to an interface is safe only when that interface is explicitly blocked from Blueprint implementation.

Table of contents

Two ways to call

There are two ways to call an interface function in Unreal: cast to the I* type or use the generated Execute_ wrapper. They look interchangeable, but they ask the engine two different questions.

Cast<IMyInterface>(Obj) returns nullptr for an object whose interface implementation exists only in Blueprint. For that same object, Obj->GetClass()->ImplementsInterface(UMyInterface::StaticClass()) returns true. The engine deliberately skips interface entries added by the Blueprint compiler, so casting to I* is safe only when the interface is explicitly marked as non-implementable in Blueprint. Everywhere else, it is a silent bug waiting for the day a designer creates a Blueprint implementation.

The whole problem comes down to one specific configuration: a native interface declared in C++ and implemented in Blueprint.

Two functions, one condition apart

GetInterfaceAddress and ImplementsInterface both walk the same UClass::Interfaces array and test the same relationship with IsChildOf. Exactly one extra condition separates them.

UObjectBaseUtility::GetInterfaceAddress (Engine/Source/Runtime/CoreUObject/Private/UObject/UObjectBaseUtility.cpp), in the branch for a native interface:

for (TArray<FImplementedInterface>::TIterator It(CurrentClass->Interfaces); It; ++It)
{
    // See if this is the implementation we are looking for, and it was done natively, not in K2
    FImplementedInterface& ImplInterface = *It;
    if ( !ImplInterface.bImplementedByK2 && ImplInterface.Class->IsChildOf(InterfaceClass) )
    {
        Result = (uint8*)this + It->PointerOffset;
        break;
    }
}

UClass::ImplementsInterface (Engine/Source/Runtime/CoreUObject/Private/UObject/Class.cpp), with the same loop:

for (TArray<FImplementedInterface>::TConstIterator It(CurrentClass->Interfaces); It; ++It)
{
    const UClass* InterfaceClass = It->Class;
    if (InterfaceClass && InterfaceClass->IsChildOf(SomeInterface))
    {
        return true;
    }
}

The entire difference is !ImplInterface.bImplementedByK2. That flag marks an entry produced by the Blueprint compiler, and the engine’s own comment says exactly that: “and it was done natively, not in K2.” ImplementsInterface has no such filter, so it gives the broader answer: the class implements the interface. GetInterfaceAddress gives a narrower answer: the class implements the interface natively.

Insight
ImplementsInterface asks whether the class implements the interface. GetInterfaceAddress, the function behind Cast<I*>, asks a narrower question: whether the class implements it natively. For a class whose interface implementation exists only in Blueprint, the first returns true, while the second returns nullptr.

And GetInterfaceAddress is the cast. Not figuratively but literally, in Engine/Source/Runtime/CoreUObject/Public/Templates/Casts.h:

if constexpr (TIsIInterface<To>::Value)
{
    return (To*)((UObject*)Src)->GetInterfaceAddress(To::UClassType::StaticClass());
}

A cast to an interface never touches the class-cast-flag path used by a plain Cast<AActor>. Instead, it calls a function designed specifically to exclude Blueprint implementations.

Why there is nothing to return

This looks like an oversight. It is a constraint of the memory layout.

A C++ class that implements IMyInterface inherits from it, so its memory layout contains an IMyInterface subobject at a known offset. That offset is exactly what the cast is built on: (uint8*)this + It->PointerOffset. If the interface is implemented only in Blueprint, there is no corresponding native IMyInterface subobject, so there is nothing for PointerOffset to describe. Without that subobject, there is no IMyInterface vtable either, and therefore no address for the cast to return.

A Blueprint implementation exists in a completely different form. It is a UFunction on the Blueprint class, invoked through the virtual machine. No valid pointer exists, so nullptr is the only honest answer.

Cast itself is what makes this misleading: it looks like a uniform reflection mechanism, but in this case it is really asking about native memory layout.

The fix: Execute_

For every interface function, UHT generates a static Execute_Foo wrapper. AppendInterfaceCallFunction (Engine/Source/Programs/Shared/EpicGames.UHT/Exporters/CodeGen/UhtHeaderCodeGeneratorCppFile.cs) emits the body, which expands to this:

static FName NAME_UMyInterface_Foo = FName(TEXT("Foo"));
void IMyInterface::Execute_Foo(UObject* O)
{
    check(O != NULL);
    check(O->GetClass()->ImplementsInterface(UMyInterface::StaticClass()));
    UFunction* const Func = O->FindFunction(NAME_UMyInterface_Foo);
    if (Func)
    {
        O->ProcessEvent(Func, NULL);
    }
    else if (auto I = (IMyInterface*)(O->GetNativeInterfaceAddress(UMyInterface::StaticClass())))
    {
        I->Foo_Implementation();
    }
}

The order is the opposite of what you would expect. FindFunction comes first. That is the reflection path, and it finds a Blueprint override just as well as a UHT-registered native function. Only when there is no UFunction does the wrapper retrieve the native subobject address and call _Implementation directly.

Execute_ gates itself on ImplementsInterface, the broader check, not on the existence of an interface address.

The correct call site therefore looks like this:

if (Obj->Implements<UMyInterface>())
{
    IMyInterface::Execute_Foo(Obj);
}

A second safeguard sits in the native body of the event function itself. For interfaces, UHT emits that body as a hard assertion:

check(0 && "Do not directly call Event functions in Interfaces. Call Execute_Foo instead.");

That gives Cast<IMyInterface>(Obj)->Foo() two independent ways to fail. Either the cast returns nullptr and the program crashes on the dereference, or the cast succeeds and the call runs straight into the assertion. The nullptr failure is worse because it occurs only for objects whose interface implementation comes from Blueprint.

When the cast is valid

A cast to I* covers every implementation of the interface under exactly one condition: the interface must be explicitly non-implementable in Blueprint. Then bImplementedByK2 is never set on any entry, so GetInterfaceAddress and ImplementsInterface always agree.

UINTERFACE(BlueprintType, meta=(CannotImplementInterfaceInBlueprint), MinimalAPI)
class UDamageSource : public UInterface { GENERATED_BODY() };
Tip
This leads to the rule I follow: casting to I* is allowed only when I declare the interface myself and explicitly prevent it from being implemented in Blueprint. Everywhere else, use Implements<> plus Execute_.

Verification

I verified this in the editor, not just by reading the source. The probe collects three readings for the same object: ImplementsInterface, a cast to the interface type, and GetNativeInterfaceAddress. It then calls Execute_. The test interface allows Blueprint implementations, so the test can cover the case where the cast fails:

UINTERFACE(Blueprintable)
class UPickupTarget : public UInterface { GENERATED_BODY() };

class IPickupTarget
{
    GENERATED_BODY()
public:
    UFUNCTION(BlueprintNativeEvent, Category = "Probe")
    void OnPicked();
};

// Control group: native implementation.
UCLASS()
class ANativePickup : public AActor, public IPickupTarget
{
    GENERATED_BODY()
public:
    virtual void OnPicked_Implementation() override
    {
        UE_LOG(LogTemp, Warning, TEXT("[probe] OnPicked from C++"));
    }
};

UCLASS()
class UInterfaceProbe : public UBlueprintFunctionLibrary
{
    GENERATED_BODY()
public:
    UFUNCTION(BlueprintCallable, Category = "Probe")
    static void Probe(UObject* Obj);
};

void UInterfaceProbe::Probe(UObject* Obj)
{
    if (!Obj)
    {
        return;
    }

    const bool bImplements = Obj->GetClass()->ImplementsInterface(UPickupTarget::StaticClass());
    IPickupTarget* AsInterface = Cast<IPickupTarget>(Obj);
    void* NativeActor = Obj->GetNativeInterfaceAddress(UPickupTarget::StaticClass());

    UE_LOG(LogTemp, Warning, TEXT("[probe] %s | Implements=%s | Cast=%s | NativeActor=%s"),
        *Obj->GetClass()->GetName(),
        bImplements ? TEXT("true") : TEXT("false"),
        AsInterface ? TEXT("ptr") : TEXT("NULL"),
        NativeActor ? TEXT("ptr") : TEXT("NULL"));

    if (bImplements)
    {
        IPickupTarget::Execute_OnPicked(Obj);
    }
}

The second implementation of the same interface is created in the editor: a Blueprint Actor class named BP_BlueprintPickup, with the Pickup Target interface added under Class Settings and an On Picked event that prints [probe] OnPicked from BP.

The BP_BlueprintPickup event graph. This On Picked implementation does not exist in any C++ file, and it is what makes the class's entry in UClass::Interfaces carry bImplementedByK2 = true.

The Level Blueprint of an empty map drives the test. NativePickup is spawned in BeginPlay. BP_BlueprintPickup is placed in the level and retrieved with Get Actor of Class. Both objects pass through the same Probe along a single execution chain:

Level Blueprint of the test map. One BeginPlay, two actors, the same Probe function. The test compares native and Blueprint interface implementations.

The log from that run:

LogTemp: Warning: [probe] NativePickup | Implements=true | Cast=ptr | NativeActor=ptr
LogTemp: Warning: [probe] OnPicked from C++
LogTemp: Warning: [probe] BP_BlueprintPickup_C | Implements=true | Cast=NULL | NativeActor=NULL
LogBlueprintUserMessages: [probe] OnPicked from BP

In the third line, Implements=true sits next to Cast=NULL for the same object in the same call. The fourth line shows Execute_ invoking the implementation despite the NULL. The LogBlueprintUserMessages category confirms that the call landed in the Blueprint graph rather than the native fallback.

The first two lines are the control group. For a native implementation, all three readings agree and the cast works. The comparison focuses on where the interface implementation comes from.

Limits

An interface class declared in Blueprint rather than in C++ β€” that is, one without CLASS_Native β€” behaves differently. For such an interface, GetInterfaceAddress never enters the loop over Interfaces. It returns this, provided ImplementsInterface is true. That is a separate branch of the same function in Engine/Source/Runtime/CoreUObject/Private/UObject/UObjectBaseUtility.cpp.

Warning
CastChecked<IMyInterface> does not make this pattern safe. It only changes where the failure occurs. With DO_CHECK enabled, it raises a fatal error, which is the behavior you want. In a build without DO_CHECK, the unchecked definition of CastChecked in Casts.h takes over and returns whatever GetInterfaceAddress gives it, without any check. That definition is itself marked FUNCTION_NON_NULL_RETURN, so it promises the caller a non-null pointer while potentially handing back nullptr. On that basis, the compiler is free to remove null checks at the call site. The behavior becomes least predictable in exactly the configuration where it is hardest to diagnose.

This also has consequences for testing. A test built entirely from C++ classes will never catch the problem. Every implementation is native, bImplementedByK2 is false everywhere, and the cast works every time. You can see that in the first two lines of the log above, where the control group passes. A test must include a Blueprint that implements the interface; otherwise, it exercises only the variant that was already correct.

As for the limits of the evidence: the mechanism comes from reading the UE 5.7 source (UObjectBaseUtility.cpp, Class.cpp, Casts.h, and the UHT generator) and was confirmed by one run in the editor on the same version. I have not checked whether PointerOffset and the bImplementedByK2 filter behave the same way in older engine branches.

Sources

  • UObjectBaseUtility::GetInterfaceAddress, GetNativeInterfaceAddress β€” Engine/Source/Runtime/CoreUObject/Private/UObject/UObjectBaseUtility.cpp
  • UClass::ImplementsInterface β€” Engine/Source/Runtime/CoreUObject/Private/UObject/Class.cpp
  • Cast / CastChecked for interfaces β€” Engine/Source/Runtime/CoreUObject/Public/Templates/Casts.h
  • Execute_ generation β€” Engine/Source/Programs/Shared/EpicGames.UHT/Exporters/CodeGen/UhtHeaderCodeGeneratorCppFile.cs, AppendInterfaceCallFunction
  • Unreal Docs: Interfaces β€” the official description of UINTERFACE and Execute_ calls

Related pages