The cast to the interface looks like there is an engine bug. Sad to have to use ghar’s workaround - especially given that Rama’s tutorial with the Cast is one of the first google hits.
UObjectBaseUtility::GetInterfaceAddress is what is doing the casting. The c++ interface has the CLASS_Native flag (because it was written in C++), so it goes to the native lookup. This part looks like a copy paste of ::GetNativeInterfaceAddress - which fails if bImplementedByK2 is true (which it is in this situation).
removing that flag check allows it to properly return the interface structure (with 0 offset, so the calculation is fine), but of course this is an engine change.
This won’t work. If it’s implemented in BP, then at the C++ level the object does not inherit from the C++ interface and so it doesn’t have the virtual function table entries for the methods. Allowing the cast to return the address is just a recipe for crashing the engine.
This is simply not a bug, it’s a limitation of the way BP is built on top of C++. The accepted answer is not a workaround, it’s the (admittedly ugly) intended approach.
#pragma once
#include "Interface.h"
/**
* Fast and full checking of interface implementation including nullptr check, C++ cast check and blueprint check
* @param TIInterfaceType is interface IClass (not UClass)
* @param object is pointer to check
* @return true if object implements TIInterfaceType, false if nullptr or doesn't implement
*/
template<typename TIInterfaceType>
FORCEINLINE bool IsInterfaceImplementedBy(const UObject* const object)
{
return
// Nullptr is invalid
object &&
// C++-style cast much faster
(Cast<TIInterfaceType>(object) ||
// But blueprint implementation pass only this check
object->GetClass()->ImplementsInterface(TIInterfaceType::UClassType::StaticClass()));
}
You need to include this header into every interface declaration and use it in C++:
void ATestCodeGameModeBase::CallDoSomething(UObject* object)
{
// Clear check of what we mean
if (IsInterfaceImplementedBy<ITestInterface>(object))
{
ITestInterface::Execute_DoSomething(object);
}
else
{
UE_LOG(LogTemp, Error, TEXT("Object does not implement interface"));
}
}
// vs
void ATestCodeGameModeBase::CallDoSomething(UObject* object)
{
// Doesn't understandable
if (object && object->GetClass()->ImplementsInterface(UTestInterface::StaticClass()))
{
ITestInterface::Execute_DoSomething(object);
}
else
{
UE_LOG(LogTemp, Error, TEXT("Object does not implement interface"));
}
}