www.astonshell.com

few words about writing plug-ins in Delphi

 
Post new topic   Reply to topic    AstonShell.com Forum Index   Aston 1.x Plug-ins
View previous topic :: View next topic  
Author Message
Yurgel



Joined: 26 Dec 2007
Posts: 2
Location: Moscow, Russia

PostPosted: Wed Dec 26, 2007 11:04 pm    Post subject: few words about writing plug-ins in Delphi Reply with quote

Good day to all of you. I'm newbie here, so maybe currently i'm writing wrong things in wrong place - i'll beg your pardon, if this is so Smile Hope that moderators will put things right or just kill the post if it isn't necessary.

but i'd like to share some ideas about the subj, 'cause current situation looks a little bit incorrect. First, examples, provided for "delphi-writers" in Plugin SDK have no common with regular Delphi approach for writing things of a such kind. It's not a "object-oriented code", it's just a word-by-word translation of C++ sources. And these examples, from my point of view, are violating the main idea of Delphi - to let the developer concentrate at really important things, not at window creating by calling Win32 API functions. (BTW, this is one of the reasons, why delphists are called "mouse-clickers" - the main part of a job IDE does itself, but from other hand - that is the factor which let us think about the goal but not the implementation) All mentioned above is just my point of view, and i'm gonna to prove it Smile

The second and the last. Although creating plugins for Aston in Delphi IS VERY EASY, provided samples can only frighten a person who is really writing in pure Delphi and, in most cases, has no idea about Win32 API. Of course, we will have to use low-level functions, but not for implementing the whole gadget, but only for providing some specific "Aston-looking" behavior to our form.

Well, here the philosophy ends, and let us get to "mouse-clicking" closely.

===============================================

We know, that Aston plugin is a regular dynamic loadable library, dll, with specific extension and a row of a specific functions, being exported. This is the only one thing, which we will need from the examples in SDK - we must understand, what functions with which parameters we will have to export, when we're writing our own plugin. I was fighting with DeskChld.dpr, but the logic is quite similar everywhere.

So, exported functions are:
Code:

exports
  InitGlobalModule,
  DoneModule;
end.


Let's have a look at them:

Code:

procedure InitGlobalModule(const AstonData:PAstonData);stdcall;
var
  WindowClass:TWndClass;
begin

  RtlMoveMemory(@AData, AstonData, SizeOf(TAstonData));

  RtlZeroMemory(@WindowClass, sizeOf(TWndClass));
  WindowClass.style:=CS_HREDRAW or CS_VREDRAW;
  WindowClass.hCursor := LoadCursor(0, IDC_ARROW);
  WindowClass.hInstance := hInstance;
  WindowClass.lpfnWndProc:=@WindowProc;
  WindowClass.lpszClassName:= szClassName;
  if RegisterClass(WindowClass) = 0 then exit;


  ReadConfig;

  ChildWindow:= CreateWindowEx(WS_EX_TRANSPARENT,
    szClassName,
    nil,
    WS_VISIBLE or WS_CHILD or WS_CLIPSIBLINGS or WS_CLIPCHILDREN,
    XPos, YPos,
    64, 48,
    AData.Desktop,
    0,
    hInstance,
    nil);

  AData.Node.Wnd:= ChildWindow;
  GetClientRect(ChildWindow, ClientRC);
end;

{---------------------------------------------------------------------}
{---------------------------------------------------------------------}

procedure DoneModule; stdcall;
begin
  if ChildWindow<>0 then
  begin
    DestroyWindow(ChildWindow);
    ChildWindow:=0;
  end;
  UnregisterClass(szClassName, hInstance);



InitGlobalModule is the main function, that is being called from Aston and have to fire up our module. What it does?

Adata is a global variable, which is being used in other functions. When we are being called from Aston for the very first time, Aston is passing to us the pointer to it's own data. Note, that we can modify this data. Although this is a constant parameter, but actually it is a pointer. We can't modify the address in memory, which this pointer is pointing to, and that is why it is "const", but we can freely modify the data, which resides at this address, and these changes may reflect at the Aston's behavior. So, in this function we are receiving a pointer to some internal Aston data and here we are supposed to create our own form. Given example creates this form by calling Win32 API functions, says to Aston, that his Desktop have to be a parent for the window created, and stores coordinates of the window in another global variables. Actually, this is almost everyting, that this procedure does: receives reference at Aston's desktop, creates window, parented to this desktop, and copies Aston's data to it's own variables, which will be available until the module is loaded in memory.

procedure DoneModule is quite transparent - it's just freeing our form, created before.

Now, when we're understanding, what we have to do, let's forget about examples and create the subj in Delphi style. What does the Delphist when he have to create the new form? The correct answer - he is pushing the appropriate menu item. The goal is quite simple: we have to create new library, add a form to it, and exports two functions: one creating the form parented to a given structure and the second, freeing this form. The whole source will look loke this:

Code:

library lazycpu;

uses
  Windows,
  Messages,
  FNxxx in '..\..\AstonSDK\Pascal\SDK\FNxxx.pas',
  AstonSDK in '..\..\AstonSDK\Pascal\SDK\AstonSDK.pas',
  st_api in '..\..\AstonSDK\Pascal\SDK\st_api.pas',
  Unit1 in 'Unit1.pas' {Form1};

{---------------------------------------------------------------------}

{$IFDEF Debug}
{$E .plg}
{$ELSE}
{$I ..\plg.pas}
{$ENDIF}

{$IMAGEBASE $00400000}


procedure InitGlobalModule(const AstonData:PAstonData);stdcall;
begin
  Form1 := TForm1.CreateParented (AstonData^);
end;

procedure DoneModule; stdcall;
begin
  if Assigned (Form1) then begin
    Form1.Free;
    Form1 := nil;
  end;
end;

exports
  InitGlobalModule,
  DoneModule;
end.


Look at the original DeskChld.dpr from SDK:
Code:

library DeskChld;

uses
  Windows,
  Messages,
  FNxxx in '..\..\SDK\FNxxx.pas',
  AstonSDK in '..\..\SDK\AstonSDK.pas',
  st_api in '..\..\SDK\st_api.pas';

{---------------------------------------------------------------------}

{$IFDEF Debug}
{$E .plg}
{$ELSE}
{$I ..\..\plg.pas}
{$ENDIF}

{$IMAGEBASE $00400000}


{---------------------------------------------------------------------}
const
 szClassName = 'Desktop Plugin Sample';
 szXPos = 'XPos';
 szYPos = 'YPos';

var
 AData: TAstonData;
 ChildWindow:hWnd=0;
 ClientRC:TRect;
 XPos, YPos: integer;
{---------------------------------------------------------------------}
{---------------------------------------------------------------------}
{---------------------------------------------------------------------}
procedure SavePos;
var
  CFG:TCFG;
  r:TRect;
begin
  OpenCFGw(AData.PluginsCFG, @CFG);
  FindSection(szClassName, SECTION_CREATE_ALWAYS, @CFG);
  GetWindowRect(ChildWindow, r);
  XPos:=r.Left;
  YPos:=r.Top;
  SetCFGInt(szXPos, XPos, @CFG);
  SetCFGInt(szYPos, YPos, @CFG);
  CloseCFGw(AData.PluginsCFG, false, @CFG);
end;
{---------------------------------------------------------------------}
function WindowProc(Window: HWnd; Message:UINT; WParam: WParam;
  LParam: LParam): Longint;  stdcall;
var
  DC: hDC;
  ps: TPaintStruct;
  r, r2:Trect;
  uFlags:UINT;
begin
  result := 0;
  case Message of
    WM_PAINT:
    begin
      DC:=BeginPaint(Window, ps);
      GetClientRect(Window, ClientRC);
      GetWindowRect(Window, r);
      SetRect(r2, 0,0, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN));
      uFlags:= DPEx_CORRECTBRUSH;
      with AData.WallPaperInfo do
      begin
        if TileWallpaper<>0 then uFlags:= uFlags or DPEx_TILED;
        if WallpaperStyle=0 then uFlags:= uFlags or DPEx_CENTERED;
        DrawSkin(DC, DC, ClientRC, r, r2, Wallpaper, DesktopBrush, uFlags);
      end;

      DrawEdge(DC, ClientRC, BDR_SUNKENOUTER, BF_RECT);
      InflateRect(ClientRC, -8, -8);
      DrawEdge(DC, ClientRC, BDR_RAISEDINNER, BF_RECT);
      EndPaint(Window, ps);
      exit;
    end;
    WM_ERASEBKGND:
    begin
      result:= 1;
      exit;
    end;
    WM_ENTERSIZEMOVE:
    begin
      SetWindowLong(Window, GWL_EXSTYLE, GetWindowLong(Window, GWL_EXSTYLE) and not WS_EX_TRANSPARENT);
    end;
    WM_MOVING:
    begin
      InvalidateRect(Window, nil, false);
    end;
    WM_EXITSIZEMOVE:
    begin
      SavePos;
      SetWindowLong(Window, GWL_EXSTYLE, GetWindowLong(Window, GWL_EXSTYLE) or WS_EX_TRANSPARENT);
      InvalidateRect(Window, nil, false);
    end;
    WM_NCHITTEST:
    begin
      DefWindowProc(Window, Message, WParam, LParam);
      result:=HTCAPTION;
      exit;
    end;
  end;
  result := DefWindowProc(Window, Message, WParam, LParam);
end;

{---------------------------------------------------------------------}
procedure ReadConfig;
var
  CFG:TCFG;
begin
  OpenCFGr(AData.PluginsCFG, @CFG);

  FindSection(szClassName, 0, @CFG);
  XPos:=GetCFGInt(szXPos, 0, @CFG);
  CheckInt(XPos, 0, GetSystemMetrics(SM_CXSCREEN)-8, 0);
  YPos:=GetCFGInt(szYPos, 1, @CFG);
  CheckInt(YPos, 0, GetSystemMetrics(SM_CYSCREEN)-8, 0);
  CloseCFGr(@CFG);
end;
{---------------------------------------------------------------------}
{---------------------------------------------------------------------}

procedure InitGlobalModule(const AstonData:PAstonData);stdcall;
var
  WindowClass:TWndClass;
begin

  RtlMoveMemory(@AData, AstonData, SizeOf(TAstonData));

  RtlZeroMemory(@WindowClass, sizeOf(TWndClass));
  WindowClass.style:=CS_HREDRAW or CS_VREDRAW;
  WindowClass.hCursor := LoadCursor(0, IDC_ARROW);
  WindowClass.hInstance := hInstance;
  WindowClass.lpfnWndProc:=@WindowProc;
  WindowClass.lpszClassName:= szClassName;
  if RegisterClass(WindowClass) = 0 then exit;


  ReadConfig;

  ChildWindow:= CreateWindowEx(WS_EX_TRANSPARENT,
    szClassName,
    nil,
    WS_VISIBLE or WS_CHILD or WS_CLIPSIBLINGS or WS_CLIPCHILDREN,
    XPos, YPos,
    64, 48,
    AData.Desktop,
    0,
    hInstance,
    nil);

  AData.Node.Wnd:= ChildWindow;
  GetClientRect(ChildWindow, ClientRC);
end;

{---------------------------------------------------------------------}
{---------------------------------------------------------------------}

procedure DoneModule; stdcall;
begin
  if ChildWindow<>0 then
  begin
    DestroyWindow(ChildWindow);
    ChildWindow:=0;
  end;
  UnregisterClass(szClassName, hInstance);
end;

{---------------------------------------------------------------------}
{---------------------------------------------------------------------}

exports
  InitGlobalModule,
  DoneModule;
end.


and try to feel a difference. There is only one moment, needs to be explained: call to class function CreateParented with AstonData as parameter. I didn't want to write form-specific functions in DLL, so all functions, which are implementing mentioned above "Aston-specific" behaviour are written directly in the form unit. Since our form have to operate with Aston's data, it was quite logical to pass reference to this data in a moment of form creation. Therefore i write a little bit different version of a class function CreateParented, which receives AstonData as parameter and creates the form as a child of a field Desktop of AstonData structure:

Code:

constructor TForm1.CreateParented(AstonData: TAstonData);
begin
  ParentWindow := AstonData.Desktop;
  Create (nil);
  fAstonData := AstonData;
  fAstonData.Node.Wnd := Handle;
  ReadConfig;
  ...
end;


As you can see, Form1 "remembers" AstonData reference and can operate with it until it lives. Therefore procedures ReadConfig and SavePos, being implemented like methods of a form, have an access to Aston structures and Form is able to read its parameters from ini and to write it back when they're changed.

Her is the listing of the whole form:

Code:

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, AstonSDK, st_api, StdCtrls, ExtCtrls, Registry, Grids;

const
  szXPos = 'XPos';
  szYPos = 'YPos';

  SystemBasicInformation = 0;
  SystemPerformanceInformation = 2;
  SystemTimeOfDayInformation = 3;

type

  SYSTEM_BASIC_INFORMATION = packed record
    AlwaysZero              : ULONG;
    uKeMaximumIncrement     : ULONG;
    uPageSize               : ULONG;
    uMmNumberOfPhysicalPages: ULONG;
    uMmLowestPhysicalPage   : ULONG;
    uMmHighestPhysicalPage  : ULONG;
    uAllocationGranularity  : ULONG;
    pLowestUserAddress      : POINTER;
    pMmHighestUserAddress   : POINTER;
    uKeActiveProcessors     : POINTER;
    bKeNumberProcessors     : BYTE;
    Filler                  : array [0..2] of BYTE;
  end;

  SYSTEM_PERFORMANCE_INFORMATION = packed record
    nIdleTime               : INT64;
    dwSpare                 : array [0..75]of DWORD;
  end;

  SYSTEM_TIME_INFORMATION = packed record
    nKeBootTime             : INT64;
    nKeSystemTime           : INT64;
    nExpTimeZoneBias        : INT64;
    uCurrentTimeZoneId      : ULONG;
    dwReserved              : DWORD;
  end;

type
  TForm1 = class(TForm)
    Timer1: TTimer;
    sg: TStringGrid;
    Label1: TLabel;
    Label2: TLabel;
    procedure Timer1Timer(Sender: TObject);
    procedure sgDrawCell(Sender: TObject; ACol, ARow: Integer; Rect: TRect;
      State: TGridDrawState);
  private
    { Private declarations }
    fAstonData: TAstonData;
  protected
    procedure CreateParams(var Params: TCreateParams); override;
    procedure WMNCHitTest (var Msg: TWMNCHITTEST); message WM_NCHITTEST;
    procedure WMExitSizeMove (var Msg: TMessage); message WM_EXITSIZEMOVE;

    procedure ReadConfig;
    procedure SavePos;
  public
    { Public declarations }
    constructor CreateParented(AstonData: TAstonData);
  end;

function NtQuerySystemInformation(
   SystemInformationClass: DWORD;   // тип требуемой информации
   SystemInformation : Pointer;     // указатель на буфер, в который вернется информация
   SystemInformationLength : DWORD; // размер буфера в байтах
   var ReturnLength: DWORD          // сколько байт было возвращено или требуется
   ): DWORD; stdcall; external 'ntdll.dll';

var
  nOldIdleTime    : Int64 = 0;
  nOldSystemTime  : INT64 = 0;
  nNewCPUTime     : ULONG = 0;
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.CreateParams(var Params: TCreateParams);
begin
  inherited;
  Params.Style := WS_VISIBLE or WS_CHILD or WS_CLIPSIBLINGS or WS_CLIPCHILDREN;
end;

constructor TForm1.CreateParented(AstonData: TAstonData);
begin
  ParentWindow := AstonData.Desktop;
  Create (nil);
  fAstonData := AstonData;
  fAstonData.Node.Wnd := Handle;
  ReadConfig;
  sg.ColCount := sg.ClientWidth div 4;
  sg.RowCount := sg.ClientHeight div 2;
  Timer1.Enabled := true;
end;

procedure TForm1.ReadConfig;
var
  lCFG:TCFG;
  lClassName: string;
  XPos, YPos: integer;
begin
  lClassName := ClassName;
  OpenCFGr(fAstonData.PluginsCFG, @lCFG);

  FindSection(PAnsiChar (lClassName), 0, @lCFG);
  XPos:=GetCFGInt(szXPos, 0, @lCFG);
  CheckInt(XPos, 0, GetSystemMetrics(SM_CXSCREEN)-8, 0);
  YPos:=GetCFGInt(szYPos, 1, @lCFG);
  CheckInt(YPos, 0, GetSystemMetrics(SM_CYSCREEN)-8, 0);
  CloseCFGr(@lCFG);

  Left := XPos;
  Top := YPos;
end;

procedure TForm1.SavePos;
var
  lCFG:TCFG;
  lClassName: string;
begin
  lClassName := ClassName;
  OpenCFGw(fAstonData.PluginsCFG, @lCFG);
  FindSection(PAnsiChar (lClassName), SECTION_CREATE_ALWAYS, @lCFG);
  SetCFGInt(szXPos, Left, @lCFG);
  SetCFGInt(szYPos, Top, @lCFG);
  CloseCFGw(fAstonData.PluginsCFG, false, @lCFG);
end;

procedure TForm1.WMExitSizeMove(var Msg: TMessage);
begin
  SavePos;
end;

procedure TForm1.WMNCHitTest(var Msg: TWMNCHITTEST);
begin
  Msg.Result := HTCAPTION;
end;

function GetNewCPUTime: integer;
var
  spi : SYSTEM_PERFORMANCE_INFORMATION;
  sti : SYSTEM_TIME_INFORMATION;
  sbi : SYSTEM_BASIC_INFORMATION;
  Dummy: DWORD;
begin
  Result := 0;
  if NTQuerySystemInformation(SystemBasicInformation, @sbi,
    SizeOf(SYSTEM_BASIC_INFORMATION), Dummy) = NO_ERROR then
      if NTQuerySystemInformation(SystemTimeOfDayInformation, @sti,
        SizeOf(SYSTEM_TIME_INFORMATION), Dummy) = NO_ERROR then
          if NTQuerySystemInformation(SystemPerformanceInformation, @spi,
            SizeOf(SYSTEM_PERFORMANCE_INFORMATION), Dummy) = NO_ERROR then begin
            if (nOldIdleTime <> 0) then begin
              Result := Trunc(100 - ((spi.nIdleTime - nOldIdleTime)
              / (sti.nKeSystemTime - nOldSystemTime) * 100)
              / sbi.bKeNumberProcessors + 0.5);
              if (Result <> nOldIdleTime) then
            end;
            nOldIdleTime   := spi.nIdleTime;
            nOldSystemTime := sti.nKeSystemTime;
          end;
end;

procedure TForm1.Timer1Timer(Sender: TObject);
var
  x, y: integer;
  cur: integer;
begin
    cur := GetNewCPUTime;
    with sg do begin
      for x := 0 to ColCount - 2 do begin
        for y := 0 to RowCount - 1 do begin
          Cells [x, y] := Cells [x + 1, y];
        end;
      end;
      for y := 0 to RowCount - 1 do begin
        if y < RowCount - cur / (100 / RowCount) then
          Cells [ColCount - 1, y] := ''
        else
          Cells [ColCount - 1, y] := '1';
      end;
    end;
    Label2.Caption := IntToStr (cur) + '%';
end;

procedure TForm1.sgDrawCell(Sender: TObject; ACol, ARow: Integer;
  Rect: TRect; State: TGridDrawState);
begin
  with sg.Canvas do begin
    if sg.Cells [ACol, ARow] = '' then
      Brush.Color := clBlack
    else begin
      if ARow < sg.RowCount / 4 then
        Brush.Color := clRed
      else
      if ARow < sg.RowCount / 2 then
        Brush.Color := clYellow
      else
        Brush.Color := clLime;
    end;
    Rect.Right := Rect.Right - 1;
    Rect.Bottom := Rect.Bottom - 1;
    FillRect (Rect);
  end;
end;

end.


I won't describe it step-by-step, main part of a code implements an alternative variant for CPU-Meter. But instead of example in SDK, this is a really Delphi form, with a TStringGrid, being painted in it's own event, and 2 TLabels, created by simple "mouse-clicking", not by calling to CreateWindowEx and other stuff Smile Feel free to kill existing controls on the form and add your own buttons, images, or whatever you like. There is still much work to do: there is no configuration window, form does not change itself according to theme changes, but i hope, that main idea is clear Smile such things, lke a WndProc and many others - are already implemented by low-level Delphi components, and there is absolutely no need to cary about such trivial things, like erasing background or invalidating the form after it has been moved.

P.S.

This CPUMeter does not work with PerfomanceStats, but counts a time during which CPU was idle for a period of time mesured, supposing that other time during this period CPU was doing smth:) That is why things it shows can differ from standard Aston's CPU gauge, and that is whay it is called "Lazy" - it is based on a fact that sometimes CPU does nothing.

P.S.S.
I've just reread everything before posting.. and i just want to apologize before gyus who wrote SDK for Delphi/Pascal - it was a great help, actually, and all that i wanted to say - that Delphi provides a way to make things easier Wink

P.S.S.S.
also sorry for my language - english isn't my native.




lazycpu.rar
 Description:
*.PLG file and the sources

Download
 Filename:  lazycpu.rar
 Filesize:  185.53 KB
 Downloaded:  964 Time(s)

Back to top
View user's profile Send private message
Display posts from previous:   
Post new topic   Reply to topic    AstonShell.com Forum Index   Aston 1.x Plug-ins All times are GMT
Page 1 of 1
Jump to:  

 
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum
You can attach files in this forum
You can download files in this forum


This forum is available via an RSS feed  Click to see the XML version of this web page.

Copyright © 1999-2007 Gladiators Software