轉自:http://blog.csdn.net/u012999985/article/details/71554628
一.基本內容概述
最近做項目時經常看到build.cs文件,就想研究一下UE4中第三方庫的使用。通過網絡以及wiki確實獲取到不少有用的信息,但是沒有一篇文章,讓我看完就立刻明白的。所以,我在這里詳細的描述dll與lib在UE4中的使用,同時簡單描述一些基本原理,解決網上的一些文章由於描述不清而造成的誤導。
UE4本身有很多功能使用的是第三方的庫,如物理physX,模型優化Simplygon,SpeedTree以及steam平台相關內容等等。我們如果想使用一些自己的已經實現的庫(或者其他現成的庫)我們通常可以把庫文件放到下面這個的地方——\Engine\Source\ThirdParty目錄(下面的例子就是放到這個目錄的)。
(這里需要注意一下,庫文件的位置其實是隨意的,你只要讓引擎能找到他就行。比如你隨意的放了一個位置,那你可以通過下面幾個方法獲取一些特定的路徑,然后進一步尋找你的庫文件。
UEBuildConfiguration.UEThirdPartySourceDirectory方法可以獲取到當前所在第三方庫的目錄,
ModuleDirectory表示當前模塊的目錄,
*FPaths::GamePluginsDir()可以獲取到當前項目的插件目錄。
總之,多查查,多試試,各種API,沒有你找不到的文件)
如果想要使用自己的庫,就不得不提到UE4的編譯系統,UnrealBuildTool。UE4里面的項目都是以Module模塊為單位的,不同模塊之間關聯在一起,構成整個系統。UnrealBuildTool負責將不同的模塊編譯到一起,每一個Module模塊需要一個.build.cs文件。比如聯機需要的OnlineSubSystemSteam就是一個插件模塊,你同時還可以看到OnlineSubsystemFacebook等模塊。(當然,UnrealBuildTool做的工作可能比你想的還多,比如跨平台相關的處理等。)
那么我想添加第三方庫就出現了兩種選擇:
第一,自己在工程項目下新建一個目錄,直接在項目工程的.build.cs下配置這個第三方庫,比較直接了當。缺點是,多個靜態庫,就需要寫多行代碼,可能需要經常修改。
第二,通常我們添加一個庫之后,應該給這個庫建立一個Module(當然也就需要建立一個對應的庫模塊的.build.cs文件),方便管理與修改。項目直接調用這個Module即可。可以隨時通過修改項目工程的.build.cs安裝與卸載整個模塊。缺點是還要單獨建立一個第三方庫的Module,不如方法一簡單。
下面的例子中,我使用的是方法二。一般來說,第三方庫模塊的.build.cs文件與項目工程的.build.cs文件差異還是很大的。
(靜態庫與動態庫是有區別的。靜態庫是在編譯期就要鏈接到工程的dll里面的,所以需要去修改項目的build文件來配置。而第三方的動態庫是在運行時而不是在編譯的時候使用,所以一般不需要配置build文件,但需要在cpp里面獲取。)
二.第三方庫與插件的關系
對於不熟悉UE4的人,可能對第三方庫與插件的關系有點模糊。對於第三方庫,一般我們在F:\UnrealEngine4.14\Engine\Source\ThirdParty目錄下存放其源代碼,頭文件,lib等。而第三方庫的dll一般存在於F:\UnrealEngine4.14\Engine\Binaries\ThirdParty目錄下。正如字面上的理解,第三方庫更偏向於於一個代碼工具庫,直接服務於我們的代碼。
而插件,是直接服務於功能的。插件分為引擎插件與項目插件,分別位於F:\UnrealEngine4.14\Engine\Plugins與F:\UE4Project\項目名稱\Plugins,插件的源碼就位於這兩個文件夾,而其二進制文件通過引擎生成后同樣位於該目錄的Binary文件夾下。插件可以在不直接修改引擎代碼的前提下,為引擎添加完整獨立的新功能,或者修改引擎中內建的功能。每一個插件至少包含一個Module,你可以根據你插件里面的內容對模塊進行進一步的划分(參考下圖Media插件的布局分布,一個AndroidMedia還有好幾個Module)。我們的插件模塊與項目一樣,也可能需要包含第三方庫,需要在插件源碼的build文件里面配置。
關於插件的使用,網上有很多教程,這里貼出來幾篇給大家作為參考,如果有什么問題,歡迎大家在文章末尾提問。
http://blog.csdn.net/sh15285118586/article/details/53332951
http://blog.csdn.net/yangxuan0261/article/details/52098104
三.靜態庫:創建與使用流程
1.新建一個靜態庫lib(如果有庫文件就跳過這步)
在VS中,點擊新建項目——VisualC++——Win32項目(比如名稱為MyThirdParty)。
點擊確定后,在導航窗口中選擇靜態庫。
添加自己的類代碼,修改為x64平台並生成MyThirdParty.lib文件。(Debug與Release都可以)
(一般的非虛幻項目中,引用外部庫只需要設置,項目->屬性->配置屬性->VC++目錄,添加包含目錄,庫目錄,ok,代碼中載入庫文件#pragmacomment(lib," MyThirdParty.lib ");就可以了,然而像前面提到的,虛幻有自己的編譯系統,這么使用可以運行,但是無法打包)
2.在\Engine\Source\ThirdParty目錄下新建自己的庫模塊
在\Engine\Source\ThirdParty目錄下,新建文件夾並命名,這里以MyTestThirdParty為例。把用到的頭文件以及lib分別放到文件夾include,文件夾lib下。
給第三方庫模塊創建一個新的MyTestThirdParty.build.cs文件。
3.編輯MyTestThirdParty.build.cs文件
MyTestThirdParty.build.cs文件內容如下:
public classMyTestThirdParty : ModuleRules { publicMyTestThirdParty(TargetInfoTarget) { //表示第三方庫 Type= ModuleType.External; //第三方庫新模塊根目錄目錄路徑你可以通過其他方式來獲取路徑比如get { return Path.GetFullPath(Path.Combine(ModuleDirectory,"../../ThirdParty/"));} stringMyPath= UEBuildConfiguration.UEThirdPartySourceDirectory +"MyTestThirdParty/"; //包含的頭文件路徑,因為編譯的庫里面都是鏈接過的編譯單元,可以認為編譯單元是不包含頭文件的,所以在之后的使用時還需要獲取到頭文件的聲明信息 PublicIncludePaths.Add(MyPath +"include/"); if(Target.Platform== UnrealTargetPlatform.Win64) { //第三方靜態庫的路徑 PublicLibraryPaths.Add(MyPath+"lib/"); //第三方靜態庫的名稱 PublicAdditionalLibraries.Add("MyThirdParty.lib"); } } }
到此為止,我們的新的模塊的名稱就叫做MyTestThirdParty。
4.編輯工程.build.cs文件
一個新的項目的build.cs文件大概是這樣的,最后一行的代碼是需要自己添加的
usingUnrealBuildTool; publicclassMyCClassProjectA :ModuleRules { public MyCClassProjectA(TargetInfoTarget) { PublicDependencyModuleNames.AddRange(new string[] { "Core","CoreUObject","Engine","InputCore","HeadMountedDisplay" } ); //將新的第三方庫的模塊添加進來 AddThirdPartyPrivateStaticDependencies(Target,"MyTestThirdParty"); }
}
正如前面我所提到的,如果你沒有單獨給第三方庫添加一個模塊,你就可以直接在項目的build.cs添加靜態庫的相關配置(也就是第三步的內容)。
編譯后你會發現,無論是第三方庫還是項目,他的Binary文件夾都會有一個UE4Editor.modules文件。
5.配置工程屬性
右鍵項目屬性——NMake——IntelliSense——包含搜索路徑。添加庫的目錄位置。這樣項目就可以搜索到你的庫頭文件並使用了。
.在項目工程類里面#include你的庫里面的頭文件並測試
包含完文件,就可以正常的使用庫文件里面的內容了。修改完點擊生成。如果你發現有不識別的你所包含的頭文件的錯誤,那就重新確認一下Module模塊的名稱與路徑,肯定是這里出了問題。
(如果需要新建一個類,要注意你的類的.cpp文件的第一個包含#include“項目工程名.h”應該是項目工程名的頭文件。否則會編譯失敗。)
四.動態庫:創建與使用流程
1.新建一個dll(如果有庫文件就跳過這步)
在VS中,點擊新建項目——VisualC++——Win32項目(比如名稱為MyThirdParty)。
點擊確定后,在導航窗口中選擇Dll庫。
添加自己的類代碼
本例中,采用最簡單的類。一個頭文件與一個.cpp文件,cpp文件里面都是全局的方法。
(參考UE4wiki)
.h文件內容
#pragma once #define DLL_EXPORT__declspec(dllexport) //shortens__declspec(dllexport) to DLL_EXPORT Dll的導出需要借助__declspec,當然你也可以不使用宏,直接寫__declspec(dllexport) floatgetCircleArea(float radius); #ifdef __cplusplus //if C++ is used convert it toC to prevent C++'s name mangling of method names 采用C導出動態庫更為安全(有空詳細說說) extern "C" { #endif float DLL_EXPORT getCircleArea(floatradius); #ifdef __cplusplus } #endif
.cpp文件內容
#pragma once #include "string.h" #include"TestDll.h" //一個簡單的方法,根據半徑計算一個圓的面積 float getCircleArea(floatradius) { return float(3.1416f* (radius *radius)); }
網上一般說要采用Release版本編譯,其實這取決於你的版本,一般來說release版本是發行用的,對dll做了很多優化,要比Debug版本的小很多,Debug版本的也可以的(兩者都可以進行調試)。同時平台修改為x64平台(除非你是給32位機器使用的)。
2.建立你自己的工程
我這里新建一個工程名為MyCClassProjectA,然后新建一個類MyTestDll,讓這個類繼承與UBlueprintFunctionLibrary,這樣就可以直接在Event藍圖里面調用。類內容如下,
MyTestDll.h #include "Kismet/BlueprintFunctionLibrary.h" #include "MyTestDll.generated.h" UCLASS() class UMyTestDll :publicUBlueprintFunctionLibrary { GENERATED_BODY() public: //導入dll,BlueprintCallable入表示藍圖可以調用,這是UE的基礎應該了解 UFUNCTION(BlueprintCallable,Category="My DLL Library") static bool importDLL(FStringfolder,FString name); //獲取dll中的方法的指針 UFUNCTION(BlueprintCallable,Category="My DLL Library") static bool importMethodGetCircleArea(); //調用dll里面的方法 UFUNCTION(BlueprintCallable,Category="My DLL Library") static float getCircleAreaFromDll(float radius); //釋放dll UFUNCTION(BlueprintCallable,Category="My DLL Library") static void freeDLL(); };
MyTestDll.cpp
#include "MyCClassProjectA.h" #include "MyTestDll.h" typedef float(*_getCircleArea)(floatradius);//Declare a method to store the DLL method getCircleArea. //計算圓面積的函數指針 _getCircleArea m_getCircleAreaFromDll; //dll的句柄 void *v_dllHandle; #pragma regionLoad DLL // Method to import a DLL. bool UMyTestDll::importDLL(FStringfolder,FString name) { //這里是通過GamePluginsDir獲取當前工程的插件目錄,folder和named都作為參數傳遞,得到的filePath就是目標dll的具體位置了 FStringfilePath =*FPaths::GamePluginsDir()+folder+"/"+name; if (FPaths::FileExists(filePath)) { //通過FPlatformProcess::GetDllHandle獲取dll的句柄 v_dllHandle = FPlatformProcess::GetDllHandle(*filePath);//Retrieve the DLL. if (v_dllHandle !=NULL) { return true; } } return false; // Return an error. } #pragma endregionLoad DLL #pragma regionImport Methods // Imports the method getCircleArea from the DLL. bool UMyTestDll::importMethodGetCircleArea() { if (v_dllHandle !=NULL) { m_getCircleAreaFromDll =NULL; FStringprocName ="getCircleArea";//函數名稱. //通過句柄和名稱獲取到函數指針 m_getCircleAreaFromDll = (_getCircleArea)FPlatformProcess::GetDllExport(v_dllHandle,*procName); if(m_getCircleAreaFromDll !=NULL) { return true; } } return false; // Return an error. } // 從dll里面調用對應的函數. float UMyTestDll::getCircleAreaFromDll(float radius) { //如果獲取到了這個函數指針 if (m_getCircleAreaFromDll !=NULL) { //通過函數指針調用dll里面的方法,可以在這里斷點調試 float out=float(m_getCircleAreaFromDll(radius));//Call the DLL method with arguments corresponding to the exact signature andreturn type of the method. retur nout; } return -32202.0F; //Return an error. }
這一步把dll的加載分成了三步,其實使用的時候需要按照順序來,先調用importdll導入你需要的dll庫,然后調用importMethodGetCircleArea獲取到你的函數指針,最后執行getCircleAreaFromDll就可以執行dll庫里面的函數了。
3.dll的拷貝
創建完下面的類后,需要把你的第三方庫的dll拷貝到工程這邊,這里我是在MyCClassProject里面新建一個Plugins文件夾,然后里面又新建MyTutorialDLLs文件夾,拷貝到這個文件夾里面。(前面的importDLL(FStringfolder,FStringname)函數,我們可以傳入文件夾MyTutorialDLLs以及文件名TestDll)如下圖所示
4.運行與調試
下面就可以生成工程運行測試了,development版本就可以。運行工程后,新建一個藍圖(繼承Actor就行),名為UseDllActor,把藍圖拖進場景,在EventGraph里面書寫如下。當然,這些方法,你也可以直接在代碼里面調用的。
在接下來的調試中,你可以在getCircleAreaFromDll方法 這一行floatout=float(m_getCircleAreaFromDll(radius));設置斷點。如果F11進入的時候系統提示找不到你dll的源cpp文件,就會彈出下面的對話框,找到你的工程源文件目錄就可以了。當然如果你要調試,上面拷貝dll的同時需要把TestDll.pdb文件也拷過來。
另外,通過動態方式調用dll類的函數是比較麻煩的,大家可以先在網上了解一下,之后有研究的話可能會更新這篇文檔。
五.第三方庫PhysX的 dll 調用淺析
大家可能還是有一點疑惑,引擎里面有很多第三方庫dll的調用,難道使用的就是這種方法么?為什么有一些第三方庫的build文件里面會有像PublicDelayLoadDLLs這樣加載dll的方法,有什么作用?答案是方法大同小異,略有區別,關於PublicDelayLoadDLLs后面再說。下面簡單給大家分析一下,UE4中的物理模塊——PhysX第三方庫的dll的調用。
首先,我們定位到PhysX模塊dll的位置,F:\UnrealEngine4.14\Engine\Binaries\
ThirdParty\PhysX\Win64\VS2015。(不同平台以及VS版本有不同目錄)。我首先就想到,如果把這里的Dll移到其他目錄或者刪掉會怎么樣呢?
(F:\UnrealEngine4.14\Engine\Source\ThirdParty\PhysX\PhysX_3.4\Source是源文件目錄)
果然,提示我找不到PhysX3CommonPROFILE_x64.dll。隨后,拋出了個異常,我就打開了調用堆棧。(終於意識到異常的好處了!) 定位到了PhysXLevel.cpp的voidInitGamePhys()函數。異常在268行的位置拋出。
果斷進去看看,定位到了第三方庫文件PXFoundation.h。
PX_C_EXPORT PX_FOUNDATION_API physx::PxFoundation*PX_CALL_CONV
PxCreateFoundation(physx::PxU32version,physx::PxAllocatorCallback&allocator,physx::PxErrorCallback&errorCallback);
看着有點眼熟了,你可能說哪里眼熟!?明明看不懂好么。不過你可以仔細看一下這兩個宏PX_C_EXPORT 、PX_FOUNDATION_API,跟進去看定義會發現第一個宏就是exturn "C",第二個宏就是__declspec(dllexport)。這不就是我們第一步做的么?
不過,我們還是有個問題。dll的路徑他到底是怎么獲取的?我們上面的辦法是通過importDll函數搜索到的,那他是不是也應該有個類似的函數呢?沒錯,就是262行 LoadPhysXMoudles();。再跟進去,熟悉的目錄映入眼簾。
同時,還看到了各種句柄比如PxFoundationHandle,可以看到他的定義就是這樣的(具體的使用我就不太清楚了,各位可以自行研究該模塊)
HMODULEPxFoundationHandle = 0;
DECLARE_HANDLE(HINSTANCE);
typedefHINSTANCEHMODULE; /* HMODULEs can be used inplace of HINSTANCEs */
最后,再說一下Build.cs文件中的PublicDelayLoadDLLs方法。因為網上的教程提到了這兩個方法,又沒給人解釋清,確實讓人很煩,而且我上面說了第三方的dll庫一般不需要修改build文件,這會讓一些朋友有點疑惑。
其實熟悉windows編程的朋友應該知道,windows中dll的加載有兩種方式,一種是在exe運行的時候就加載,而另一種則是在需要用到dll的時候再去加載(可以加快exe啟動的速度)。所以PublicDelayLoadDLLs其實就是專門針對第三方庫的dll的延遲加載,在這里執行PublicDelayLoadDLLs.Add();實際上就是把這些dll的名稱作為參數傳遞給鏈接器。
打開PhysX.build.cs文件,你會看到有好幾個dll都是延遲加載的。
如果想看到編譯工具中的這些變量,最簡單的方法就是在C#代碼里面執行Console.WriteLine
(FileName);
參考文章:
http://blog.csdn.net/lunweiwangxi3/article/details/48373033
https://segmentfault.com/a/1190000008210614
https://wiki.unrealengine.com/Linking_Dlls
https://wiki.unrealengine.com/Linking_Static_Libraries_Using_The_Build_System