본문 바로가기

Unreal/문제해결

언리얼: LNK2001, LNK2019 등 unresolved external symbol 해결

언리얼 엔진은 C++를 언어를 사용하고, 여러 모듈을 참조해가며 작업해야 할 일이 잦습니다. 주기적으로 컴파일을 시도한다면 함수의 구현부를 작성하는 점을 잊는 등의 사소한 실수도 쉽게 파악해 큰 덩어리의 문제로 커질 일이 거의 없습니다. 때로는 어느 정도 규모의 작업을 한 번에 수행한 뒤 컴파일 및 런타임 테스트를 진행해야 하는 상황이 생깁니다. 일반적으로는 이미 만들어진 프레임워크의 리팩터링을 진행할 때 접할 수 있습니다. 

 

여러가지 상황의 테스트 환경에서 다양한 개발을 시도하는 과정에서 많은 수의 링커 예외가 발생하면 당황스러울 수 있는데요, 다음 번 이와 같은 상황을 맞닥뜨렸을 때를 위해 순차적으로 확인해보았던 요소들을 기록해둡니다.


1. 함수의 구현부의 부재

헤더에 클래스를 정의하고 함수를 선언하였으나, 구현부를 작성하지 않았을 수 있습니다. 

 

직접적인 콘텐츠 로직을 개발하다보면. "어떻게 함수 구현을 수행하지 않았을 수 있느냐"는 의문이 들 수도 있지만, 복잡한 구조의 상속관계를 포함해 추상 클래스와 인터페이스를 혼용하다보면 종종 발생할 수 있는 일입니다. 헤더에 선언만 하고 구현해 두지 않았을 때, 다른 곳에서 호출만 하지 않는다면 구현된 함수를 찾지 않아 문제가 생기지 않다가, 나중에 어디에선가 헤더에 선언된 부분만 보고 사용을 시도했을 때 수면 위로 드러나기도 합니다. 

 

추상 클래스였던 함수가 일반 클래스가 되는 등의 변동 사항이 있을 때는 확인해 주는 것이 좋습니다.

 

2. 헤더에 정의된 구현부

일반 가상 함수의 구현부가 헤더에 직접 정의된 경우 언리얼 헤더 툴이 오작동을 일으킬 수 있습니다. 

이 부분은 예제 코드가 있는 편이 직관적일 듯 합니다. 

 

// FGameContentsPack.h
#pragma once

#include "CoreMinimal.h"
#include "FPatchablePack.h"

class FFeaturePackArchetype;

class SHOOTERGAME_API FGameContentsPack : public FPatchablePack {
public:
	virtual bool HasExtendedFeaturePack() override { return this->ExtendedFeaturePack.IsValid(); }
    
private:
	TSharedPtr<FFeaturePackArchetype> ExtendedFeaturePack;
};

// FGameContentsPack.cpp
#include "FFeaturePackArchetype.h"

일반적인 케이스에서는 이 코드는 문제를 일으키지 않지만, 상속 구조 간에 가상 클래스의 가상 함수, 추상 클래스가 끼이고 여러 모듈간에 상속을 받게되는 경우 컴파일 타임에서 UBT가 제대로 구현부를 링킹해주지 못하는 경우가 생길 수 있습니다.

 

저는 구현부를 cpp 파일에 분리해주어 링킹을 찾지 못하는 문제를 해결하였는데요, 사실 왜 이런 일이 발생하는지 명확하게는 파악하지 못했습니다. 결과적으로 보았을 때는 이 원인을 엄밀하게 결론짓지 못했기 때문에 이와 같은 이슈를 겪고 해결하는데 시간을 소모하게 되었을지도 모릅니다. 관련하여서는 제보해주시면 정말 감사할 듯 합니다.

 

3. 다른 모듈에서 인식하지 못하는 경우

다른 모듈에 정의된 클래스를 사용하고 싶은데, 링커가 오작동 하는 경우에는 클래스 정의부에 리플렉션 코드 생성을 위한 모듈 API 매크로가 사용되었는지 확인해 보아야 합니다. 

 

마찬 가지로 위에서 사용했던 예제를 그대로 사용해보려고 하는데요. 

// FGameContentsPack.h
#pragma once

#include "CoreMinimal.h"
#include "FPatchablePack.h"

class FFeaturePackArchetype;

class SHOOTERGAME_API FGameContentsPack : public FPatchablePack {
public:
	virtual bool HasExtendedFeaturePack() override { return this->ExtendedFeaturePack.IsValid(); }
    
private:
	TSharedPtr<FFeaturePackArchetype> ExtendedFeaturePack;
};

// FGameContentsPack.cpp
#include "FFeaturePackArchetype.h"

이 클래스에서는 GameContentsPack 클래스를 정의할 때, SHOOTERGAME_API 이 부분이 위에서 말씀드린 매크로 부분입니다. 이 매크로는 적절히 dllexport 와 같은 형태로 다른 모듈에 해당 API를 노출해 주는데요, 이 매크로가 없으면 클래스를 사용하는 모듈에서 dllimport에 실패하게 되어 예외가 발생합니다. 

 

4. 다른 모듈에 구현부가 존재하는 경우

1번 케이스와 유사한 이유로 발생하게 된다고 볼 수 있습니다. 

 

LogCategory를 정의할 때 발생하는 경우가 있는데요, 헤더 파일에는 DECLARE_LOG_CATEGORY_EXTERN(LogMyLog, Log, All); 로 정의를 소스 파일에는 DEFINE_LOG_CATEGORY(LogMyLog); 와 같은식으로 자주 사용합니다. 혹시나 다른 모듈에서 직접 정의한 로그 카테고리에 대해 링커 예외가 발생하는 경우 로그를 사용하는 모듈에서도 구현부를 작성해 줘 보세요. 

 

소스 파일을 찾지 못해서 발생하는 문제인지 아닌지 확인할 수 있게 됩니다. 

 


문제가 해결되셨길 바라며, 혹여나 위에 언급된 예외와 관계있지만 기록되지 않은 이슈를 겪고 해결하셨다면 정보 공유를 해 주시면 많은 분들께 도움이 될 거라 생각합니다. 아무쪼록, 즐거운 개발 생활 되시길 기원합니다.