{* UltraStar Deluxe - Karaoke Game
*
* UltraStar Deluxe is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*
* $URL$
* $Id$
*}
unit UPluginLoader;
{*********************
UPluginLoader
Unit contains two classes
TPluginLoader: Class searching for and loading the plugins
TtehPlugins: Class representing the plugins in modules chain
*********************}
interface
{$IFDEF FPC}
{$MODE Delphi}
{$ENDIF}
{$I switches.inc}
uses
UPluginDefs,
UCoreModule;
type
TPluginListItem = record
Info: TUS_PluginInfo;
State: Byte; // State of this plugin: 0 - undefined; 1 - loaded; 2 - inited / running; 4 - unloaded; 254 - loading aborted by plugin; 255 - unloaded because of error
Path: String; // path to this plugin
NeedsDeInit: Boolean; // if this is inited correctly this should be true
hLib: THandle; // handle of loaded libary
Procs: record // procs offered by plugin. Don't call this directly use wrappers of TPluginLoader
Load: Func_Load;
Init: Func_Init;
DeInit: Proc_DeInit;
end;
end;
{*********************
TPluginLoader
Class searches for plugins and manages loading and unloading
*********************}
PPluginLoader = ^TPluginLoader;
TPluginLoader = class (TCoreModule)
private
LoadingProcessFinished: Boolean;
sUnloadPlugin: THandle;
sLoadPlugin: THandle;
sGetPluginInfo: THandle;
sGetPluginState: THandle;
procedure FreePlugin(Index: Cardinal);
public
PluginInterface: TUS_PluginInterface;
Plugins: array of TPluginListItem;
// TCoreModule methods to inherit
constructor Create; override;
procedure Info(const pInfo: PModuleInfo); override;
function Load: Boolean; override;
function Init: Boolean; override;
procedure DeInit; override;
Destructor Destroy; override;
// New methods
procedure BrowseDir(Path: String); // browses the path at _Path_ for plugins
function PluginExists(Name: String): integer; // if plugin exists: Index of plugin, else -1
procedure AddPlugin(Filename: String); // adds plugin to the array
function CallLoad(Index: Cardinal): integer;
function CallInit(Index: Cardinal): integer;
procedure CallDeInit(Index: Cardinal);
//Services offered
function LoadPlugin(wParam: TwParam; lParam: TlParam): integer; //wParam PChar(PluginName/PluginPath) | lParam (if wParam = nil) ID of the Plugin
function UnloadPlugin(wParam: TwParam; lParam: TlParam): integer; //wParam PChar(PluginName/PluginPath) | lParam (if wParam = nil) ID of the Plugin
function GetPluginInfo(wParam: TwParam; lParam: TlParam): integer; //If wParam = -1 then (If lParam = nil then get length of Moduleinfo Array. If lparam <> nil then write array of TUS_PluginInfo to address at lparam) else (Get PluginInfo of Plugin with Index(wParam) to Address at lParam)
function GetPluginState(wParam: TwParam; lParam: TlParam): integer; //If wParam = -1 then (If lParam = nil then get length of Moduleinfo Array. If lparam <> nil then write array of TUS_PluginInfo to address at lparam) else (Return PluginInfo of Plugin with Index(wParam))
end;
{*********************
TtehPlugins
Class represents the plugins in module chain.
It calls the plugins procs and funcs
*********************}
TtehPlugins = class (TCoreModule)
private
PluginLoader: PPluginLoader;
public
// TCoreModule methods to inherit
constructor Create; override;
procedure Info(const pInfo: PModuleInfo); override;
function Load: Boolean; override;
function Init: Boolean; override;
procedure DeInit; override;
end;
const
{$IF Defined(MSWINDOWS)}
PluginFileExtension = '.dll';
{$ELSEIF Defined(DARWIN)}
PluginFileExtension = '.dylib';
{$ELSEIF Defined(UNIX)}
PluginFileExtension = '.so';
{$IFEND}
implementation
uses
UCore,
UPluginInterface,
{$IFDEF MSWINDOWS}
windows,
{$ELSE}
dynlibs,
{$ENDIF}
UMain,
SysUtils;
{*********************
TPluginLoader
Implementation
*********************}
//-------------
// function that gives some infos about the module to the core
//-------------
procedure TPluginLoader.Info(const pInfo: PModuleInfo);
begin
pInfo^.Name := 'TPluginLoader';
pInfo^.Version := MakeVersion(1,0,0,chr(0));
pInfo^.Description := 'Searches for plugins, loads and unloads them';
end;
//-------------
// Just the constructor
//-------------
constructor TPluginLoader.Create;
begin
inherited;
// Init PluginInterface
// Using methods from UPluginInterface
PluginInterface.CreateHookableEvent := CreateHookableEvent;
PluginInterface.DestroyHookableEvent := DestroyHookableEvent;
PluginInterface.NotivyEventHooks := NotivyEventHooks;
PluginInterface.HookEvent := HookEvent;
PluginInterface.UnHookEvent := UnHookEvent;
PluginInterface.EventExists := EventExists;
PluginInterface.CreateService := @CreateService;
PluginInterface.DestroyService := DestroyService;
PluginInterface.CallService := CallService;
PluginInterface.ServiceExists := ServiceExists;
// UnSet private var
LoadingProcessFinished := False;
end;
//-------------
// Is called on loading.
// In this method only events and services should be created
// to offer them to other modules or plugins during the init process
// if false is returned this will cause a forced exit
//-------------
function TPluginLoader.Load: Boolean;
begin
Result := True;
try
// Start searching for plugins
BrowseDir(PluginPath);
except
Result := False;
Core.ReportError(integer(PChar('Error browsing and loading.')), PChar('TPluginLoader'));
end;
end;
//-------------
// Is called on init process
// In this method you can hook some events and create + init
// your classes, variables etc.
// If false is returned this will cause a forced exit
//-------------
function TPluginLoader.Init: Boolean;
begin
// Just set private var to true.
LoadingProcessFinished := True;
Result := True;
end;
//-------------
// Is called if this module has been inited and there is a exit.
// Deinit is in backwards initing order
//-------------
procedure TPluginLoader.DeInit;
var
I: integer;
begin
// Force deinit
// if some plugins aren't deinited for some reason o0
for I := 0 to High(Plugins) do
begin
if (Plugins[I].State < 4) then
FreePlugin(I);
end;
// Nothing to do here. Core will remove the hooks
end;
//-------------
// Is called if this module will be unloaded and has been created
// Should be used to free memory
//-------------
Destructor TPluginLoader.Destroy;
begin
// Just save some memory if it wasn't done now..
SetLength(Plugins, 0);
inherited;
end;
//--------------
// Browses the path at _Path_ for plugins
//--------------
procedure TPluginLoader.BrowseDir(Path: String);
var
SR: TSearchRec;
begin
// Search for other dirs to browse
if FindFirst(Path + '*', faDirectory, SR) = 0 then begin
repeat
if (SR.Name <> '.') and (SR.Name <> '..') then
BrowseDir(Path + Sr.Name + PathDelim);
until FindNext(SR) <> 0;
end;
FindClose(SR);
// Search for plugins at path
if FindFirst(Path + '*' + PluginFileExtension, 0, SR) = 0 then
begin
repeat
AddPlugin(Path + SR.Name);
until FindNext(SR) <> 0;
end;
FindClose(SR);
end;
//--------------
// If plugin exists: Index of plugin, else -1
//--------------
function TPluginLoader.PluginExists(Name: String): integer;
var
I: integer;
begin
Result := -1;
if (Length(Name) <= 32 { =>Length(TUS_PluginInfo.Name)}) then
begin
for I := 0 to High(Plugins) do
if (Plugins[I].Info.Name = Name) then
begin //Found the Plugin
Result := I;
Break;
end;
end;
end;
//--------------
// Adds plugin to the array
//--------------
procedure TPluginLoader.AddPlugin(Filename: String);
var
hLib: THandle;
PInfo: Proc_PluginInfo;
Info: TUS_PluginInfo;
PluginID: integer;
begin
if (FileExists(Filename)) then
begin //Load Libary
hLib := LoadLibrary(PChar(Filename));
if (hLib <> 0) then
begin // Try to get address of the info proc
PInfo := GetProcAddress (hLib, PChar('USPlugin_Info'));
if (@PInfo <> nil) then
begin
Info.cbSize := SizeOf(TUS_PluginInfo);
try // Call info proc
PInfo(@Info);
except
Info.Name := '';
Core.ReportError(integer(PChar('Error getting plugin info: ' + Filename)), PChar('TPluginLoader'));
end;
// Is name set ?
if (Trim(Info.Name) <> '') then
begin
PluginID := PluginExists(Info.Name);
if (PluginID > 0) and (Plugins[PluginID].State >=4) then
PluginID := -1;
if (PluginID = -1) then
begin
// Add new item to array
PluginID := Length(Plugins);
SetLength(Plugins, PluginID + 1);
// Fill with info:
Plugins[PluginID].Info := Info;
Plugins[PluginID].State := 0;
Plugins[PluginID].Path := Filename;
Plugins[PluginID].NeedsDeInit := False;
Plugins[PluginID].hLib := hLib;
// Try to get procs
Plugins[PluginID].Procs.Load := GetProcAddress (hLib, PChar('USPlugin_Load'));
Plugins[PluginID].Procs.Init := GetProcAddress (hLib, PChar('USPlugin_Init'));
Plugins[PluginID].Procs.DeInit := GetProcAddress (hLib, PChar('USPlugin_DeInit'));
if (@Plugins[PluginID].Procs.Load = nil) OR (@Plugins[PluginID].Procs.Init = nil) OR (@Plugins[PluginID].Procs.DeInit = nil) then
begin
Plugins[PluginID].State := 255;
FreeLibrary(hLib);
Core.ReportError(integer(PChar('Can''t get plugin procs from libary: "' + Info.Name + '" ' + Filename)), PChar('TPluginLoader'));
end;
// Emulate loading process if this plugin is loaded too late
if (LoadingProcessFinished) then
begin
CallLoad(PluginID);
CallInit(PluginID);
end;
end
else if (LoadingProcessFinished = False) then
begin
if (Plugins[PluginID].Info.Version < Info.Version) then
begin // Found newer version of this plugin
Core.ReportDebug(integer(PChar('Found a newer version of plugin: ' + String(Info.Name))), PChar('TPluginLoader'));
// Unload old plugin
UnloadPlugin(PluginID, nil);
// Fill with new info
Plugins[PluginID].Info := Info;
Plugins[PluginID].State := 0;
Plugins[PluginID].Path := Filename;
Plugins[PluginID].NeedsDeInit := False;
Plugins[PluginID].hLib := hLib;
// Try to get procs
Plugins[PluginID].Procs.Load := GetProcAddress (hLib, PChar('USPlugin_Load'));
Plugins[PluginID].Procs.Init := GetProcAddress (hLib, PChar('USPlugin_Init'));
Plugins[PluginID].Procs.DeInit := GetProcAddress (hLib, PChar('USPlugin_DeInit'));
if (@Plugins[PluginID].Procs.Load = nil) OR (@Plugins[PluginID].Procs.Init = nil) OR (@Plugins[PluginID].Procs.DeInit = nil) then
begin
FreeLibrary(hLib);
Plugins[PluginID].State := 255;
Core.ReportError(integer(PChar('Can''t get plugin procs from libary: "' + Info.Name + '" ' + Filename)), PChar('TPluginLoader'));
end;
end
else
begin // Newer Version already loaded
FreeLibrary(hLib);
end;
end
else
begin
FreeLibrary(hLib);
Core.ReportError(integer(PChar('Plugin with this name already exists: ' + String(Info.Name))), PChar('TPluginLoader'));
end;
end
else
begin
FreeLibrary(hLib);
Core.ReportError(integer(PChar('No name reported: ' + Filename)), PChar('TPluginLoader'));
end;
end
else
begin
FreeLibrary(hLib);
Core.ReportError(integer(PChar('Can''t find info procedure: ' + Filename)), PChar('TPluginLoader'));
end;
end
else
Core.ReportError(integer(PChar('Can''t load plugin libary: ' + Filename)), PChar('TPluginLoader'));
end;
end;
//--------------
// Calls load func of plugin with the given index
//--------------
function TPluginLoader.CallLoad(Index: Cardinal): integer;
begin
Result := -2;
if(Index < Length(Plugins)) then
begin
if (@Plugins[Index].Procs.Load <> nil) and (Plugins[Index].State = 0) then
begin
try
Result := Plugins[Index].Procs.Load(@PluginInterface);
except
Result := -3;
End;
if (Result = 0) then
Plugins[Index].State := 1
else
begin
FreePlugin(Index);
Plugins[Index].State := 255;
Core.ReportError(integer(PChar('Error calling load function from plugin: ' + String(Plugins[Index].Info.Name))), PChar('TPluginLoader'));
end;
end;
end;
end;
//--------------
// Calls init func of plugin with the given index
//--------------
function TPluginLoader.CallInit(Index: Cardinal): integer;
begin
Result := -2;
if(Index < Length(Plugins)) then
begin
if (@Plugins[Index].Procs.Init <> nil) and (Plugins[Index].State = 1) then
begin
try
Result := Plugins[Index].Procs.Init(@PluginInterface);
except
Result := -3;
End;
if (Result = 0) then
begin
Plugins[Index].State := 2;
Plugins[Index].NeedsDeInit := True;
end
else
begin
FreePlugin(Index);
Plugins[Index].State := 255;
Core.ReportError(integer(PChar('Error calling init function from plugin: ' + String(Plugins[Index].Info.Name))), PChar('TPluginLoader'));
end;
end;
end;
end;
//--------------
// Calls deinit proc of plugin with the given index
//--------------
procedure TPluginLoader.CallDeInit(Index: Cardinal);
begin
if(Index < Length(Plugins)) then
begin
if (Plugins[Index].State < 4) then
begin
if (@Plugins[Index].Procs.DeInit <> nil) and (Plugins[Index].NeedsDeInit) then
try
Plugins[Index].Procs.DeInit(@PluginInterface);
except
End;
// Don't forget to remove services and subscriptions by this plugin
Core.Hooks.DelbyOwner(-1 - Index);
FreePlugin(Index);
end;
end;
end;
//--------------
// Frees all plugin sources (procs and handles) - helper for deiniting functions
//--------------
procedure TPluginLoader.FreePlugin(Index: Cardinal);
begin
Plugins[Index].State := 4;
Plugins[Index].Procs.Load := nil;
Plugins[Index].Procs.Init := nil;
Plugins[Index].Procs.DeInit := nil;
if (Plugins[Index].hLib <> 0) then
FreeLibrary(Plugins[Index].hLib);
end;
//--------------
// wParam PChar(PluginName/PluginPath) | wParam (if lParam = nil) ID of the plugin
//--------------
function TPluginLoader.LoadPlugin(wParam: TwParam; lParam: TlParam): integer;
var
Index: integer;
sFile: String;
begin
Result := -1;
sFile := '';
// lParam is ID
if (lParam = nil) then
begin
Index := wParam;
end
else
begin //lParam is PChar
try
sFile := String(PChar(lParam));
Index := PluginExists(sFile);
if (Index < 0) And FileExists(sFile) then
begin // Is filename
AddPlugin(sFile);
Result := Plugins[High(Plugins)].State;
end;
except
Index := -2;
end;
end;
if (Index >= 0) and (Index < Length(Plugins)) then
begin
AddPlugin(Plugins[Index].Path);
Result := Plugins[Index].State;
end;
end;
//--------------
// wParam PChar(PluginName/PluginPath) | wParam (if lParam = nil) ID of the plugin
//--------------
function TPluginLoader.UnloadPlugin(wParam: TwParam; lParam: TlParam): integer;
var
Index: integer;
sName: String;
begin
Result := -1;
// lParam is ID
if (lParam = nil) then
begin
Index := wParam;
end
else
begin // wParam is PChar
try
sName := String(PChar(lParam));
Index := PluginExists(sName);
except
Index := -2;
end;
end;
if (Index >= 0) and (Index < Length(Plugins)) then
CallDeInit(Index)
end;
//--------------
// if wParam = -1 then (if lParam = nil then get length of moduleinfo array. if lparam <> nil then write array of TUS_PluginInfo to address at lparam) else (Get PluginInfo of plugin with Index(wParam) to address at lParam)
//--------------
function TPluginLoader.GetPluginInfo(wParam: TwParam; lParam: TlParam): integer;
var I: integer;
begin
Result := 0;
if (wParam > 0) then
begin // Get info of 1 plugin
if (lParam <> nil) and (wParam < Length(Plugins)) then
begin
try
Result := 1;
PUS_PluginInfo(lParam)^ := Plugins[wParam].Info;
except
End;
end;
end
else if (lParam = nil) then
begin // Get length of plugin (info) array
Result := Length(Plugins);
end
else //Write PluginInfo Array to Address in lParam
begin
try
for I := 0 to high(Plugins) do
PAUS_PluginInfo(lParam)^[I] := Plugins[I].Info;
Result := Length(Plugins);
except
Core.ReportError(integer(PChar('Could not write PluginInfo Array')), PChar('TPluginLoader'));
End;
end;
end;
//--------------
// if wParam = -1 then (if lParam = nil then get length of plugin state array. if lparam <> nil then write array of Byte to address at lparam) else (return state of plugin with index(wParam))
//--------------
function TPluginLoader.GetPluginState(wParam: TwParam; lParam: TlParam): integer;
var I: integer;
begin
Result := -1;
if (wParam > 0) then
begin // Get state of 1 plugin
if (wParam < Length(Plugins)) then
begin
Result := Plugins[wParam].State;
end;
end
else if (lParam = nil) then
begin // Get length of plugin (info) array
Result := Length(Plugins);
end
else // Write plugininfo array to address in lParam
begin
try
for I := 0 to high(Plugins) do
Byte(Pointer(integer(lParam) + I)^) := Plugins[I].State;
Result := Length(Plugins);
except
Core.ReportError(integer(PChar('Could not write pluginstate array')), PChar('TPluginLoader'));
End;
end;
end;
{*********************
TtehPlugins
Implementation
*********************}
//-------------
// function that gives some infos about the module to the core
//-------------
procedure TtehPlugins.Info(const pInfo: PModuleInfo);
begin
pInfo^.Name := 'TtehPlugins';
pInfo^.Version := MakeVersion(1,0,0,chr(0));
pInfo^.Description := 'Module executing the Plugins!';
end;
//-------------
// Just the constructor
//-------------
constructor TtehPlugins.Create;
begin
inherited;
PluginLoader := nil;
end;
//-------------
// Is called on loading.
// In this method only events and services should be created
// to offer them to other modules or plugins during the init process
// if false is returned this will cause a forced exit
//-------------
function TtehPlugins.Load: Boolean;
var
i: integer; // Counter
CurExecutedBackup: integer; //backup of Core.CurExecuted Attribute
begin
// Get pointer to pluginloader
PluginLoader := PPluginLoader(Core.GetModulebyName('TPluginLoader'));
if (PluginLoader = nil) then
begin
Result := false;
Core.ReportError(integer(PChar('Could not get pointer to pluginLoader')), PChar('TtehPlugins'));
end
else
begin
Result := true;
// Backup curexecuted
CurExecutedBackup := Core.CurExecuted;
// Start loading the plugins
for i := 0 to High(PluginLoader.Plugins) do
begin
Core.CurExecuted := -1 - i;
try
// Unload plugin if not correctly executed
if (PluginLoader.CallLoad(i) <> 0) then
begin
PluginLoader.CallDeInit(i);
PluginLoader.Plugins[i].State := 254; // Plugin asks for unload
Core.ReportDebug(integer(PChar('Plugin selfabort during loading process: ' + String(PluginLoader.Plugins[i].Info.Name))), PChar('TtehPlugins'));
end
else
begin
Core.ReportDebug(integer(PChar('Plugin loaded succesfully: ' + String(PluginLoader.Plugins[i].Info.Name))), PChar('TtehPlugins'));
end;
except
// Plugin could not be loaded.
// => Show error message, then shutdown plugin
on E: Exception do
begin
PluginLoader.CallDeInit(i);
PluginLoader.Plugins[i].State := 255; // Plugin causes error
Core.ReportError(integer(PChar('Plugin causes error during loading process: ' + PluginLoader.Plugins[i].Info.Name + ', ErrorMsg: "' + E.Message + '"')), PChar('TtehPlugins'));
end;
end;
end;
// Reset CurExecuted
Core.CurExecuted := CurExecutedBackup;
end;
end;
//-------------
// Is called on init process
// in this method you can hook some events and create + init
// your classes, variables etc.
// if false is returned this will cause a forced exit
//-------------
function TtehPlugins.Init: Boolean;
var
i: integer; // Counter
CurExecutedBackup: integer; // backup of Core.CurExecuted attribute
begin
Result := true;
// Backup CurExecuted
CurExecutedBackup := Core.CurExecuted;
// Start loading the plugins
for i := 0 to High(PluginLoader.Plugins) do
try
Core.CurExecuted := -1 - i;
// Unload plugin if not correctly executed
if (PluginLoader.CallInit(i) <> 0) then
begin
PluginLoader.CallDeInit(i);
PluginLoader.Plugins[i].State := 254; //Plugin asks for unload
Core.ReportDebug(integer(PChar('Plugin selfabort during init process: ' + String(PluginLoader.Plugins[i].Info.Name))), PChar('TtehPlugins'));
end
else
Core.ReportDebug(integer(PChar('Plugin inited succesfully: ' + String(PluginLoader.Plugins[i].Info.Name))), PChar('TtehPlugins'));
except
// Plugin could not be loaded.
// => Show error message, then shut down plugin
PluginLoader.CallDeInit(i);
PluginLoader.Plugins[i].State := 255; //Plugin causes Error
Core.ReportError(integer(PChar('Plugin causes error during init process: ' + String(PluginLoader.Plugins[i].Info.Name))), PChar('TtehPlugins'));
end;
// Reset CurExecuted
Core.CurExecuted := CurExecutedBackup;
end;
//-------------
// Is called if this module has been inited and there is a exit.
// Deinit is in backwards initing order
//-------------
procedure TtehPlugins.DeInit;
var
i: integer; // Counter
CurExecutedBackup: integer; // backup of Core.CurExecuted attribute
begin
// Backup CurExecuted
CurExecutedBackup := Core.CurExecuted;
// Start loop
for i := 0 to High(PluginLoader.Plugins) do
begin
try
// DeInit plugin
PluginLoader.CallDeInit(i);
except
end;
end;
// Reset CurExecuted
Core.CurExecuted := CurExecutedBackup;
end;
end.