LAG ENVIRONMENT MANUAL EXTERNAL SMART OBJECT
library typicalexternalso;
{
The function getcompatibility is mandatory.
The program checks for the presence of the function with the following syntax in a library and expects an output with the identifier 'LAG ENVIRONMENT'.
If this requirement is not fulfilled then the library will not be treated as if it is compatible with this program.
If the initialize procedure exists then immediately after the library is loaded the initialize procedure is applied.
If the finalize procedure exists then immediately before the library is unloaded the finalize procedure is applied.
If the help function exists then the output lines will be appended to the main help lines. Make sure binary commands are used as text commands may interfere with other parts of help.
In order to be able to use commands the following functions/procedures must be present(all of them): setinputdata, getpointertoidentifier, issanecommand, command, getoutputdata
If all of the above functions/procedures exist then the main program will:
1:
If the main program allows threads then if threadmode exists and returns a value greater than one then that value is the maximum number of threads.
If the returned value is 255 then unlimited number of threads may be used at the same time to call the command procedure.
If the value returned is zero, or the function doesn't exist then threads will be disabled to this library even if the main program allows use of threads.
2:
If getnewidentifier exists then it will be called.
If it doesn't exist then it's the same as if the function returns value zero.
If the value is zero then the main program tries to find an identifier.
3:
If initializeidentifier exists then it is called in order to allow the library to do any needed operations(like allocating space).
4:
Call setinputdata in order to allow the library to assign the command content to the identifier(like copying the data).
5:
If timeout exists then it will be called.
If it doesn't exist or it returns a value smaller than 1 then the main program will use a default timeout value.
The timeout value represents milliseconds and is used when the main program waits for the library command to finish.
6:
Call getpointertoidentifier in order to get an address to the identifier.
This memory address will be used as parameter when calling command.(In the example the pointer points to the identifier longword value);
7:
Call issanecommand in order to know if the command procedure should be used with the identifier(data that was given as parameter when setinputdata was called).
8:
If issanecommand returned a different than zero value, then command procedure is called with the identifier.
If upon execution of the command routine, OUTPUTDATABLOCK contains a new command(chr(39) by default for a silent success), then the command is considered success.
If upon execution of the command routine, OUTPUTDATABLOCK is nil or empty, then the command is considered failure.
9:
If issanecommand returned a different than zero value, then getoutputdata is called with the identifier in order to read the output of the command.
10:
Call finalizeidentifier in order to announce the library that the main program has finished working with the identifier.
This means that any input and output allocated space for that identifier may be freed.
In order to avoid memory leaks and other memory related problems the following rule is used:
The entity that allocates space is in charge with the management of the occupied space.
This means that if memory is allocated by the library, then the library has to free the memory space and also the library will resize(if needed) the allocated memory.
Exceptions from this rule may be allowed, but these should be carefully tested.
Binary commands syntax:
Character 0, Size of the command content in bytes stored as a longword, Command content.
Examples:
Textcommand 'Echo
Binary command byte(0),longword(5),'Echo
Textcommand GETCAMERAINFO
Binary command byte(0),longword(13),GETCAMERAINFO
Calling mechanism is "stdcall" for Windows and "cdecl" for other operating systems.
ATTENTION!!!
Do not try to export functions that return pointers. It will not work.
}
{$mode objfpc}{$H+}
uses sysutils, classes;//Uses example
type tdatastructure=record
Identifier:longword;
OutputDataBlock:pointer;
InputDataBlock:pointer;
end;
ptdatastructure=^tdatastructure;
var DataArray:array of tdatastructure;//Recommended approach to use identifiers with multiple threads.
StaticData:pointer;//Data for functions like help or getcompatibility
procedure command(const pidentifier:pointer);{$ifdef windows}stdcall{$else}cdecl{$endif};
var IndexTmp:longword;
begin
if ((length(DataArray)>0)and(pidentifier<>nil)) then
for IndexTmp:=0 to high(DataArray) do
if DataArray[IndexTmp].Identifier=plongword(pidentifier)^ then
begin
if ((DataArray[IndexTmp].InputDataBlock<>nil)and(plongword(DataArray[IndexTmp].InputDataBlock)^>0)) then
begin//The command input pointer is not nil and the content of the input data is greater than zero
end;
break;
end;
end;
procedure setinputdata(const Identifier:longword;const MemoryBlock:pointer);{$ifdef windows}stdcall{$else}cdecl{$endif};
{Sets input data content. MemoryBlock points to the input data(the command content) of the Identifier.
This procedure is always used before calling the command procedure. If this procedure is missing then the command procedure will not be called.
The content of the procedure is just as example!!!}
var IndexTmp:longword;
begin
if length(DataArray)>0 then for IndexTmp:=0 to high(DataArray) do
if DataArray[IndexTmp].Identifier=Identifier then
begin//Copy the data from MemoryBlock
if MemoryBlock<>nil then
begin
if DataArray[IndexTmp].InputDataBlock=nil then DataArray[IndexTmp].InputDataBlock:=getmem(sizeof(longword)+plongword(MemoryBlock)^) else ReallocMem(DataArray[IndexTmp].InputDataBlock,sizeof(longword)+plongword(MemoryBlock)^);
move(MemoryBlock^,DataArray[IndexTmp].InputDataBlock^,sizeof(longword)+plongword(MemoryBlock)^);
end else
begin
if DataArray[IndexTmp].InputDataBlock=nil then DataArray[IndexTmp].InputDataBlock:=getmem(sizeof(longword)) else ReallocMem(DataArray[IndexTmp].InputDataBlock,sizeof(longword));
plongword(DataArray[IndexTmp].InputDataBlock)^:=0;
end;
break;
end;
end;
procedure getoutputdata(const Identifier:longword;var MemoryBlock:pointer);{$ifdef windows}stdcall{$else}cdecl{$endif};
{This procedure is always used after calling the command procedure. If this procedure is missing then the command procedure will not be called.
Gets the output data content. MemoryBlock points to the output data(data stored by the command procedure) of the Identifier.
The content of the procedure is just as example!!!}
var IndexTmp:longword;
begin
MemoryBlock:=nil;
if length(DataArray)>0 then for IndexTmp:=0 to high(DataArray) do
if DataArray[IndexTmp].Identifier=Identifier then begin MemoryBlock:=DataArray[IndexTmp].OutputDataBlock;break;end;
end;
procedure help(var MemoryBlock:pointer);{$ifdef windows}stdcall{$else}cdecl{$endif};
begin
//The following lines can be replaced with: MemoryBlock:=nil;
ReallocMem(StaticData,sizeof(longword));plongword(StaticData)^:=0;
MemoryBlock:=StaticData;
end;
procedure getcompatibility(var MemoryBlock:pointer);{$ifdef windows}stdcall{$else}cdecl{$endif};
const CompatibilityIdentifier:shortstring='LAG ENVIRONMENT';//This is the compatibility identifier.
begin
ReallocMem(StaticData,sizeof(longword)+length(CompatibilityIdentifier));//Allocate space(4Bytes as the size of longword+15Bytes as the size of the identifier)
plongword(StaticData)^:=length(CompatibilityIdentifier);//Update the size of the content stored at StaticData
move(pointer(@CompatibilityIdentifier[1])^,pointer(StaticData+sizeof(longword))^,length(CompatibilityIdentifier));//Copy the content of CompatibilityIdentifier
MemoryBlock:=StaticData;//Return a pointer to StaticData which now contains the identifier
end;
procedure initialize;{$ifdef windows}stdcall{$else}cdecl{$endif};
begin
StaticData:=getmem(sizeof(longword));plongword(StaticData)^:=0;//Initialize the memory block that stores output information for standard procedures like getcompatibility or help
setlength(DataArray,0);//Other initializations
end;
procedure finalize;{$ifdef windows}stdcall{$else}cdecl{$endif};
var IndexTmp:longword;
begin
freemem(StaticData,sizeof(longword)+plongword(StaticData)^);//Free the memory block that stores output information for standard procedures like getcompatibility or help
//Free the memory blocks that store identifiers together with inputs and outputs
if length(DataArray)>0 then for IndexTmp:=0 to high(DataArray) do
begin
freemem(DataArray[IndexTmp].InputDataBlock,sizeof(longword)+plongword(DataArray[IndexTmp].InputDataBlock)^);
freemem(DataArray[IndexTmp].OutputDataBlock,sizeof(longword)+plongword(DataArray[IndexTmp].OutputDataBlock)^);
end;
setlength(DataArray,0);
end;
function threadmode:byte;{$ifdef windows}stdcall{$else}cdecl{$endif};
{The function should return:
0 Threads are not allowed;
1..254 No more than this number of threads are allowed;
255 Any number of threads is allowed;
}
begin
Result:=0;//Threads are not allowed
end;
function getnewidentifier:longword;{$ifdef windows}stdcall{$else}cdecl{$endif};
{If the function returns 0, it's the same as if the function doesn't exist.This means that the main program has to find a unique command identifier.
Developing code within this function might be useful if this library is used within unlimited threads or
when optimizations based on space allocated for obsolete identifiers is reused.}
begin
Result:=0;//Set default output
end;
procedure initializeidentifier(const Identifier:longword);{$ifdef windows}stdcall{$else}cdecl{$endif};
{After getting a new identifier the main program calls this procedure.}
var IndexTmp:longword;
IdentifierExists:boolean;
begin
IdentifierExists:=False;
if length(DataArray)>0 then for IndexTmp:=0 to high(DataArray) do
if DataArray[IndexTmp].Identifier=Identifier then
begin
IdentifierExists:=True;
if DataArray[IndexTmp].InputDataBlock<>nil then
begin
ReallocMem(DataArray[IndexTmp].InputDataBlock,sizeof(longword));plongword(DataArray[IndexTmp].InputDataBlock)^:=0;
end;
if DataArray[IndexTmp].OutputDataBlock<>nil then
begin
ReallocMem(DataArray[IndexTmp].OutputDataBlock,sizeof(longword));plongword(DataArray[IndexTmp].OutputDataBlock)^:=0;
end;
break;
end;
if IdentifierExists=False then
begin
setlength(DataArray,succ(length(DataArray)));
DataArray[high(DataArray)].Identifier:=Identifier;
DataArray[high(DataArray)].InputDataBlock:=getmem(sizeof(longword));plongword(DataArray[high(DataArray)].InputDataBlock)^:=0;
DataArray[high(DataArray)].OutputDataBlock:=getmem(sizeof(longword));plongword(DataArray[high(DataArray)].OutputDataBlock)^:=0;
end;
end;
procedure finalizeidentifier(const Identifier:longword);{$ifdef windows}stdcall{$else}cdecl{$endif};
{After the command procedure has ended the main program calls this procedure.}
var IndexTmp:longword;
ShiftData:bytebool;
begin
ShiftData:=False;
if length(DataArray)>0 then for IndexTmp:=0 to high(DataArray) do
begin
if DataArray[IndexTmp].Identifier=Identifier then
begin//The Identifier has been found
freemem(DataArray[IndexTmp].InputDataBlock,sizeof(longword)+plongword(DataArray[IndexTmp].InputDataBlock)^);
freemem(DataArray[IndexTmp].OutputDataBlock,sizeof(longword)+plongword(DataArray[IndexTmp].OutputDataBlock)^);
//Shift data is needed
ShiftData:=True;
end;
if ((ShiftData=True)and(IndexTmp<high(DataArray))) then DataArray[IndexTmp]:=DataArray[succ(IndexTmp)];
end;
if ShiftData=True then setlength(DataArray,pred(length(DataArray)));//If the Identifier has been found then remove the last entry
end;
procedure getpointertoidentifier(const Identifier:longword;var MemoryBlock:pointer);{$ifdef windows}stdcall{$else}cdecl{$endif};
var IndexTmp:longword;
begin
MemoryBlock:=nil;
if length(DataArray)>0 then
for IndexTmp:=0 to high(DataArray) do
begin
if DataArray[IndexTmp].Identifier=Identifier then
begin
MemoryBlock:=pointer(@DataArray[IndexTmp].Identifier);break;
end;
end;
end;
function timeout(const Identifier:longword):longword;{$ifdef windows}stdcall{$else}cdecl{$endif};
{The function returns the number of milliseconds given for the command procedure to end.
The main program will replace any value smaller than 1 with a default value.
If this function is missing from a library then the main program will use a default value.}
begin
Result:=10000;//Give 10 seconds to any command
end;
function issanecommand(const Command:pointer):byte;{$ifdef windows}stdcall{$else}cdecl{$endif};
{The function returns 1 if the command can be used by the library and returns 0 if it can't be used.
The function is called before the command procedure.}
begin
Result:=0;
if ((Command<>nil)and(plongword(Command)^>0)) then
begin//Assume any non-empty Command to be a valid one.{Not recommended to be left in this state.}
Result:=1;
end;
end;
exports getcompatibility, setinputdata, command, getoutputdata, initialize, finalize, help, threadmode,
getpointertoidentifier, getnewidentifier, initializeidentifier, finalizeidentifier, issanecommand, timeout;
begin
end.