home *** CD-ROM | disk | FTP | other *** search
/ PC Pro 1999 February / DPPCPRO0299.ISO / February / Delphi / Runimage / DELPHI20 / SOURCE / SAMPLES / IBCTRLS.PAS < prev    next >
Encoding:
Pascal/Delphi Source File  |  1996-06-10  |  13.5 KB  |  479 lines

  1. {********************************************************}
  2. {                                                        }
  3. {       InterBase EventAlerter components                }
  4. {       Copyright (c) 1995 Borland International         }
  5. {                                                        }
  6. {       Written by:                                      }
  7. {         James Thorpe                                   }
  8. {         CSA Australasia                                }
  9. {         Compuserve: 100035,2064                        }
  10. {         Internet:   csa@csaa.com.au                    }
  11. {                                                        }
  12. {********************************************************}
  13.  
  14. unit IBCtrls;
  15.  
  16. interface
  17.  
  18. uses
  19.   SysUtils, Windows, Messages, Classes, Graphics, Controls,
  20.   Forms, Dialogs, DB, IBProc32, BDE;
  21.  
  22. const
  23.   MaxEvents = 15;
  24.   EventLength = 64;
  25.  
  26. type
  27.  
  28.   TIBComponent = class( TComponent)
  29.   private
  30.     FDatabase: TDatabase;
  31.     procedure SetDatabase( value: TDatabase);
  32.     procedure ValidateDatabase( Database: TDatabase);
  33.   protected
  34.     function  GetNativeHandle: isc_db_handle;
  35.     procedure HandleIBErrors( status: pstatus_vector);
  36.     function  IsInterbaseDatabase( Database: TDatabase): Boolean;
  37.   published
  38.     property  Database: TDatabase read FDatabase write SetDatabase;
  39.   end;
  40.  
  41.   TEventAlert = procedure( Sender: TObject; EventName: string; EventCount: longint;
  42.                            var CancelAlerts: Boolean) of object;
  43.  
  44.   TEventBuffer = array[ 0..MaxEvents-1, 0..EventLength-1] of char;
  45.  
  46.   TIBEventAlerter = class(TIBComponent)
  47.   private
  48.     LibHandle: THandle;
  49.     FEvents: TStrings;
  50.     FOnEventAlert: TEventAlert;
  51.     FQueued: Boolean;
  52.     FRegistered: Boolean;
  53.     Buffer: TEventBuffer;
  54.     Changing: Boolean;
  55.     CS: TRTLCriticalSection;
  56.     EventBuffer: PChar;
  57.     EventBufferLen: integer;
  58.     EventID: isc_long;
  59.     EventThreadID: integer;
  60.     ProcessingEvents: Boolean;
  61.     RegisteredState: Boolean;
  62.     ResultBuffer: PChar;
  63.     procedure DoQueueEvents;
  64.     procedure EventChange( sender: TObject);
  65.     procedure UpdateResultBuffer( length: short; updated: PChar);
  66.   protected
  67.     procedure HandleEvent;
  68.     procedure Loaded; override;
  69.     procedure Notification( AComponent: TComponent; Operation: TOperation); override;
  70.     procedure SetEvents( value: TStrings);
  71.     procedure SetDatabase( value: TDatabase);
  72.     procedure SetRegistered( value: boolean);
  73.   public
  74.     constructor Create( AOwner: TComponent); override;
  75.     destructor Destroy; override;
  76.     procedure CancelEvents;
  77.     procedure QueueEvents;
  78.     procedure RegisterEvents;
  79.     procedure UnRegisterEvents;
  80.     property  Queued: Boolean read FQueued;
  81.   published
  82.     property Events: TStrings read FEvents write SetEvents;
  83.     property Registered: Boolean read FRegistered write SetRegistered;
  84.     property OnEventAlert: TEventAlert read FOnEventAlert write FOnEventAlert;
  85.   end;
  86.  
  87.   EIBError = class( Exception);
  88.  
  89. implementation
  90.  
  91. {$R *.RES}
  92.  
  93. const
  94.   SIBMessageBase       = 57800;
  95.   SNoEventsRegistered  = SIBMessageBase + 0;
  96.   SInvalidDBConnection = SIBMessageBase + 1;
  97.   SInvalidDatabase     = SIBMessageBase + 2;
  98.   SInvalidCancellation = SIBMessageBase + 3;
  99.   SInvalidEvent        = SIBMessageBase + 4;
  100.   SInvalidQueueing     = SIBMessageBase + 5;
  101.   SInvalidRegistration = SIBMessageBase + 6;
  102.   SMaximumEvents       = SIBMessageBase + 7;
  103.  
  104. var
  105.   // Dynamically Loaded InterBase API functions (gds32.dll)
  106.   IscQueEvents: TIscQueEvents;
  107.   IscFree: TIscfree;
  108.   IscEventBlock: TIscEventBlock;
  109.   IscEventCounts: TIscEventCounts;
  110.   IscCancelEvents: TIscCancelEvents;
  111.   IscInterprete: TIscInterprete;
  112.  
  113.  
  114. // TIBComponent
  115.  
  116. function TIBComponent.GetNativeHandle: isc_db_handle;
  117. var
  118.   length: word;
  119. begin
  120.   if assigned( FDatabase) and FDatabase.Connected then
  121.     Check( DbiGetProp( HDBIOBJ(FDatabase.Handle), dbNATIVEHNDL,
  122.                        @result, sizeof( isc_db_handle), length))
  123.   else result := nil;
  124. end;
  125.  
  126. procedure TIBComponent.HandleIBErrors( status: pstatus_vector);
  127. var
  128.   buffer: array[0..255] of char;
  129.   errMsg, lastMsg: string;
  130.   errCode: isc_status;
  131. begin
  132.   errMsg := '';
  133.   repeat
  134.     errCode := IscInterprete( @buffer, @status);
  135.     if lastMsg <> strPas( Buffer) then
  136.     begin
  137.       lastMsg := strPas( buffer);
  138.       if length( errMsg) <> 0 then errMsg := errMsg+#13#10;
  139.       errMsg := errMsg+lastMsg;
  140.     end;
  141.   until errCode = 0;
  142.   raise EIBError.Create( errMsg);
  143. end;
  144.  
  145. function TIBComponent.IsInterbaseDatabase( Database: TDatabase): Boolean;
  146. var
  147.   Length: Word;
  148.   Buffer: array[0..63] of Char;
  149. begin
  150.   Result := False;
  151.   if Database.Handle <> nil then
  152.   begin
  153.     Check(DbiGetProp(HDBIOBJ(Database.Handle), dbDATABASETYPE, @Buffer,
  154.       SizeOf(Buffer), Length));
  155.     Result := StrIComp(Buffer, 'INTRBASE') = 0;
  156.   end;
  157. end;
  158.  
  159. procedure TIBComponent.SetDatabase( value: TDatabase);
  160. begin
  161.   if value <> FDatabase then
  162.   begin
  163.     if assigned( value) and value.Connected then ValidateDatabase( value);
  164.     FDatabase := value;
  165.   end;
  166. end;
  167.  
  168. procedure TIBComponent.ValidateDatabase( Database: TDatabase);
  169. begin
  170.   if not assigned( Database) or not Database.Connected then
  171.     raise EIBError.CreateRes( SInvalidDBConnection)
  172.   else if not IsInterbaseDatabase( Database) then
  173.     raise EIBError.CreateResFmt( SInvalidDatabase, [Database.Name]);
  174. end;
  175.  
  176. // TIBEventAlerter
  177.  
  178. procedure HandleEvent( param: integer); stdcall;
  179. begin
  180.   // don't let exceptions propogate out of thread
  181.   try
  182.     TIBEventAlerter( param).HandleEvent;
  183.   except
  184.     Application.HandleException( nil);
  185.   end;
  186. end;
  187.  
  188. procedure IBEventCallback( ptr: pointer; length: short; updated: PChar); cdecl;
  189. var
  190.   ThreadID: integer;
  191. begin
  192.   // Handle events asynchronously in second thread
  193.   EnterCriticalSection( TIBEventAlerter( ptr).CS);
  194.   TIBEventAlerter( ptr).UpdateResultBuffer( length, updated);
  195.   CloseHandle( CreateThread( nil, 8192, @HandleEvent, ptr, 0, ThreadID));
  196.   LeaveCriticalSection( TIBEventAlerter( ptr).CS);
  197. end;
  198.  
  199. constructor TIBEventAlerter.Create( AOwner: TComponent);
  200. begin
  201.   inherited Create( AOwner);
  202.   InitializeCriticalSection( CS);
  203.   FEvents := TStringList.Create;
  204.   with TStringList( FEvents) do
  205.   begin
  206.     OnChange := EventChange;
  207.     Duplicates := dupIgnore;
  208.   end;
  209.   // Attempt to load GDS32.DLL.  If this fails then raise an exception.
  210.   // This will cause the component not to be created
  211.   LibHandle := LoadLibrary('gds32.dll');
  212.   if LibHandle < 32 then
  213.     raise EDLLLoadError.Create('Unable to load gds32.dll');
  214.  
  215.   @IscQueEvents := GetProcAddress(LibHandle, 'isc_que_events');
  216.   if @IscQueEvents = nil then
  217.     raise EDLLLoadError.Create('Failed to lookup isc_que_events');
  218.  
  219.   @IscInterprete := GetProcAddress(LibHandle, 'isc_interprete');
  220.   if @IscInterprete = nil then
  221.     raise EDLLLoadError.Create('Failed to lookup isc_interprete');
  222.  
  223.   @IscFree := GetProcAddress(LibHandle, 'isc_free');
  224.   if @IscFree = nil then
  225.     raise EDLLLoadError.Create('Failed to lookup isc_free');
  226.  
  227.   @IscEventBlock := GetProcAddress(LibHandle, 'isc_event_block');
  228.   if @IscEventBlock = nil then
  229.     raise EDLLLoadError.Create('Failed to lookup isc_event_block');
  230.  
  231.   @IscEventCounts := GetProcAddress(LibHandle, 'isc_event_counts');
  232.   if @IscEventCounts = nil then
  233.     raise EDLLLoadError.Create('Failed to lookup isc_event_counts');
  234.  
  235.   @IscCancelEvents := GetProcAddress(LibHandle, 'isc_cancel_events');
  236.   if @IscCancelEvents = nil then
  237.     raise EDLLLoadError.Create('Failed to lookup isc_cancel_events');
  238.  
  239. end;
  240.  
  241. destructor TIBEventAlerter.Destroy;
  242. begin
  243.   UnregisterEvents;
  244.   SetDatabase( nil);
  245.   TStringList(FEvents).OnChange := nil;
  246.   FEvents.Free;
  247.   DeleteCriticalSection( CS);
  248.   inherited Destroy;
  249.   if LibHandle >= 32 then
  250.     FreeLibrary(LibHandle);
  251.  
  252. end;
  253.  
  254. procedure TIBEventAlerter.CancelEvents;
  255. var
  256.   status: status_vector;
  257.   errCode: isc_status;
  258.   dbHandle: isc_db_handle;
  259. begin
  260.   if ProcessingEvents then
  261.     raise EIBError.CreateRes( SInvalidCancellation);
  262.   if FQueued then
  263.   begin
  264.     try
  265.       // wait for event handler to finish before cancelling events
  266.       EnterCriticalSection( CS);
  267.       ValidateDatabase( Database);
  268.       FQueued := false;
  269.       Changing := true;
  270.       dbHandle := GetNativeHandle;
  271.       errCode := IscCancelEvents( @status, @dbHandle, @EventID);
  272.       if errCode <> 0 then HandleIBErrors( @status)
  273.     finally
  274.       LeaveCriticalSection( CS);
  275.     end;
  276.   end;
  277. end;
  278.  
  279. procedure TIBEventAlerter.DoQueueEvents;
  280. var
  281.   status: status_vector;
  282.   errCode: isc_status;
  283.   callback: pointer;
  284.   dbHandle: isc_db_handle;
  285. begin
  286.   ValidateDatabase( DataBase);
  287.   callback := @IBEventCallback;
  288.   dbHandle := GetNativeHandle;
  289.   errCode := IscQueEvents( @status, @dbHandle, @EventID, EventBufferLen,
  290.                                EventBuffer, isc_callback(callback), self);
  291.   if errCode <> 0 then HandleIBErrors( @status);
  292.   FQueued := true;
  293. end;
  294.  
  295. procedure TIBEventAlerter.EventChange( sender: TObject);
  296. begin
  297.   // check for blank event 
  298.   if TStringList(Events).IndexOf( '') <> -1 then
  299.     raise EIBError.CreateRes( SInvalidEvent);
  300.   // check for too many events
  301.   if Events.Count > MaxEvents then
  302.   begin
  303.     TStringList(Events).OnChange := nil;
  304.     Events.Delete( MaxEvents);
  305.     TStringList(Events).OnChange := EventChange;
  306.     raise EIBError.CreateRes( SMaximumEvents);
  307.   end;
  308.   if Registered then RegisterEvents;
  309. end;
  310.  
  311. procedure TIBEventAlerter.HandleEvent;
  312. var
  313.   CancelAlerts: Boolean;
  314.   i: integer;
  315.   status: status_vector;
  316. begin
  317.   try
  318.     // prevent modification of vital data structures while handling events
  319.     EnterCriticalSection( CS);
  320.     ProcessingEvents := true;
  321.     IscEventCounts( @status, EventBufferLen, EventBuffer, ResultBuffer);
  322.     CancelAlerts := false;
  323.     if assigned(FOnEventAlert) and not Changing then
  324.     begin
  325.       for i := 0 to Events.Count-1 do
  326.       begin
  327.         try
  328.           if (status[i] <> 0) and not CancelAlerts then
  329.             FOnEventAlert( self, Events[Events.Count-i-1], status[i], CancelAlerts);
  330.         except
  331.           Application.HandleException( nil);
  332.         end;
  333.       end;
  334.     end;
  335.     Changing := false;
  336.     if not CancelAlerts and FQueued then DoQueueEvents;
  337.   finally
  338.     ProcessingEvents := false;
  339.     LeaveCriticalSection( CS);
  340.   end;
  341. end;
  342.  
  343. procedure TIBEventAlerter.Loaded;
  344. begin
  345.   inherited Loaded;
  346.   try
  347.     if RegisteredState then RegisterEvents;
  348.   except
  349.     if csDesigning in ComponentState then
  350.       Application.HandleException( self)
  351.     else raise;
  352.   end;
  353. end;
  354.  
  355. procedure TIBEventAlerter.Notification( AComponent: TComponent;
  356.                                         Operation: TOperation);
  357. begin
  358.   inherited Notification( AComponent, Operation);
  359.   if (Operation = opRemove) and (AComponent = FDatabase) then
  360.   begin
  361.     UnregisterEvents;
  362.     FDatabase := nil;
  363.   end;
  364. end;
  365.  
  366. procedure TIBEventAlerter.QueueEvents;
  367. begin
  368.   if not FRegistered then
  369.     raise EIBError.CreateRes( SNoEventsRegistered);
  370.   if ProcessingEvents then
  371.     raise EIBError.CreateRes( SInvalidQueueing);
  372.   if not FQueued then
  373.   begin
  374.     try
  375.       // wait until current event handler is finished before queuing events
  376.       EnterCriticalSection( CS);
  377.       DoQueueEvents;
  378.       Changing := true;
  379.     finally
  380.       LeaveCriticalSection( CS);
  381.     end;
  382.   end;
  383. end;
  384.  
  385. procedure TIBEventAlerter.RegisterEvents;
  386. var
  387.   i: integer;
  388.   bufptr: pointer;
  389.   eventbufptr: pointer;
  390.   resultbufptr: pointer;
  391.   buflen: integer;
  392. begin
  393.   ValidateDatabase( Database);
  394.   if csDesigning in ComponentState then FRegistered := true
  395.   else begin
  396.     UnregisterEvents;
  397.     if Events.Count = 0 then exit;
  398.     for i := 0 to Events.Count-1 do
  399.       StrPCopy( @Buffer[i][0], Events[i]);
  400.     i := Events.Count;
  401.     bufptr := @buffer[0];
  402.     eventbufptr :=  @EventBuffer;
  403.     resultBufPtr := @ResultBuffer;
  404.     asm
  405.       mov ecx, dword ptr [i]
  406.       mov eax, dword ptr [bufptr]
  407.       @@1:
  408.       push eax
  409.       add  eax, EventLength
  410.       loop @@1
  411.       push dword ptr [i]
  412.       push dword ptr [resultBufPtr]
  413.       push dword ptr [eventBufPtr]
  414.       call [IscEventBlock]
  415.       mov  dword ptr [bufLen], eax
  416.       mov eax, dword ptr [i]
  417.       shl eax, 2
  418.       add eax, 12
  419.       add esp, eax
  420.     end;
  421.     EventBufferlen := Buflen;
  422.     FRegistered := true;
  423.     QueueEvents;
  424.   end;
  425. end;
  426.  
  427. procedure TIBEventAlerter.SetEvents( value: TStrings);
  428. begin
  429.   FEvents.Assign( value);
  430. end;
  431.  
  432. procedure TIBEventAlerter.SetDatabase( value: TDatabase);
  433. begin
  434.   if value <> FDatabase then
  435.   begin
  436.     UnregisterEvents;
  437.     if assigned( value) and value.Connected then ValidateDatabase( value);
  438.     FDatabase := value;
  439.   end;
  440. end;
  441.  
  442. procedure TIBEventAlerter.SetRegistered( value: Boolean);
  443. begin
  444.   if (csReading in ComponentState) then
  445.     RegisteredState := value
  446.   else if FRegistered <> value then
  447.     if value then RegisterEvents else UnregisterEvents;
  448. end;
  449.  
  450. procedure TIBEventAlerter.UnregisterEvents;
  451. begin
  452.   if ProcessingEvents then
  453.     raise EIBError.CreateRes( SInvalidRegistration);
  454.   if csDesigning in ComponentState then
  455.     FRegistered := false
  456.   else if not (csLoading in ComponentState) then
  457.   begin
  458.     CancelEvents;
  459.     if FRegistered then
  460.     begin
  461.       IscFree( EventBuffer);
  462.       EventBuffer := nil;
  463.       IscFree( ResultBuffer);
  464.       ResultBuffer := nil;
  465.     end;
  466.     FRegistered := false;
  467.   end;
  468. end;
  469.  
  470. procedure TIBEventAlerter.UpdateResultBuffer( length: short; updated: PChar);
  471. var
  472.   i: integer;
  473. begin
  474.   for i := 0 to length-1 do
  475.     ResultBuffer[i] := updated[i];
  476. end;
  477.  
  478. end.
  479.