Bingo, Computer Graphics & Game Developer

UE4初读_Misc

Unreal为每个平台的底层都封装了一致的高层代码,本文讲述的是Windows下Unreal的源代码细节表现

###1. 使用Mutex来保证当前程序是当前系统中唯一开启的

Unreal源代码

bool MakeNamedMutex( const TCHAR* CmdLine )
{
    bool bIsFirstInstance = false;

    TCHAR MutexName[MAX_SPRINTF] = TEXT( "" );

    FCString::Strcpy( MutexName, MAX_SPRINTF, TEXT( "UnrealEngine4" ) );

    GNamedMutex = CreateMutex( NULL, true, MutexName );

    if( GNamedMutex    && GetLastError() != ERROR_ALREADY_EXISTS && !FParse::Param( CmdLine, TEXT( "NEVERFIRST" ) ) )
    {
        // We're the first instance!
        bIsFirstInstance = true;
    }
    else
    {
        // Still need to release it in this case, because it gave us a valid copy
        ReleaseNamedMutex();
        // There is already another instance of the game running.
        bIsFirstInstance = false;
    }

    return( bIsFirstInstance );
}

其中使得程序不会启动两次的关键在于互斥锁CreateMutex()。我参考了这篇文章CreateMutex,其中这一段讲解清晰了原理

进程在启动时首先检查当前系统是否已经存在有此进程的实例,如果没有,进程将成功创建并设置标识实例已经存在的标记。此后再创建进程时将会通过该标记而知晓其实例已经存在,从而保证进程在系统中只能存在一个实例。

如有同名程序运行,则通过 GetLastError()得到错误代码 ERROR_ALREADY_EXIST。


###2. do{ some_code }while(false); 代码块实现模板switch

参考以下文章 Link_1, Link_2

Unreal源代码

#define checkCode( Code )        do { Code } while ( false );

大致含义就是代码块中一旦出现错误或者指定情况下可以直接跳过(break)后部代码,实现类似goto的逻辑代码。说的直白一些,我个人更倾向于认为这像是”模板Switch”的实现。

// 若任何类型的switch存在 就会像下面这样
switch(condition)
{
case condition1:
    break;
case condition2:
    break;
    // ...
}

因为switch只能满足标量类型,而搭配此,就无需在每步中额外提供一些标示符来表示执行到的位置。其优点就相当于switch(int)和一堆的if/else组合对比下的优势。就像下面这样,阅读感受很糟糕。

// if/else
// condition1
if ( data ==  & array[o1])
    operation = O1;
// condition2
else if ( data ==  & array[o2])
    operation = O2;
// conditionxxx
else if
    //...
// conditionn
else if ( data ==  & array[on])
    operation = ON;

Log( "operation:",operation);

相对比之下,checkCode()的优势很明显

// macro switch
checkCode(
    if(!condition1)
        break;
    if(!condition2)
        break;
    // ...
)

除了我个人理解的switch以外,它也有一定的错误代码跳转能力。以下某种情况下,some_codesome_further_code是有可能不被执行的,实现了类似goto的逻辑。

// template switch
do
{
    if (!condition1) break;
    some_code;
    if (!condition2) break;
    some_further_code;
    // …
} while(false);

###3. class后部用于方便导出到dll的”关键字”

Unreal源代码

class RHI_API FDynamicRHI
{
public:
    // ...
}

找了很久,在WindowsPlatfrom.h下找到类似定义

#define DLLEXPORT __declspec(dllexport)
#define DLLIMPORT __declspec(dllimport)

因为我只使用过一次dll导出函数使用,同时也没有过导出类的经验,因此遇到这个宏时一无所知。参考与以下文章CSDN, C++编写DLL的方法, 其中第二篇更为精确一点。

这个宏出现最核心的原因就在于,客户端和DLL本身共用同一份头文件

如若是客户端,那么需要在class后声明_declspec(dllimport)用以导入该类给自己,DLL则是class后声明_declspec(dllexport),问题就出在这里,要共用一份头文件,但class却有两个写法,岂不是无法起到共用的效果?

解决方案如下
.h

#ifdef  DLL_API
#else
#define DLL_API _declspec(dllimport)
#endif

.cpp

#define DLL_API     _declspec(dllexport)

#include "DLLTest.h"

此时客户端在导入这个类时,使用_declspec(dllimport),DLL使用时已定义DLL_API,因此依旧是class被认定为导出_declspec(dllexport)

下次打算自己亲手试一试Windows平台下的dll导出。这样印象更深一点。