UE 插件与模块加载

概述


呜哇哈哈哈哈哈哈哈哈哈哈哈哈哈哈
整理知识体系
源码版本5.3

插件与模块

基本使用

创建插件
编辑->插件->添加,创建一个Blank插件
看 .uplugin

{
  "FileVersion": 3,
  "Version": 1,
  "VersionName": "1.0",
  "FriendlyName": "TestBlank",
  "Description": "",
  "Category": "Other",
  "CreatedBy": "cwl",
  "CreatedByURL": "",
  "DocsURL": "",
  "MarketplaceURL": "",
  "SupportURL": "",
  "CanContainContent": true,
  "IsBetaVersion": false,
  "IsExperimentalVersion": false,
  "Installed": false,
  "Modules": [
    {
      "Name": "TestBlank",
      "Type": "Runtime",
      "LoadingPhase": "Default"
    }
  ]
}

这个 Type 定义在 ModuleDescriptor.h

/**
 * Environment that can load a module.
 */
namespace EHostType
{
    enum Type
    {
       // Loads on all targets, except programs.
       Runtime,

       // Loads on all targets, except programs and the editor running commandlets.
       RuntimeNoCommandlet,

       // Loads on all targets, including supported programs.
       RuntimeAndProgram,

       // Loads only in cooked games.
       CookedOnly,

       // Only loads in uncooked games.
       UncookedOnly,

       // Deprecated due to ambiguities. Only loads in editor and program targets, but loads in any editor mode (eg. -game, -server).
       // Use UncookedOnly for the same behavior (eg. for editor blueprint nodes needed in uncooked games), or DeveloperTool for modules
       // that can also be loaded in cooked games but should not be shipped (eg. debugging utilities).
       Developer,

       // Loads on any targets where bBuildDeveloperTools is enabled.
       DeveloperTool,

       // Loads only when the editor is starting up.
       Editor,

       // Loads only when the editor is starting up, but not in commandlet mode.
       EditorNoCommandlet,

       // Loads only on editor and program targets
       EditorAndProgram,

       // Only loads on program targets.
       Program,

       // Loads on all targets except dedicated clients.
       ServerOnly,

       // Loads on all targets except dedicated servers.
       ClientOnly,

       // Loads in editor and client but not in commandlets.
       ClientOnlyNoCommandlet,

       //~ NOTE: If you add a new value, make sure to update the ToString() method below!
       Max
    };
  }

这个 LoadingPhase 是加载阶段。同一个文件

/**
 * Phase at which this module should be loaded during startup.
 */
namespace ELoadingPhase
{
    enum Type
    {
        /** As soon as possible - in other words, uplugin files are loadable from a pak file (as well as right after PlatformFile is set up in case pak files aren't used) Used for plugins needed to read files (compression formats, etc) */
        EarliestPossible,

        /** Loaded before the engine is fully initialized, immediately after the config system has been initialized.  Necessary only for very low-level hooks */
        PostConfigInit,

        /** The first screen to be rendered after system splash screen */
        PostSplashScreen,

        /** Loaded before coreUObject for setting up manual loading screens, used for our chunk patching system */
        PreEarlyLoadingScreen,

        /** Loaded before the engine is fully initialized for modules that need to hook into the loading screen before it triggers */
        PreLoadingScreen,

        /** Right before the default phase */
        PreDefault,

        /** Loaded at the default loading point during startup (during engine init, after game modules are loaded.) */
        Default,

        /** Right after the default phase */
        PostDefault,

        /** After the engine has been initialized */
        PostEngineInit,

        /** Do not automatically load this module */
        None,

        // NOTE: If you add a new value, make sure to update the ToString() method below!
        Max
    };

模块在这个函数
void UEditorEngine::Init(IEngineLoop* InEngineLoop)
也就是 UEditorEngine 的 Init 阶段加载。

    FScopedSlowTask ModuleSlowTask((float)UE_ARRAY_COUNT(ModuleNames));
    for (const TCHAR* ModuleName : ModuleNames)
    {
        ModuleSlowTask.EnterProgressFrame(1);
        FModuleManager::Get().LoadModule(ModuleName);
    }

上面有一个超长数组

看插件的 build.cs 可以配置一些模块依赖和搜索路径

如何包含这个插件,uproject 写

    "Plugins": [
        {
            "Name": "TestBlank",
            "Enabled": true
        }
    ]

build.cs 写

        PrivateDependencyModuleNames.AddRange(
            new string[]
            {
                "CoreUObject",
                "Engine",
                "Slate",
                "SlateCore",
                "TestBlank"
                // ... add private dependencies that you statically link with here ...  
            }
            );

项目目录下的多模块


时代进步了。。。

插件目录下多模块

以前是创建一个空插件T, 把Source下的内容,粘到 TestBlank 的 Source下。然后删除插件T


build.cs uplugin看着改。

现在是rider在Source右键创建module。效果一样
在uplugin会帮我们写上

    "Modules": [
        {
            "Name": "TestBlank",
            "Type": "Runtime",
            "LoadingPhase": "Default"
        },
        {
            "Name": "T",
            "Type": "Runtime",
            "LoadingPhase": "Default"
        },
        {
            "Name": "TR",
            "Type": "Runtime",
            "LoadingPhase": "Default"
        }
    ]

不编译模块

build.cs里面

bUsePrecompiled = true;

达到隐藏头文件的目的。

IMPLEMENT_MODULE(FTRModule, TR)

IS_MONOLITHIC 是一个预处理器宏,用于确定当前的编译配置是否为单体(monolithic)模式。 在单体模式下,所有的代码(包括引擎代码和游戏代码)都被编译到一个单独的可执行文件中。这种模式通常用于最终的产品发布,因为它可以提供更好的性能和更小的二进制文件大小。 相反,如果 IS_MONOLITHIC 为 false,则表示编译配置为模块化(modular)模式。在模块化模式下,代码被编译成多个动态链接库(DLLs),每个模块一个。这种模式通常用于开发过程中,因为它支持热重载(hot-reloading),即在不重启应用程序的情况下重新编译和加载代码。

所以直接看这个

#if IS_MONOLITHIC

    // If we're linking monolithically we assume all modules are linked in with the main binary.
    #define IMPLEMENT_MODULE( ModuleImplClass, ModuleName ) \
        /** Global registrant object for this module when linked statically */ \
        static FStaticallyLinkedModuleRegistrant< ModuleImplClass > ModuleRegistrant##ModuleName( TEXT(#ModuleName) ); \
        /* Forced reference to this function is added by the linker to check that each module uses IMPLEMENT_MODULE */ \
        extern "C" void IMPLEMENT_MODULE_##ModuleName() { } \
        PER_MODULE_BOILERPLATE_ANYLINK(ModuleImplClass, ModuleName)

#else
static FStaticallyLinkedModuleRegistrant< ModuleImplClass > ModuleRegistrant##ModuleName( TEXT(#ModuleName) ); \

声明了一个全局static对象,是 FStaticallyLinkedModuleRegistrant 这个类,名字叫xx

他的构造函数,会这样子注册

/**
 * Utility class for registering modules that are statically linked.
 */
template< class ModuleClass >
class FStaticallyLinkedModuleRegistrant
{
public:

    /**
     * Explicit constructor that registers a statically linked module
     */
    FStaticallyLinkedModuleRegistrant( FLazyName InModuleName )
    {
        // Create a delegate to our InitializeModule method
        FModuleManager::FInitializeStaticallyLinkedModule InitializerDelegate = FModuleManager::FInitializeStaticallyLinkedModule::CreateRaw(
                this, &FStaticallyLinkedModuleRegistrant<ModuleClass>::InitializeModule );

        // Register this module
        FModuleManager::Get().RegisterStaticallyLinkedModule(
            InModuleName,           // Module name
            InitializerDelegate );  // Initializer delegate
    }

    /**
     * Creates and initializes this statically linked module.
     *
     * The module manager calls this function through the delegate that was created
     * in the @see FStaticallyLinkedModuleRegistrant constructor.
     *
     * @return A pointer to a new instance of the module.
     */
    IModuleInterface* InitializeModule( )
    {
        return new ModuleClass();
    }
};

绑定了一个代理,
注册 RegisterStaticallyLinkedModule,存到ModuleManager的一个 tarray 里面

    /**
     * Registers an initializer for a module that is statically linked.
     *
     * @param InModuleName The name of this module.
     * @param InInitializerDelegate The delegate that will be called to initialize an instance of this module.
     */
    void RegisterStaticallyLinkedModule( const FLazyName InModuleName, const FInitializeStaticallyLinkedModule& InInitializerDelegate )
    {
        PendingStaticallyLinkedModuleInitializers.Emplace( InModuleName, InInitializerDelegate );
    }

我们找 PendingStaticallyLinkedModuleInitializers 哪里使用的,可以找到这个调用链。
IModuleInterface FModuleManager::LoadModule(const FName InModuleName, ELoadModuleFlags InLoadModuleFlags)
IModuleInterface
FModuleManager::LoadModuleWithFailureReason(const FName InModuleName, EModuleLoadResult& OutFailureReason, ELoadModuleFlags InLoadModuleFlags)
void FModuleManager::ProcessPendingStaticallyLinkedModuleInitializers() const

源头 LoadModule 的调用就比较多了,我们可以得出结论。这个接口可以根据模块名字来加载模块。

IModuleInterface* FModuleManager::LoadModule(const FName InModuleName, ELoadModuleFlags InLoadModuleFlags)
{
    LLM_SCOPE_BYNAME(TEXT("Modules"));
    // We allow an already loaded module to be returned in other threads to simplify
    // parallel processing scenarios but they must have been loaded from the main thread beforehand.
    if(!IsInGameThread())
    {
        return GetModule(InModuleName);
    }

    EModuleLoadResult FailureReason;
    IModuleInterface* Result = LoadModuleWithFailureReason(InModuleName, FailureReason, InLoadModuleFlags);

    // This should return a valid pointer only if and only if the module is loaded
    checkSlow((Result != nullptr) == IsModuleLoaded(InModuleName));

    return Result;
}

非游戏线程走GetModule, 这里面大概是上锁,去一个对象Map里面找,主线程已经创建好的模块对象,没找到就是没有了。

    /** Map of all modules.  Maps the case-insensitive module name to information about that module, loaded or not. */
    FModuleMap Modules;

游戏线程就走 LoadModuleWithFailureReason,看下这个函数

    // Do fast check for existing module, this is the most common case
    ModuleInfoPtr FoundModulePtr = FindModule(InModuleName);

    if (FoundModulePtr.IsValid())
    {
        LoadedModule = FoundModulePtr->Module.Get();

        if (LoadedModule)
        {
            // note: this function does not check (bIsReady || IsInGameThread()) the way GetModule() does
            //   that looks like a bug if called from off-game-thread

            return LoadedModule;
        }
    }

开头是找,已经创建过了就直接返回

没找到就 AddModule,再 FindModule

    if (!FoundModulePtr.IsValid())
    {
        // Update our set of known modules, in case we don't already know about this module
        AddModule(InModuleName);

        // Ptr will always be valid at this point
        FoundModulePtr = FindModule(InModuleName);

        // NOTE: Module is now findable , calls to Find or Load Module will find this module pointer
        //  but it's not initialized yet (bIsReady is false so Get from other threads will fail)
        // this AddModule must be done before the module is initialized
        // because StartupModule may call functions that Find/Load on this module
        // and they should get back this pointer, even though it is not finished initializing yet
    }

这里的AddModule 只是把模块信息
ModuleInfoRef ModuleInfo(new FModuleInfo());
加入到模块的一个Map里面

void FModuleManager::AddModule(const FName InModuleName)
{
    // Do we already know about this module?  If not, we'll create information for this module now.
    if (!((ensureMsgf(InModuleName != NAME_None, TEXT("FModuleManager::AddModule() was called with an invalid module name (empty string or 'None'.)  This is not allowed.")) &&
        !Modules.Contains(InModuleName))))
    {
        return;
    }

    ModuleInfoRef ModuleInfo(new FModuleInfo());

#if !IS_MONOLITHIC
    RefreshModuleFilenameFromManifestImpl(InModuleName, ModuleInfo.Get());
#endif  // !IS_MONOLITHIC

    // Make sure module info is added to known modules and proper delegates are fired on exit.
    FModuleManager::Get().AddModuleToModulesList(InModuleName, ModuleInfo);
}

这个模块信息代码很短
里面的 TUniquePtr Module; 就是我们的模块实力。我们创建的模块刚刚好也是继承了 IModuleInterface 的

    const FInitializeStaticallyLinkedModule* ModuleInitializerPtr = StaticallyLinkedModuleInitializers.Find(InModuleName);
    if (ModuleInitializerPtr != nullptr)
    {
        const FInitializeStaticallyLinkedModule& ModuleInitializer(*ModuleInitializerPtr);

        // Initialize the module!
        ModuleInfo->Module = TUniquePtr<IModuleInterface>(ModuleInitializer.Execute());

        if (ModuleInfo->Module.IsValid())
        {
            // 。。。
                ModuleInfo->Module->StartupModule();
            // 。。。
        }

    }

创建module并startup
最后的返回值是 这个 IModuleInterface*

FInitializeStaticallyLinkedModule* 这个结构我们刚刚看到过

template< class ModuleClass >
class FStaticallyLinkedModuleRegistrant

的构造函数里面

最后是个朴实无华的new

    IModuleInterface* InitializeModule( )
    {
        return new ModuleClass();
    }

模块初始化

位于 bool FEngineLoop::AppInit( ) 中

    // Load "asap" plugin modules
    IPluginManager&  PluginManager = IPluginManager::Get();
    IProjectManager& ProjectManager = IProjectManager::Get();
    if (!ProjectManager.LoadModulesForProject(ELoadingPhase::EarliestPossible) || !PluginManager.LoadModulesForEnabledPlugins(ELoadingPhase::EarliestPossible))
    {
        return false;
    }

LoadModulesForProject

bool FProjectManager::LoadModulesForProject( const ELoadingPhase::Type LoadingPhase )
{
    DECLARE_SCOPE_CYCLE_COUNTER(TEXT("Loading Game Modules"), STAT_GameModule, STATGROUP_LoadTime);

    bool bSuccess = true;

    if ( CurrentProject.IsValid() )
    {
        TMap<FName, EModuleLoadResult> ModuleLoadFailures;
        FModuleDescriptor::LoadModulesForPhase(LoadingPhase, CurrentProject->Modules, ModuleLoadFailures);

        if ( ModuleLoadFailures.Num() > 0 )
        {
            FText FailureMessage;
            for ( auto FailureIt = ModuleLoadFailures.CreateConstIterator(); FailureIt; ++FailureIt )
            {
                const EModuleLoadResult FailureReason = FailureIt.Value();

                if( FailureReason != EModuleLoadResult::Success )
                {
                    // 。。
                    break;
                }
            }

            FMessageDialog::Open(EAppMsgType::Ok, FailureMessage);
            bSuccess = false;
        }
    }

    OnLoadingPhaseCompleteEvent.Broadcast(LoadingPhase, bSuccess);
    return bSuccess;
} 

FModuleDescriptor::LoadModulesForPhase(LoadingPhase, CurrentProject->Modules, ModuleLoadFailures);

void FModuleDescriptor::LoadModulesForPhase(ELoadingPhase::Type LoadingPhase, const TArray<FModuleDescriptor>& Modules, TMap<FName, EModuleLoadResult>& ModuleLoadErrors)
{
    FScopedSlowTask SlowTask((float)Modules.Num());
    for (int Idx = 0; Idx < Modules.Num(); Idx++)
    {
        SlowTask.EnterProgressFrame(1);
        const FModuleDescriptor& Descriptor = Modules[Idx];

        // Don't need to do anything if this module is already loaded
        if (!FModuleManager::Get().IsModuleLoaded(Descriptor.Name))
        {
            if (LoadingPhase == Descriptor.LoadingPhase && Descriptor.IsLoadedInCurrentConfiguration())
            {
                // @todo plugin: DLL search problems.  Plugins that statically depend on other modules within this plugin may not be found?  Need to test this.

                // NOTE: Loading this module may cause other modules to become loaded, both in the engine or game, or other modules 
                //       that are part of this project or plugin.  That's totally fine.
                EModuleLoadResult FailureReason;
                IModuleInterface* ModuleInterface = FModuleManager::Get().LoadModuleWithFailureReason(Descriptor.Name, FailureReason);
                if (ModuleInterface == nullptr)
                {
                    // The module failed to load. Note this in the ModuleLoadErrors list.
                    ModuleLoadErrors.Add(Descriptor.Name, FailureReason);
                }
            }
        }
    }
}

按阶段加载模块,便利模块,看是不是当前阶段。然后 LoadModuleWithFailureReason,这个函数我们上面解释过

LoadModulesForEnabledPlugins

bool FPluginManager::LoadModulesForEnabledPlugins( const ELoadingPhase::Type LoadingPhase )
{
    // Figure out which plugins are enabled
    bool bSuccess = true;
    if (!ConfigureEnabledPlugins())
    {
        bSuccess = false;
    }
    else
    {
        FScopedSlowTask SlowTask((float)AllPlugins.Num());
        LLM_SCOPE_BYNAME(TEXT("Modules"));

        // Load plugins!
        for (const FDiscoveredPluginMap::ElementType& PluginPair : AllPlugins)
        {
            const TSharedRef<FPlugin>& Plugin = DiscoveredPluginMapUtils::ResolvePluginFromMapVal(PluginPair.Value);

            SlowTask.EnterProgressFrame(1);

            if (Plugin->bEnabled && !Plugin->Descriptor.bExplicitlyLoaded)
            {
                if (!TryLoadModulesForPlugin(Plugin.Get(), LoadingPhase))
                {
                    bSuccess = false;
                    break;
                }
            }
        }

        // This is a workaround for crashes when delaying the loading of binaries when using pak files/iostore. Should be removed
        if (bPreloadedBinaries && LoadingPhase == ELoadingPhase::PostConfigInit)
        {
            UE_SCOPED_ENGINE_ACTIVITY("Preloading all plugin binaries");
            for (const FDiscoveredPluginMap::ElementType& PluginPair : AllPlugins)
            {
                for (auto& ModuleName : DiscoveredPluginMapUtils::ResolvePluginFromMapVal(PluginPair.Value)->Descriptor.Modules)
                {
                    if (ModuleName.IsCompiledInCurrentConfiguration())
                    {
                        FModuleManager::Get().LoadModuleBinaryOnly(ModuleName.Name);
                    }
                }
            }
        }
    }
    // Some phases such as ELoadingPhase::PreEarlyLoadingScreen are potentially called multiple times,
    // but we do not return to an earlier phase after calling LoadModulesForEnabledPlugins on a later phase
    UE_CLOG(LastCompletedLoadingPhase != ELoadingPhase::None && LastCompletedLoadingPhase > LoadingPhase,
        LogPluginManager, Error, TEXT("LoadModulesForEnabledPlugins called on phase %d after already being called on later phase %d."),
        static_cast<int32>(LoadingPhase), static_cast<int32>(LastCompletedLoadingPhase));

    // We send the broadcast event each time, even if this function is called multiple times with the same phase
    LastCompletedLoadingPhase = LoadingPhase;
    LoadingPhaseCompleteEvent.Broadcast(LoadingPhase, bSuccess);
    return bSuccess;
}

TryLoadModulesForPlugin

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇