home *** CD-ROM | disk | FTP | other *** search
/ Delphi Anthology / aDELPHI.iso / Runimage / Delphi50 / Source / Samples / ibctrls.pas < prev    next >
Pascal/Delphi Source File  |  1999-08-11  |  14KB  |  477 lines

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