Using Service<T> to access Dalamud's internal services
While Dependency Injection is the recommended way for plugins to access services, Dalamud internally uses the Service Locator pattern through the Service<T> class. Understanding this pattern is useful for advanced plugin development and understanding Dalamud’s internals.
The Service<T> class is internal to Dalamud and not accessible to plugins. This page is for educational purposes to help you understand how Dalamud works internally.
The Service Locator pattern is an alternative to dependency injection where components request services from a central registry:
// Instead of injection:public MyClass(IClientState clientState) { }// Service locator:public MyClass(){ var clientState = Service<IClientState>.Get();}
Dalamud uses Service<T> internally but provides dependency injection to plugins for better testability and clarity.
Dalamud initializes services in a specific order to handle dependencies:
1
Provided Services
Manually created services are registered first:
// From Dalamud.cs:72ServiceManager.InitializeProvidedServices( this, fs, configuration, scanner, localization);
These include:
ServiceContainer
Dalamud itself
ReliableFileStorage
DalamudConfiguration
TargetSigScanner
Localization
2
Early-Loaded Services
Services marked with [EarlyLoadedService] are created asynchronously:
// From ServiceManager.cs:191public static async Task InitializeEarlyLoadableServices(){ // Discovers and loads early services}
3
Blocking Services
Services marked with [BlockingEarlyLoadedService] block game startup:
// From PluginManager.cs:45[ServiceManager.BlockingEarlyLoadedService( "Accommodation of plugins that blocks the game startup.")]internal class PluginManager : IInternalDisposableService
4
Dependency Resolution
Services with dependencies are initialized after their dependencies:
Plugins don’t have access to Service<T> for several important reasons:
Encapsulation
Service<T> is marked as internal, hiding implementation details:
// Plugins cannot access thisinternal static class ServiceManager
This allows Dalamud to change its internal service implementation without breaking plugins.
Testability
Dependency injection makes plugin code more testable:
// Easy to test with mockspublic MyPlugin(IClientState clientState){ this.clientState = clientState;}// Hard to test - requires service to be registeredpublic MyPlugin(){ this.clientState = Service<ClientState>.Get();}
Explicit Dependencies
Constructor parameters make dependencies clear:
// Dependencies are obviouspublic MyPlugin(IClientState clientState, IChatGui chat, ICommandManager commands)// Dependencies are hidden in implementationpublic MyPlugin(){ // What services does this use? Have to read the code!}
Lifetime Management
DI container ensures services are available when plugins construct:
// Container guarantees clientState is readypublic MyPlugin(IClientState clientState)// Might not be ready yet!public MyPlugin(){ var clientState = Service<ClientState>.Get(); // Could block or fail}
Services can be looked up by concrete type or interface:
// From ServiceContainer.cs:195private async Task<object?> GetSingletonService(Type serviceType, bool tryGetInterface = true){ if (tryGetInterface && this.interfaceToTypeMap.TryGetValue(serviceType, out var implementingType)) serviceType = implementingType; if (!this.instances.TryGetValue(serviceType, out var service)) return null; var instance = await service.InstanceTask; return instance.Target;}