UE 集成 Protobuf

我之前的文章 Build protobuf with MSVC on Windows 中介绍了在 Windows 上使用 MSVC 编译 Protobuf,最近的项目中有用到 Protobuf,就把 Protobuf 集成 UE4 做了一个插件,无需在系统和项目设置中添加任何环境变量以及文件包含,也不需要考虑 protobuflib的链接,方便在项目中使用,并且对 UE 不兼容 cc 格式写了编辑器插件,.pb.cc都会被转换为pb.cpp

代码我放在了 github 上:hxhb/ue4-protobuf

下面是简单的使用介绍:

What is this?

This is an Unreal Engine 4 plugin that integrates Protobuf into the project without requiring you to add system PATH or anything else.

How do use?

  1. add the plugin to the project and enable it.
  2. add the following property to build.cs of the project :
1
2
3
4
PublicDependencyModuleNames.Add("Protobuf");
bEnableUndefinedIdentifierWarnings = false;
bUseRTTI = true;
bEnableExceptions = true;
  1. Create .proto file into project source code folder
  2. Launch the Project in Editor, Click the Protoc button.

Protobuf Version

  • Protobuf v3.9.1 build with MSVC 15 64bit (Visual Studio 2017).

protoc 处理目录中的.proto 文件

1
$ protoc --proto_path=PROTO_FILE_PATH PROTO_FILE --cpp_out=OUT_FILE_PATH PROTO_FILE_PATH\*.proto

如:

1
$ protoc --proto_path="d:\protoc" MyName.proto --cpp_out="d:\"

Was only expecting C++ files to have CachedCPPEnvironments!

写了个插件在 UE 中使用 protobuf,在从 proto 文件生成 .cc/.h 之后编译报这个错误,分析了一下是 UBT 的代码文件类型不支持.cc

1
2
3
4
1>------ Build started: Project: GWorld, Configuration: Development x64 ------
1>Using 'git status' to determine working set for adaptive non-unity build (C:\Users\imzlp\Documents\Unreal Projects\GWorld).
1>UnrealBuildTool : error : Was only expecting C++ files to have CachedCPPEnvironments!
1>(see ../Programs/UnrealBuildTool/Log.txt for full exception trace)

在引擎中搜索代码,发现这个错误是在 UnrealBuildTools\System\ActionGraph.csIsActionOutdated里的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public bool IsActionOutdated(BuildConfiguration BuildConfiguration, UEBuildTarget Target, CPPHeaders Headers, Action RootAction, bool bIsAssemblingBuild, bool bNeedsFullCPPIncludeRescan, Dictionary<Action, bool> OutdatedActionDictionary, ActionHistory ActionHistory, Dictionary<UEBuildTarget, List<FileItem>> TargetToOutdatedPrerequisitesMap)
{
// ...
// @todo ubtmake: we may be scanning more files than we need to here -- indirectly outdated files are bIsOutdated=true by this point (for example basemost includes when deeper includes are dirty)
if (bIsOutdated && RootAction.ActionType == ActionType.Compile)// @todo ubtmake: Does this work with RC files? See above too.
{
Log.TraceVerbose("Outdated action: {0}", RootAction.StatusDescription);
foreach (FileItem PrerequisiteItem in RootAction.PrerequisiteItems)
{
if (PrerequisiteItem.CachedIncludePaths != null)
{
if (!IsCPPFile(PrerequisiteItem))
{
throw new BuildException("Was only expecting C++ files to have CachedCPPEnvironments!");
}
Log.TraceVerbose(" -> DEEP include scan: {0}", PrerequisiteItem.AbsolutePath);

List<FileItem> OutdatedPrerequisites;
if (!TargetToOutdatedPrerequisitesMap.TryGetValue(Target, out OutdatedPrerequisites))
{
OutdatedPrerequisites = new List<FileItem>();
TargetToOutdatedPrerequisitesMap.Add(Target, OutdatedPrerequisites);
}

OutdatedPrerequisites.Add(PrerequisiteItem);
}
else if (IsCPPImplementationFile(PrerequisiteItem) || IsCPPResourceFile(PrerequisiteItem))
{
Log.TraceVerbose(" -> WARNING: No CachedCPPEnvironment: {0}", PrerequisiteItem.AbsolutePath);
}
}
}
// ...
}

重点就是 IsCPPFile 这个函数,报这个错误是因为 UBT 检测到了项目中不能识别的文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/// <summary>
/// Checks if the specified file is a C++ source file
/// </summary>
/// <param name="FileItem">The file to check</param>
/// <returns>True if this is a C++ source file</returns>
private static bool IsCPPFile(FileItem FileItem)
{
return IsCPPImplementationFile(FileItem) || IsCPPIncludeFile(FileItem) || IsCPPResourceFile(FileItem);
}

/// <summary>
/// Checks if the specified file is a C++ source implementation file (e.g., .cpp)
/// </summary>
/// <param name="FileItem">The file to check</param>
/// <returns>True if this is a C++ source file</returns>
private static bool IsCPPImplementationFile(FileItem FileItem)
{
return (FileItem.AbsolutePath.EndsWith(".cpp", StringComparison.InvariantCultureIgnoreCase) ||
FileItem.AbsolutePath.EndsWith(".c", StringComparison.InvariantCultureIgnoreCase) ||
FileItem.AbsolutePath.EndsWith(".mm", StringComparison.InvariantCultureIgnoreCase));
}

/// <summary>
/// Checks if the specified file is a C++ source header file (e.g., .h or .inl)
/// </summary>
/// <param name="FileItem">The file to check</param>
/// <returns>True if this is a C++ source file</returns>
private static bool IsCPPIncludeFile(FileItem FileItem)
{
return (FileItem.AbsolutePath.EndsWith(".h", StringComparison.InvariantCultureIgnoreCase) ||
FileItem.AbsolutePath.EndsWith(".hpp", StringComparison.InvariantCultureIgnoreCase) ||
FileItem.AbsolutePath.EndsWith(".inl", StringComparison.InvariantCultureIgnoreCase));
}

/// <summary>
/// Checks if the specified file is a C++ resource file (e.g., .rc)
/// </summary>
/// <param name="FileItem">The file to check</param>
/// <returns>True if this is a C++ source file</returns>
private static bool IsCPPResourceFile(FileItem FileItem)
{
return (FileItem.AbsolutePath.EndsWith(".rc", StringComparison.InvariantCultureIgnoreCase));
}

可以看到,UBT 只认下列几种文件:

  • IsCPPImplementationFile.cpp/.c/.m
  • IsCPPIncludeFile.h/.hpp/.inl
  • IsCPPResourceFile.rc

因为 Protobuf 产生的是 .cc 文件,所以就会报开头的错误。

既然问题找到了。那么解决办法有两个:

  1. 改 UBT 的代码,让其支持.cc
  2. 改 protobuf 产生的 .cc.cpp