库工程设置

vs创建一个动态链接库工程:

在属性面板里开启调试符号, 调试信息格式设置为用于“编辑并继续”的程序数据库

编译生成 .dll 和 .lib

第二种方式就是通过 CMake, 去CMake 官网下载相关的编译软件, 然后编写CMakelist.txt

cmake_minimum_required(VERSION 2.8)

if ( WIN32 AND NOT CYGWIN AND NOT ( CMAKE_SYSTEM_NAME STREQUAL "WindowsStore" ) AND NOT ANDROID)
 set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /MT" CACHE STRING "")
 set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} /MTd" CACHE STRING "")
 set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MT" CACHE STRING "")
 set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /MTd" CACHE STRING "")
endif ()

project(TestUE)
if ( IOS )
        set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fembed-bitcode")
        set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fembed-bitcode")
endif ()
option ( UINT_ESPECIALLY "using custom ulong" OFF )
option ( GC64 "using gc64" OFF )
find_path(TestUE_PROJECT_DIR NAMES SConstruct
    PATHS 
    ${CMAKE_SOURCE_DIR}
    NO_DEFAULT_PATH
    )
MARK_AS_ADVANCED(TestUE_PROJECT_DIR)
set(SRC_PATH TestUE)

include_directories(
    ${CMAKE_SOURCE_DIR}
 ${SRC_PATH}
)

aux_source_directory(${SRC_PATH} SRC_CORE)

source_group_by_dir(${CMAKE_CURRENT_SOURCE_DIR} SRC_CORE)

if (APPLE)
    if (IOS)
        set(CMAKE_OSX_ARCHITECTURES "$(ARCHS_STANDARD)")
        add_library(TestUE STATIC
           ${SRC_CORE}
        )
  set_xcode_property (TestUE IPHONEOS_DEPLOYMENT_TARGET "7.0" "all")
    else ()
        if (BUILD_SILICON)
           set(CMAKE_OSX_ARCHITECTURES arm64)

            add_library(TestUE SHARED
                ${SRC_CORE}
            )
        else ()
            set(CMAKE_OSX_ARCHITECTURES "$(ARCHS_STANDARD_64_BIT)")
            add_library(TestUE MODULE
                ${SRC_CORE}
            )
            set_target_properties ( TestUE PROPERTIES BUNDLE TRUE )
        endif ()
    endif ()
elseif ("${CMAKE_SYSTEM_NAME}" STREQUAL "Switch")
    add_library(TestUE STATIC
        ${SRC_CORE}
    )
    target_compile_options(TestUE PRIVATE -m64 -mcpu=cortex-a57+fp+simd+crypto+crc -fno-common -fno-short-enums -ffunction-sections -fdata-sections -fPIC -fms-extensions)
else ( )
    add_library(TestUE SHARED
        ${SRC_CORE}
    )
endif ( )

if ( WIN32 AND NOT CYGWIN )
    target_compile_definitions (TestUE PRIVATE LUA_BUILD_AS_DLL)
endif ( )

if(UINT_ESPECIALLY)
    ADD_DEFINITIONS(-DUINT_ESPECIALLY)
endif()

在cmake里指定工程组织的结构, 相关编译的宏, link需要的flag, 然后通过shell或者bat生成的相关的工程或者库, 例如生成vs工程 可以使用如下build.bat. (需要安装vs2019)

mkdir build64 & pushd build64
cmake -G "Visual Studio 16 2019" ..
popd
cmake --build build64 --config Release
md Plugins\x86_64
copy /Y build64\Release\TestUE.dll Plugins\x86_64\TestUE.dll
copy /Y build64\Release\TestUE.lib Plugins\x86_64\TestUE.lib
pause

在比如 编译 android 的so可以使用 build.sh 的shell脚本(Mac环境下, 需要本地下载了NDK)

if [ -n "$ANDROID_NDK" ]; then
    export NDK=${ANDROID_NDK}
elif [ -n "$ANDROID_NDK_HOME" ]; then
    export NDK=${ANDROID_NDK_HOME}
elif [ -n "$ANDROID_NDK_HOME" ]; then
    export NDK=${ANDROID_NDK_HOME}
else
    export NDK=~/Documents/android-sdk/ndk
fi

if [ ! -d "$NDK" ]; then
    echo "Please set ANDROID_NDK environment to the root of NDK."
    exit 1
fi

function build() {
    API=$1
    ABI=$2
    TOOLCHAIN_ANME=$3
    BUILD_PATH=build.Android.${ABI}
    cmake -H. -B${BUILD_PATH} -DANDROID_ABI=${ABI} -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=${NDK}/build/cmake/android.toolchain.cmake -DANDROID_NATIVE_API_LEVEL=${API} -DANDROID_TOOLCHAIN=clang -DANDROID_TOOLCHAIN_NAME=${TOOLCHAIN_ANME}
    cmake --build ${BUILD_PATH} --config Release
    mkdir -p Plugins/Android/libs/${ABI}/
    cp ${BUILD_PATH}/libTestUE.so Plugins/Android/libs/${ABI}/libTestUE.so
}

build android-16 armeabi-v7a arm-linux-androideabi-4.9
build android-16 arm64-v8a  arm-linux-androideabi-clang

头文件处理, 在 vs 工程导出的时候, 函数名前需要加上 __declspec(dllexport) (针对Windows上编译dll)

#ifdef _API_IMPORTS
#define DLL_API __declspec(dllexport)
#else
#define DLL_API 
#endif // _API_IMPORTS

DLL_API int AddNumbers(int a, int b);

当导入到Unreal, 配置 build.cs 关联进去, 相关的 需要改为 dllimport

#ifdef _API_IMPORTS
#define DLL_API __declspec(dllimport)
#else
#define DLL_API 
#endif // _API_IMPORTS

DLL_API int AddNumbers(int a, int b);

目录结构

在工程根目录新建

- ThirdParty
-- XXX(lib Name)
--- android
--- include 
--- x64
--- IOS

Build.cs

找到工程目录下 **.Build.cs, 添加 ThirdPartyPath 路径指定:

private string ThirdPartyPath
{
    get
    {
        return Path.GetFullPath(Path.Combine(ModuleDirectory, "../../ThirdParty"));
    }
}

在 Build的构造函数里指定 头文件路径, 库的搜索路径和相关引用的库名称。 还可以使用 Target.Platform 来区分不同的平台

//Type = ModuleType.External;
string exPath = ThirdPartyPath + "/TestUE";
PublicIncludePaths.Add(exPath + "/include/");

if (Target.Platform == UnrealTargetPlatform.Win64)
{
    PublicDefinitions.AddRange(new string[] { "_API_IMPORTS" });
    //PublicSystemLibraryPaths.Add(exPath + "/x64");
    Console.WriteLine("File Exist: " + Directory.Exists(exPath + "/x64"));
    PublicAdditionalLibraries.Add(exPath + "/x64/TestUE.lib");
}
else if (Target.Platform == UnrealTargetPlatform.Android)
{
    AdditionalPropertiesForReceipt.Add("AndroidPlugin", Path.Combine(exPath, "My_APL_armv7.xml"));
    PublicSystemLibraryPaths.Add(Path.Combine(exPath, "android"));
    PublicAdditionalLibraries.Add("TestUE");
}
else if (Target.Platform == UnrealTargetPlatform.IOS)
{
    PublicSystemLibraryPaths.Add(exPath + "/IOS");
}

build.cs 支持 log输出, 在vs编译生成的时候, 输出选项栏里可以看到相关的提示:

Console.WriteLine("File Exist: " + Directory.Exists(exPath + "/include"));

如果是库工程, 可以指定 Type 为 External。

Type = ModuleType.External;

编写好 build.cs之后, 需要重新生成, 右键选择 uproject 文件, 重新生成 vs 工程文件。

导出

Win64 导出, 需要手动copy动态链接库(.dll), 到.exe 同目录下。 启动 xxx/Binaries/Win64 下的.exe文件

Android的生成时, 需要配置 _armv7.xml。

<?xml version="1.0" encoding="utf-8"?>
<!-- steps to add to build additions -->
<root xmlns:android="http://schemas.android.com/apk/res/android">
 <!-- init section is always evaluated once per architecture -->
 <init>
     <setBool result="bSupported" value="false"/>
         <isArch arch="armeabi-v7a">
             <setBool result="bSupported" value="true"/>
         </isArch>
 </init>
 
 <!-- optional files or directories to copy to Intermediate/Android/APK -->
 <resourceCopies>
     <isArch arch="armeabi-v7a">
         <copyFile src="$S(BuildDir)/../../../ThirdParty/TestUE/android/libTestUE.so"
                   dst="$S(BuildDir)/libs/armeabi-v7a/libTestUE.so" />
  </isArch>
 </resourceCopies>
 
 <!-- optional libraries to load in GameActivity.java before libUE4.so -->
 <soLoadLibrary>
     <if condition="bSupported">
         <true>
   <loadLibrary name="TestUE" failmsg="Failed to load myso library" />
  </true>
     </if>
 </soLoadLibrary>
</root>

它的作用是:

(1)在打包Android应用时,将AndroidProject/ThirdParty/TestUE/android/libTestUE.so文件拷贝到AndroidProject/Intermediate/Android/APK/libs/armeabi-v7a/目录下。这样就不用手动拷贝了。

(2)在AndroidProject\Intermediate\Android\APK\src\com\epicgames\ue4\GameActivity.java文件中生成加载TestUE库的代码:

try
{
 System.loadLibrary("TestUE");
}
catch (java.lang.UnsatisfiedLinkError e)
{
 Log.debug(e.toString());
 Log.debug("Failed to load TestUE library");
}

Note:

Android某些关联的插件可能会因为 墙 的原因而下载不了, 这里可以直接使用 Android Studio 直接打开生成的Android 工程, 在AS里设置好代理之后, 再进行编译运行。 Android生成路径一般在 Intermediate\Android\armv7\gradle目录下。

断点调试

在vs里 以 DebugGame Editor启动 UE (调试-> 开始执行(不调试)),

UE 启动之后, 然后使用vs 打开 dll 库工程, 选择 Debug -> Attach Process, 在弹出的进程 列表里选择 UE4 。

附加上去就可以命中断点了