详解UE4静态库与动态库的导入与使用

转自:https://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——包含搜索路径。添加库的目录位置。这样项目就可以搜索到你的库头文件并使用了。

6.在项目工程类里面#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方法,这一行float out=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

原文地址:https://www.cnblogs.com/sevenyuan/p/12028472.html

时间: 2024-10-12 01:21:15

详解UE4静态库与动态库的导入与使用的相关文章

Linux下Gcc生成和使用静态库和动态库详解

参考文章:http://blog.chinaunix.net/uid-23592843-id-223539.html 一.基本概念 1.1什么是库 在windows平台和linux平台下都大量存在着库. 本质上来说库是一种可执行代码的二进制形式,可以被操作系统载入内存执行. 由于windows和linux的平台不同(主要是编译器.汇编器和连接器的不同),因此二者库的二进制是不兼容的. 本文仅限于介绍linux下的库. 1.2库的种类 linux下的库有两种:静态库和共享库(动态库). 二者的不同

【转】Linux下gcc生成和使用静态库和动态库详解

一.基本概念 1.1 什么是库 在Windows平台和Linux平台下都大量存在着库. 本质上来说,库是一种可执行代码的二进制形式,可以被操作系统载入内存执行. 由于windows和linux的平台不同(主要是编译器.汇编器和连接器的不同),因此二者的库的二进制是不兼容的. 本文仅限于介绍linux下的库. 1.2 库的种类 linux下的库有两种:静态库和共享库(动态库). 二者的不同点在于代码被载入的时刻不同. 静态库的代码在编译过程中已经被载入可执行程序,因此体积较大. 共享库的代码是在可

(笔记)Linux下的静态库和动态库使用详解

库从本质上来说是一种可执行代码的二进制格式,可以被载入内存中执行.库分静态库和动态库两种. 一.静态库和动态库的区别 1. 静态函数库 这类库的名字一般是libxxx.a:利用静态函数库编译成的文件比较大,因为整个 函数库的所有数据都会被整合进目标代码中,他的优点就显而易见了,即编译后的执行程序不需要外部的函数库支持,因为所有使用的函数都已经被编译进去了.当 然这也会成为他的缺点,因为如果静态函数库改变了,那么你的程序必须重新编译. 2. 动态函数库 这类库的名字一般是libxxx.so;相对于

C++静态库与动态库详解

1 库的概念? 库是写好的现有的,成熟的,可以复用的代码.现实中每个程序都要依赖很多基础的底层库. 2 动态库与静态库的概念? 先回顾一下编译过程: 2.1 静态库 静态库在链接阶段,会将汇编生成的目标文件.o与引用到的库一起链接打包到可执行文件中,对应的链接方式称为静态链接.试想一下,静态库与汇编生成的目标文件(.o文件)一起链接为可执行文件,那么静态库必定跟.o文件格式相似.其实一个静态库可以 简单看成是一组目标文件(.o/.obj文件)的归档集合,即很多目标文件经过压缩打包后形成的一个文件

静态库和动态库详解

1.什么是库,为什么使用库? 库是共享程序代码的方式,一般分为静态库和动态库:库实现了iOS程序的模块化,将某些特定的功能模块化为库的格式方便分享和使用! 2.静态库和动态库有什么特点? 异同点: 静态库:链接时完整地拷贝至可执行文件中,被多次使用就有多份冗余拷贝. 动态库:链接时不复制,程序运行时由系统动态加载到内存,供程序调用,系统只加载一次,多个程序可以共用,节省内存. 共同点: 静态库和动态库都是闭源库,只能拿来满足某个功能的使用,不会暴露内部具体的代码信息,而从github上下载的第三

VS2010 编译 openssl 源代码(输出 libeay32 and ssleay32 静态库和动态库)

一.准备工作: 1.下载并安装VS 2010 开发环境. 地址:http://www.downza.cn/soft/185464.html 2.下载 ActivePerl 工具,是一个perl脚本解释器.(我们编译过程中需要执行perl脚本,所以必须安装此工具) 地址:http://downloads.activestate.com/ActivePerl/3.下载 OpenSSL 源代码,openssl 是一个安全套接字层密码库,主要包含密码算法.常用的密钥和证书封装管理功能及实现了SSL协议,

静态库和动态库的分析

本质上来说库是一种可执行代码的二进制形式. linux下的库有两种:静态库和共享库(动态库) 静态库在程序编译时会被连接到目标代码中:程序运行时将不再需要该静态库,因此体积较大. 优点:程序运行时将不再需要该静态库 缺点:可执行文件的体积较大. 相同的库可能会需要被多次加载. 静态库: libxxxxx.a 动态库:动态库在程序编译时并不会被连接到目标代码中, 优点: 在程序运行时动态库才会被载入内存,因此代码体积较小. 缺点: 因此在程序运行时还需要动态库存在. 静态库的制作:将功能函数编译成

iOS 中的静态库与动态库,区别、制作和使用

如果我们有些功能要给别人用,但是又不想公开代码实现,比如高德地图.第三方登录分享等等,这时候我们就要打包成库了.库分静态库和动态库两种: 静态库:以.a 和 .framework为文件后缀名.动态库:以.tbd(之前叫.dylib) 和 .framework 为文件后缀名. 静态库与动态库的区别 静态库:链接时会被完整的复制到可执行文件中,被多次使用就有多份拷贝.动态库:链接时不复制,程序运行时由系统动态加载到内存,系统只加载一次,多个程序共用(如系统的UIKit.framework等),节省内

linux上静态库和动态库的编译和使用(附外部符号错误浅谈)

主要参考博客gcc创建和使用静态库和动态库 对于熟悉windows的同学,linux上的静态库.a相当于win的.lib,动态库.so相当于win的.dll. 首先简要地解释下这两种函数库的区别,参考<Linux程序设计> 1. 静态库也被称为归档文件(archive,因此创建命令是ar),编译器和链接器负责将程序代码和静态库结合在一起组成单独的可执行文件: 但是缺点是许多应用程序同时运行并使用来自同一个静态库的函数时,内存中就会有一个函数的多份副本,而且程序文件自身也有多份同样的副本,这将消