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