Announcement

Collapse
No announcement yet.

Callback Aufrufe durch den Server auslösen ?!?!

Collapse
X
  • Filter
  • Time
  • Show
Clear All
new posts

  • Callback Aufrufe durch den Server auslösen ?!?!

    Hallo,

    zunächst noch mal ein dickes Lob für "COM/DCOM mit Delphi". Dieses Buch hat mir viele neue Inspirationen zur Herangehensweise beim Entwurf mehrschichtiger Anwendungen gegeben.

    Zur Prozesskommunikation für eine mehrbenutzerfähigeAnwendung habe ich mir den Callback - Manager aus dem Buch zur Vorlage genommen und kann so durch Broadcast alle aktiven Clients nahezu in Echtzeit über entsprechende Veränderungen informieren. Neben der Funktion der Nachrichtendrehscheibe beherbergt der Sever auch noch diverse Geschäftsobjekte und Verbindungen zur Datenbank. Wenn mann das Konzept der Thin Clients konsquent verfolgt, ergibt sich damit, dass durchaus auch im Server Ereignisse ausgelöst werden, die für die Präsentationsschicht (Clients) von Interesse sind. Bei dem Versuch, die Instanz des Callback Managers mit der Broadcast Methode anzusprechen erhielt ich die folgende EOleException.:

    "Ein ausgehender Aufruf kann nicht ausgeführt werden, da die Anwendung einen Eingabe-synchronisierten Aufruf weiterleitet."

    Irgendwie leuchtet mir das ja ein, aber wie kann ich mein Problem lösen und meine Clients auch mit Ereignissen aus dem Server versorgen ? :-((

    Gruß, Holger.

  • #2
    Hallo,

    für dieses Problem gibt es drei Lösungswege. Der für den Entwickler einfachste besteht im Umstieg auf Windows 2000, da mit den <b>COM+ Events</b> ein abgekoppelter Benachrichtigungs-Mechanismus in Form eines Auftragdienstes vom Betriebssystem angeboten wird. In der vorletzten Ausgabe von DER ENTWICKLER war dazu ein Beitrag von mir (inkl. Delphi-Beispielprojekt).

    Kommt ein Wechsel auf Windows 2000 nicht in Frage, bleiben noch 2 Alternativen übrig: <br>
    1. Die COM-Objekte werden nicht in einem <b>STA</b> (Single Threaded Apartment), sondern in einem <b>MTA</b> ausgeführt. In diesem Fall entfällt die Synchronisation über das versteckte Fenster. <br>
    2. Jeder Client spaltet sein Benachrichtigungs-Objekt in einem eigenen Thread ab, so das dieses Objekt in einem eigenen STA ausgeführt wird.

    Die 2. Alternative könnte so aussehen: Da die Server-Instanz aus diesem zweiten Thread und somit aus einem fremden STA angefordert wird, darf der primäre Thread und damit das STA0 diesen Interface-Zeiger nicht unbehandelt verwenden. Statt dessen muss der Sink-Thread den Interface-Zeiger marshalen, wobei ein Semaphore-Sperrobjekt die Abstimmung zwischen primären Thread und Sink-Thread übernimmt.
    <pre>
    procedure TFormClient2.FormCreate(Sender: TObject);
    begin
    aSinkSemaphore := CreateSemaphore(nil, 0, 1, nil);
    FServer := CoFileSearchObj.Create;
    IncSinkObjCounter;
    FSinkThread := TSinkThread.Create;
    Application.ProcessMessages;
    if WaitForSingleObject(aSinkSemaphore, INFINITE) = WAIT_OBJECT_0 then
    begin
    OleCheck(CoGetInterfaceAndReleaseStream(IStream(pS inkStream),
    IFileSearchFound, FSinkObj));
    with TimerSink do
    begin
    Interval := 10;
    Enabled := True;
    end;
    end;
    end;
    </pre>
    Da der neue Thread ein eigenes STA einrichtet, muss er der COM-Forderung nach einer eigenen Message-Loop Rechnung tragen:
    <pre>
    procedure TSinkThread.Execute;
    var
    aMsg : TMsg;
    FSinkObj : IFileSearchFound;
    begin
    // neues STA-Apartment initialisieren
    OleCheck(CoInitialize(nil));
    try
    dwSinkThread := GetCurrentThreadId;
    // Instanz des Sink-Objekts erzeugen
    FSinkObj := TFileSearchFoundObj.Create;
    IncSinkObjCounter;
    // Interface-Zeiger des Sink-Objekts für primären Thread vorbereiten
    OleCheck(CoMarshalInterThreadInterfaceInStream(IFi leSearchFound,
    FSinkObj, IStream(pSinkStream)));
    // Semaphore-Sperrobjekt signalisieren
    ReleaseSemaphore(aSinkSemaphore, 1, nil);
    // COM-Forderung: STA-Thread muß Message Loop implementieren
    while GetMessage(aMsg, 0, 0, 0) do
    begin
    TranslateMessage(aMsg);
    DispatchMessage(aMsg);
    if Terminated then
    Break;
    end;
    FSinkObj := nil;
    finally
    CoUninitialize;
    end;
    end;
    </pre>
    Allerdings muss nun der Client dafür sorgen, dass die Message Loop zum Programmende auch wieder verlassen wird. Der <b>Break</b>-Aufruf reicht dazu nicht aus, da diese Zeile erst dann geprüft wird, wenn der Thread eine Botschaft aus der eigenen Botschaftswarteschlange ausgelesen hat und diese auch verarbeitet. Aus diesem Grund muß der Client einen zusätzlichen Aufruf von PostThreadMessage(dwSinkThread, WM_QUIT, 0, 0) vor dem Aufruf von Terminate einschachteln. Eine geeignete Stelle dafür ist die Ereignisbehandlungsmethode für das CloseQuery-Ereignis des Client-Formulars.
    <pre>procedure TFormClient2.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
    begin
    TimerSink.Enabled := False;
    PostThreadMessage(dwSinkThread, WM_QUIT, 0, 0);
    Application.ProcessMessages;
    FSinkThread.Terminate;
    FSinkObj := nil;
    FServer := nil;
    end;
    </pre>
    Nach diesen Vorbereitung kann der Client den Server aufrufen und einen Interface-Zeiger auf das eigene Benachrichtigungs-Objekt übergeben:
    <pre>
    procedure TFormClient.Button1Click(Sender: TObject);
    begin
    FServer.StartSearch('C:\Ablage', 'Test.pas', FSinkObj, 1);
    end;
    </pre&gt

    Comment


    • #3
      Hallo Andreas

      mal wieder vielen Dank.

      Der Tip mit MTA war Goldrichtig

      Helmut Hellwi

      Comment

      Working...
      X