Updating a Plugin with Third-Party Library for UE5

General / 24 July 2023

I felt it was time to update my Movie Render Queue OpenTimelineIO Plugin for Unreal Engine 5, unfortunately this proved a little less straightforward than hoped so I decided to document the process here for future reference.

TLDR

  • Unreal Engine 5 has changed some class methods, and method signatures that need to be updated.
  • Unreal Engine 5 compiles using C++ 17 so if you use a third party library with dependencies that behave differently depending on the C++ version make sure to compile your library using C++ 17 to avoid linker errors.

First things first, I copied the plugin into an UE5 demo project, the demo project has a single demo C++ class - this C++ class is needed to generate the Visual Studio project files.

Then I tried to simply launch the project, the project prompted me that the Plugin was written in a different version of Unreal Engine so it needed to be recompiled. I clicked for it to be recompiled, alas no luck, it failed to compile so time to re-generate the visual studio project files and then open the project in Visual Studio (double click the sln file). In Visual Studio I tried to build it again (right click module name -> rebuild) to generate some more useful error messages.

Error    C2664    'void UMoviePipeline::ResolveFilenameFormatArguments(const FString &,const TMap> &,FString &,FMoviePipelineFormatArgs &,const FMoviePipelineFrameOutputState *,const int32) const': cannot convert argument 2 from 'FStringFormatNamedArguments' to 'const TMap> &'    UnrealOtioExporter    C:\Users\mvann\Documents\Unreal Projects\UnrealOtioExporter\Plugins\RenderQueueOtioOption\Source\RenderQueueOtioOption\Private\OtioMoviePipelineSetting.cpp    48

For the error it looks like an argument type changed in this method: In UE4 the ResolveFilenameFormatArguments method expects a second argument of type FStringFormatNamedArguments, but in UE5 that third argument is now of type TMap< FString, FString > as described in the docs.

That's a pretty straightforward fix:

Code updated, so let's try to compile again and see what new errors come up.

Error    LNK2019    unresolved external symbol "public: __cdecl opentimelineio::v1_0::Track::Track(class std::basic_string,class std::allocator > const &,class std::optional const &,class std::basic_string,class std::allocator > const &,class opentimelineio::v1_0::AnyDictionary const &)" (??0Track@v1_0@opentimelineio@@QEAA@AEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@AEBV?$optional@VTimeRange@v1_0@opentime@@@4@0AEBVAnyDictionary@12@@Z) referenced in function "protected: virtual void __cdecl UMoviePipelineOtioExporter::BeginExportImpl(void)" (?BeginExportImpl@UMoviePipelineOtioExporter@@MEAAXXZ)    UnrealOtioExporter    C:\Users\mvann\Documents\Unreal Projects\UnrealOtioExporter\Intermediate\ProjectFiles\Module.RenderQueueOtioOption.cpp.obj    1    


Error    LNK2019    unresolved external symbol "public: __cdecl opentimelineio::v1_0::Stack::Stack(class std::basic_string,class std::allocator > const &,class std::optional const &,class opentimelineio::v1_0::AnyDictionary const &,class std::vector > const &,class std::vector > const &)" (??0Stack@v1_0@opentimelineio@@QEAA@AEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@AEBV?$optional@VTimeRange@v1_0@opentime@@@4@AEBVAnyDictionary@12@AEBV?$vector@PEAVEffect@v1_0@opentimelineio@@V?$allocator@PEAVEffect@v1_0@opentimelineio@@@std@@@4@AEBV?$vector@PEAVMarker@v1_0@opentimelineio@@V?$allocator@PEAVMarker@v1_0@opentimelineio@@@std@@@4@@Z) referenced in function "protected: virtual void __cdecl UMoviePipelineOtioExporter::BeginExportImpl(void)" (?BeginExportImpl@UMoviePipelineOtioExporter@@MEAAXXZ)    UnrealOtioExporter    C:\Users\mvann\Documents\Unreal Projects\UnrealOtioExporter\Intermediate\ProjectFiles\Module.RenderQueueOtioOption.cpp.obj    1    


Error    LNK2019    unresolved external symbol "public: __cdecl opentimelineio::v1_0::Timeline::Timeline(class std::basic_string,class std::allocator > const &,class std::optional,class opentimelineio::v1_0::AnyDictionary const &)" (??0Timeline@v1_0@opentimelineio@@QEAA@AEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@V?$optional@VRationalTime@v1_0@opentime@@@4@AEBVAnyDictionary@12@@Z) referenced in function "protected: virtual void __cdecl UMoviePipelineOtioExporter::BeginExportImpl(void)" (?BeginExportImpl@UMoviePipelineOtioExporter@@MEAAXXZ)    UnrealOtioExporter    C:\Users\mvann\Documents\Unreal Projects\UnrealOtioExporter\Intermediate\ProjectFiles\Module.RenderQueueOtioOption.cpp.obj    1    


Error    LNK2019    unresolved external symbol "public: __cdecl opentimelineio::v1_0::Clip::Clip(class std::basic_string,class std::allocator > const &,class opentimelineio::v1_0::MediaReference *,class std::optional const &,class opentimelineio::v1_0::AnyDictionary const &)" (??0Clip@v1_0@opentimelineio@@QEAA@AEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@PEAVMediaReference@12@AEBV?$optional@VTimeRange@v1_0@opentime@@@4@AEBVAnyDictionary@12@@Z) referenced in function "protected: virtual void __cdecl UMoviePipelineOtioExporter::BeginExportImpl(void)" (?BeginExportImpl@UMoviePipelineOtioExporter@@MEAAXXZ)    UnrealOtioExporter    C:\Users\mvann\Documents\Unreal Projects\UnrealOtioExporter\Intermediate\ProjectFiles\Module.RenderQueueOtioOption.cpp.obj    1    


Error    LNK2019    unresolved external symbol "public: __cdecl opentimelineio::v1_0::ExternalReference::ExternalReference(class std::basic_string,class std::allocator > const &,class std::optional const &,class opentimelineio::v1_0::AnyDictionary const &)" (??0ExternalReference@v1_0@opentimelineio@@QEAA@AEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@AEBV?$optional@VTimeRange@v1_0@opentime@@@4@AEBVAnyDictionary@12@@Z) referenced in function "protected: virtual void __cdecl UMoviePipelineOtioExporter::BeginExportImpl(void)" (?BeginExportImpl@UMoviePipelineOtioExporter@@MEAAXXZ)    UnrealOtioExporter    C:\Users\mvann\Documents\Unreal Projects\UnrealOtioExporter\Intermediate\ProjectFiles\Module.RenderQueueOtioOption.cpp.obj    1    

So it got passed the compiler error, but now throws a bunch of linker errors... that's strange because these OpenTimelineIO libraries were working for UE4. As a sanity check I decided to pull the latest OpenTimelineIO code and rebuild the libraries. Then update all the headers, dll, and lib files in the UE5 project and finally try building the UE5 project again. Unfortunately that resulted in the exact same errors.

So my next course of action was to try to narrow down which line is causing the problem, by process of elimination (commenting out code) I found a problematic line which was the creation of the timeline object.

As a next step I decided to remove the importing of the opentimelineio library to see if I get the same error, this way I can eliminate whether the issue is that the entire library isn't getting linked or if it's just specific methods.

This resulted in many more linker errors, meaning the library did in fact link most of the methods correctly, there's just something about the creation of the timeline that's tripping it up. I spend a fair amount of time googling but without much result, then decided to leave it and came back to it the next day.

The next day I went back to the error message, what is it really telling me?

Error    LNK2019    unresolved external symbol "public: __cdecl opentimelineio::v1_0::Timeline::Timeline(class std::basic_string,class std::allocator > const &,class std::optional,class opentimelineio::v1_0::AnyDictionary const &)" (??0Timeline@v1_0@opentimelineio@@QEAA@AEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@V?$optional@VRationalTime@v1_0@opentime@@@4@AEBVAnyDictionary@12@@Z) referenced in function "protected: virtual void __cdecl UMoviePipelineOtioExporter::BeginExportImpl(void)" (?BeginExportImpl@UMoviePipelineOtioExporter@@MEAAXXZ)    UnrealOtioExporter    C:\Users\mvann\Documents\Unreal Projects\UnrealOtioExporter\Intermediate\ProjectFiles\Module.RenderQueueOtioOption.cpp.obj    1    

It's telling me that I'm using a method that it cannot find in the timeline.cpp.obj file, I copied the string it's using to find the method in the library.

??0Timeline@v1_0@opentimelineio@@QEAA@AEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@V?$optional@VRationalTime@v1_0@opentime@@@4@AEBVAnyDictionary@12@@Z

Then I opened the opentimeline.lib file (this is where the obj files during the linking process come from) and searched for the string; indeed I was unable to find it. However, I could see very similar methods, so I decided to delete characters until it found a match:

??0Timeline@v1_0@opentimelineio@@QEAA@AEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@V?$optional@VRationalTime@v1_0@opentime@@@optional_lite@nonstd@@AEBVAnyDictionary@12@@Z

Aha! So the lookup string is indeed slightly different, notably the optional_lite@nonstd bit here stands out. The timeline constructor uses the optional library, this is a dependency of opentimelineio and is included as an hpp file. I opened the hpp file in both projects, and there we have it: OpenTimelineIO does not use C++17 by default and thus optional_HAVE_STD_OPTIONAL is false, but the UE5 project does, so it ends up true there. This is what's resulting in the different lookup string between the library and the headers when called from the UE5 project.

Luckily there's a fairly simple fix for this; install OpenTimelineIO using C++ 17 so it creates a library which lookup strings will match the UE5 project. This can simply be done in the CMakeLists.txt file where I set CMAKE_CXX_STANDARD to 17, then use the cmakelists.txt file to re-install and generate the libraries.

I finally replaced the newly created lib and dll files in our UE5 poject, the project now compiles without errors!

Unfortunately it looks like a lot of the internals have changed between engines so will need to do some further cleanup to get the plugin's behaviour to be the same, I will update the repo when I finish it. (For example the MovieSceneShotSection potentially being a nullptr).