Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Programmieren allgemein (https://www.delphipraxis.net/40-programmieren-allgemein/)
-   -   Delphi TWinControl via TInterfacedObject via TInterfacedPersistent (https://www.delphipraxis.net/196609-twincontrol-via-tinterfacedobject-via-tinterfacedpersistent.html)

EWeiss 3. Jun 2018 22:20


TWinControl via TInterfacedObject via TInterfacedPersistent
 
Meine Classe

Aktuell..
Delphi-Quellcode:
TOnSessionStateEvent = procedure(Sender: TCustomAudioVolume; NewState: integer) of object;

TCustomAudioVolume = class(TWinControl)
  //
end;

TAudioVolume = class(TCustomAudioVolume, IAudioSessionEvents, IMMNotificationClient,
  IAudioSessionNotification, IAudioEndpointVolumeCallback)
begin
// .. Inhalt jetzt egal
end
Ich verwende keine WinControls daher frage ich mich was besser wäre das Aktuelle oder diese hier.

TInterfacedObject..
Delphi-Quellcode:
TOnSessionStateEvent = procedure(Sender: TObject; NewState: integer) of object;

TAudioVolume = class(TInterfacedObject, IAudioSessionEvents, IMMNotificationClient,
  IAudioSessionNotification, IAudioEndpointVolumeCallback)
TInterfacedPersistent..
Delphi-Quellcode:
TOnSessionStateEvent = procedure(Sender: TPersistent; NewState: integer) of object;

TAudioVolume = class(TInterfacedPersistent, IAudioSessionEvents, IMMNotificationClient,
  IAudioSessionNotification, IAudioEndpointVolumeCallback)
welche vor bzw.. Nachteile würden sich dadurch ergeben?
wie sieht das dann mit create, inherited und Konsorte aus.

Ja bei TObject, TPersistent müsste ich Create ändern nur welche gesamt Auswirkung hätten diese Änderungen und sind sie sinnvoll.

Delphi-Quellcode:
public
  constructor Create; reintroduce;
Delphi-Quellcode:
constructor TAudioVolume.Create();
inherited Create();

gruss

jaenicke 4. Jun 2018 00:41

AW: TWinControl via TInterfacedObject via TInterfacedPersistent
 
Wenn du TWinControl nicht brauchst, macht es auch keinen Sinn davon abzuleiten.

Wenn du Interfaces benutzt, dann am besten richtig mit Referenzzählung, sprich mit TInterfacedObject. Und auch so, dass du nur über Interfaces mit den Objekten arbeitest und nie über Objektreferenzen. Denn nur so kannst du ausschließen, dass die Referenzzählung das Objekt freigibt, du aber noch eine Objektreferenz offen hast oder umgekehrt.

TInterfacedPersistent macht dabei keinen Sinn, denn es schaltet die Referenzzählung aus bzw. implementiert diese nicht.

EWeiss 4. Jun 2018 08:55

AW: TWinControl via TInterfacedObject via TInterfacedPersistent
 
Ich dachte eigentlich es hätte irgendwelche Vorteile.
Bisher kann ich keinen erkennen bei deiner Aussage.

Eher Nachteile falls jemand diese Klasse später mit WinControls nutzen möchte.
Vielleicht dann lieber gar nichts ändern.

Danke.

gruss

EWeiss 6. Jun 2018 22:58

AW: TWinControl via TInterfacedObject via TInterfacedPersistent
 
Zitat:

TInterfacedPersistent macht dabei keinen Sinn, denn es schaltet die Referenzzählung aus bzw. implementiert diese nicht.
Aber nur damit funktioniert es gerade weil die Referenzzählung ausgeschaltet ist.
Wenn ich tmpAudioVolume.Free aufrufe dann kracht es mit invalid Pointer weil noch 3 refcounter offen sind. (TInterfacedObject)
Habe es nur testweise mal versucht.

gruss

Zacherl 7. Jun 2018 01:15

AW: TWinControl via TInterfacedObject via TInterfacedPersistent
 
Ich denke, ich würde hier komplett anders vorgehen. Deine Komponente
Delphi-Quellcode:
TAudioVolume
muss die Interfaces
Delphi-Quellcode:
IAudioSessionEvents, IMMNotificationClient, IAudioSessionNotification, IAudioEndpointVolumeCallback
doch gar nicht öffentlich implementieren. Du könntest dafür eine weitere (private) Klasse in der gleichen Unit anlegen und Befehle von
Delphi-Quellcode:
TAudioVolume
an eine Instanz davon weiterleiten bzw. die Callbacks von der Instanz zur
Delphi-Quellcode:
TAudioVolume
Komponente. Also eine Art Proxy sozusagen. Von dem Proxy erstellt du dann pro
Delphi-Quellcode:
TAudioVolume
Instanz ebenfalls eine Instanz und gibst diese im Destructor frei (
Delphi-Quellcode:
TAudioVolume
besitzt also die Proxy Instanz).
Delphi-Quellcode:
TAudioVolume
selbst kannst du dann z.B. einfach von
Delphi-Quellcode:
TObject
ableiten.

EWeiss 7. Jun 2018 01:24

AW: TWinControl via TInterfacedObject via TInterfacedPersistent
 
Zitat:

Du könntest dafür eine weitere (private) Klasse in der gleichen Unit anlegen und Befehle von TAudioVolume an eine Instanz davon weiterleiten bzw. die Callbacks von der Instanz zur TAudioVolume Komponente.
Das war die vorherige Version..
Wir sind davon abgekommen weil es nicht möglich war die Interface in OnSessionCreate und Konsorte mit PostMessage weiterzuleiten.
Es ist auch keine Komponente sondern nur eine Klasse TAudioVolume.

TObject habe ich versucht dann kracht es gewaltig invalid pointer ;) sagte ich schon.

gruss

TiGü 7. Jun 2018 08:29

AW: TWinControl via TInterfacedObject via TInterfacedPersistent
 
Zitat:

tmpAudioVolume.Free
Einfach nicht machen.
Extrahiere die wichtigsten Methoden von TAudioVolume in ein IAudioVolume und füge es deiner Klasse hinzu:

Delphi-Quellcode:
 TAudioVolume = class(TInterfacedObject, IAudioVolume, IAudioSessionEvents, IMMNotificationClient, IAudioSessionNotification, IAudioEndpointVolumeCallback)
Delphi-Quellcode:
//Erzeugen in der Anwendung dann nur über:

var
  tmpAudioVolume: IAudioVolume;
begin
  tmpAudioVolume := TAudioVolume.Create;
 ...
end;

EWeiss 7. Jun 2018 08:49

AW: TWinControl via TInterfacedObject via TInterfacedPersistent
 
Zitat:

Zitat von TiGü (Beitrag 1404142)
Zitat:

tmpAudioVolume.Free
Einfach nicht machen.
Extrahiere die wichtigsten Methoden von TAudioVolume in ein IAudioVolume und füge es deiner Klasse hinzu:

Delphi-Quellcode:
 TAudioVolume = class(TInterfacedObject, IAudioVolume, IAudioSessionEvents, IMMNotificationClient, IAudioSessionNotification, IAudioEndpointVolumeCallback)
Delphi-Quellcode:
//Erzeugen in der Anwendung dann nur über:

var
  tmpAudioVolume: IAudioVolume;
begin
  tmpAudioVolume := TAudioVolume.Create;
 ...
end;

Danke. Tigü

Ich denke werde es erst mal so belassen wie es ist.
Ich muss die Kontrolle darüber haben das Object selbst freigeben zu können.
Es war auch nur mal ein Test um zu sehen welche Auswirkungen das hat, haben könnte.

Ich denke auch das der Mixer jetzt fertig ist das mit den kleinen Problem was die Session angeht
liegt an der Audio Core selbst (Win7) die entläd diese manchmal nicht oder schickt kein Event.
Ist der Grund warum ich jetzt doch über die Prozesse gehen muss.

gruss

Zacherl 7. Jun 2018 12:53

AW: TWinControl via TInterfacedObject via TInterfacedPersistent
 
Zitat:

Zitat von EWeiss (Beitrag 1404105)
Wir sind davon abgekommen weil es nicht möglich war die Interface in OnSessionCreate und Konsorte mit PostMessage weiterzuleiten.

Denke mal du willst es jetzt eh nicht mehr ändern, aber mit "Weiterleiten" meinte ich im Grunde auch nur folgendes:
Delphi-Quellcode:
type
  TOnSessionCreate = procedure(...) of Object;

  TAudioVolume = class(TObject)
  strict private
    FProxy: TAudioVolumeProxy;
  strict protected
    FOnSessionCreate: TOnSessionCreate;
  public
    property OnSessionCreate: TOnSessionCreate read FOnSessionCreate write FOnSessionCreate;
  ...

  TAudioVolumeProxy = class(TInterfacedObject, IAudioVolume, IAudioSessionEvents, IMMNotificationClient, IAudioSessionNotification, IAudioEndpointVolumeCallback)
  strict prviate
    FOwner: TAudioVolume;
  strict private
    procedure OnSessionCreate(...);
  ...

constructor TAudioVolume.Create;
begin
  inherited Create;
  FProxy := TAudioVolumeProxy.Create(Self);
end;

destructor TAudioVolume.Free;
begin
  FProxy.Free;
  inherited Destroy;
end;

procedure TAudioVolumeProxy.OnSessionCreate(...)
begin
  if Assigned(FOwner.FOnSessionCreate) then
  begin
    FOwner.FOnSessionCreate(...);
  end;
end;
Delphi-Quellcode:
TAudioVolume
selbst hat hierbei praktisch gar keine eigene Funktionalität, sondern dient nur als Schnitstelle (Proxy sollte man vermutlich in Implementation oder sowas umbenennen).

EWeiss 7. Jun 2018 13:06

AW: TWinControl via TInterfacedObject via TInterfacedPersistent
 
Zitat:

Denke mal du willst es jetzt eh nicht mehr ändern, aber mit "Weiterleiten" meinte ich im Grunde auch nur folgendes:
Versuchen kann man es ja mal ;)

gruss

Schokohase 7. Jun 2018 13:21

AW: TWinControl via TInterfacedObject via TInterfacedPersistent
 
@Zacherl

Mit der Anleitung kann man allerdings nur Schiffbruch erleiden, oder? Wo ist denn jetzt der Unterschied zu der vorherigen Situation?

- TAudioVolume erzeugen
- Über TAudioVolumeProxy eine Interface-Instanz weiterreichen
- TAudioVolume freigeben
- Es knallt, wenn die Interface-Instanz auf nil gesetzt wird.

Dieses Verhalten hatte der TE schon selber gebaut und auch noch mit viel weniger Code.

Zitat:

Zitat von EWeiss (Beitrag 1404100)
Wenn ich tmpAudioVolume.Free aufrufe dann kracht es mit invalid Pointer weil noch 3 refcounter offen sind. (TInterfacedObject)
Habe es nur testweise mal versucht.


EWeiss 7. Jun 2018 13:39

AW: TWinControl via TInterfacedObject via TInterfacedPersistent
 
Um was es mir ging..
Ist die Unterschiede zu erfahren was ist besser, schneller bzw. Korrekter in der Ausführung.
Wenn ich keine WinControls verwende macht es eigentlich keinen sinn darauf abzuleiten genauso wenig wie auf TComponent.

Es stört mich nicht weiter da es funktioniert aber einen sinn ergibt das nicht wirklich.

Ich könnte hier einfach tmpAudioVolume.Free mit tmpAudioVolume := Nil ersetzen.
Aber es hängt noch mehr davon ab denn in Destroy müssen viele Dinge freigegeben werden.
Deshalb wäre das keine Lösung.

gruss

freimatz 7. Jun 2018 14:44

AW: TWinControl via TInterfacedObject via TInterfacedPersistent
 
Zitat:

Zitat von EWeiss (Beitrag 1404185)
Wenn ich keine WinControls verwende macht es eigentlich keinen sinn darauf abzuleiten genauso wenig wie auf TComponent.

Seh ich auch so.

Zacherl 7. Jun 2018 23:14

AW: TWinControl via TInterfacedObject via TInterfacedPersistent
 
Zitat:

Zitat von Schokohase (Beitrag 1404183)
@Zacherl

Mit der Anleitung kann man allerdings nur Schiffbruch erleiden, oder? Wo ist denn jetzt der Unterschied zu der vorherigen Situation?

- TAudioVolume erzeugen
- Über TAudioVolumeProxy eine Interface-Instanz weiterreichen
- TAudioVolume freigeben
- Es knallt, wenn die Interface-Instanz auf nil gesetzt wird.

Verstehe ich nicht. Der Proxy soll innerhalb von
Delphi-Quellcode:
TAudioVolume
als Objektreferenz gehalten werden, nicht als Interface. Erzeugen im Constructor und Freigeben im Destructor.
Delphi-Quellcode:
TAudioVolume
selbst implementiert dann gar kein Interface mehr und braucht auch nicht von
Delphi-Quellcode:
TInterfacedObject/TInterfacedPersistent
abzuleiten, sondern nur vom guten alten
Delphi-Quellcode:
TObject
.

Was intern mit dem Proxy passiert ist dadurch streng reguliert (da die Proxy Instanz privat ist, nie als Interface angesprochen wird und Erzeugung und Freigabe von
Delphi-Quellcode:
TAudioVolume
geregelt wird.

EWeiss 8. Jun 2018 02:38

AW: TWinControl via TInterfacedObject via TInterfacedPersistent
 
funktioniert leider nicht und ich musste einiges umbauen.

FProxy.Free; invalid Pointer
Ich lasse es jetzt wie es ist.

gruss

TiGü 8. Jun 2018 07:53

AW: TWinControl via TInterfacedObject via TInterfacedPersistent
 
Zitat:

Zitat von Zacherl (Beitrag 1404219)
Zitat:

Zitat von Schokohase (Beitrag 1404183)
@Zacherl

Mit der Anleitung kann man allerdings nur Schiffbruch erleiden, oder? Wo ist denn jetzt der Unterschied zu der vorherigen Situation?

- TAudioVolume erzeugen
- Über TAudioVolumeProxy eine Interface-Instanz weiterreichen
- TAudioVolume freigeben
- Es knallt, wenn die Interface-Instanz auf nil gesetzt wird.

Verstehe ich nicht. Der Proxy soll innerhalb von
Delphi-Quellcode:
TAudioVolume
als Objektreferenz gehalten werden, nicht als Interface. Erzeugen im Constructor und Freigeben im Destructor.
Delphi-Quellcode:
TAudioVolume
selbst implementiert dann gar kein Interface mehr und braucht auch nicht von
Delphi-Quellcode:
TInterfacedObject/TInterfacedPersistent
abzuleiten, sondern nur vom guten alten
Delphi-Quellcode:
TObject
.

Was intern mit dem Proxy passiert ist dadurch streng reguliert (da die Proxy Instanz privat ist, nie als Interface angesprochen wird und Erzeugung und Freigabe von
Delphi-Quellcode:
TAudioVolume
geregelt wird.

Aber auch nur, wenn Emil den Proxy von TInterfacedPersistent (oder System.Generics.Defaults.TSingletonImplementation) ableitet, ansonsten hat er wie im Post darüber zu lesen für ihn unverständliche Invalid Pointer Zugriffe und er resigniert.

Zacherl 8. Jun 2018 12:50

AW: TWinControl via TInterfacedObject via TInterfacedPersistent
 
Zitat:

Zitat von TiGü (Beitrag 1404228)
Aber auch nur, wenn Emil den Proxy von TInterfacedPersistent (oder System.Generics.Defaults.TSingletonImplementation) ableitet, ansonsten hat er wie im Post darüber zu lesen für ihn unverständliche Invalid Pointer Zugriffe und er resigniert.

Stehe irgendwie auf dem Schlauch grade. Wenn er den Proxy als Klasseninstanz erzeugt und die Variable, in der er die Instanz speichert, auch vom Typ der Klasse (nicht des Interfaces) ist, dann sollte der RefCount doch eigentlich garantiert 0 sein. Außer natürlich, es werden an späterer Stelle doch noch (wie auch immer)
Delphi-Quellcode:
AddRef
Aufrufe generiert ... aber das sollte dann ja eigentlich nicht am Interface bzw.
Delphi-Quellcode:
TInterfacedObject
selbst liegen, sondern ein Programmierfehler sein, oder liege ich hier falsch?

Schokohase 8. Jun 2018 13:04

AW: TWinControl via TInterfacedObject via TInterfacedPersistent
 
Hast du dir schon mal überlegt, warum dieser Proxy-Typ
Delphi-Quellcode:
TAudioVolumeProxy = class( TInterfacedObject,
  IAudioVolume,
  IAudioSessionEvents,
  IMMNotificationClient,
  IAudioSessionNotification,
  IAudioEndpointVolumeCallback)
diese Interfacs in der Deklaration aufweist?

Evtl. weil die verwendet werden um sich z.B. an einem
Delphi-Quellcode:
IAudioSessionControl
per
Delphi-Quellcode:
HRESULT RegisterAudioSessionNotification( [in] IAudioSessionEvents *NewNotifications );
anzumelden, weil diese Interfaces ansonsten nutzlos wären wenn man die gar nicht verwendet?

Zacherl 8. Jun 2018 13:12

AW: TWinControl via TInterfacedObject via TInterfacedPersistent
 
Sehe ich kein Problem, sofern man ordnungsgemäß auch wieder
Delphi-Quellcode:
UnregisterAudioSessionNotification
verwendet. Ich meine "irgendwo" müssen die Referenzen doch geleaked werden. Die entstehen ja nicht von alleine. Dann einfach die Referenzzählung zu deaktivieren, indem man
Delphi-Quellcode:
TInterfacedPersistence
verwendet, kann doch eigentlich nicht Sinn der Sache sein.

Schokohase 8. Jun 2018 13:20

AW: TWinControl via TInterfacedObject via TInterfacedPersistent
 
Wenn du dir selber aber die Interface Referenz nicht merkst
(dein Beispiel-Code)
Delphi-Quellcode:
TAudioVolume = class(TObject)
  strict private
    FProxy: TAudioVolumeProxy;
  strict protected
    FOnSessionCreate: TOnSessionCreate;
  public
    property OnSessionCreate: TOnSessionCreate read FOnSessionCreate write FOnSessionCreate;
  ...
und du gibst eine Interface-Referenz davon heraus, dann tickt ab da die RefCount-Bombe und die kann zu jedem Zeitpunkt platzen.
Delphi-Quellcode:
begin
  RegisterAudioSessionNotification(FProxy);
  UnregisterAudioSessionNotification(FProxy);
end; { bumm, wenn diese Methode verlassen wird, denn wird FProxy zerstört }

TiGü 8. Jun 2018 13:50

AW: TWinControl via TInterfacedObject via TInterfacedPersistent
 
Zitat:

Zitat von Zacherl (Beitrag 1404268)
...sondern ein Programmierfehler sein, oder liege ich hier falsch?

Das kann man hier natürlich nicht ausschließen. :wink:

EWeiss 8. Jun 2018 13:55

AW: TWinControl via TInterfacedObject via TInterfacedPersistent
 
Delphi-Quellcode:
begin
   RegisterAudioSessionNotification(FProxy);
   UnregisterAudioSessionNotification(FProxy);
end; { bumm, wenn diese Methode verlassen wird, denn wird FProxy zerstört }
Genau so habe ich es umgesetzt also self mit FProxy ersetzt.
Dann die ganzen OnEvents.. nach TAudioVolumeProxy portiert.

Da die ganzen Events strict private sind konnte ich von außen die Events nicht mehr zuweisen.
Danach habe ich es dann gelassen ;)

Alle zugriffe in den Funktionen OnEvents habe ich dann mit FOwner angesprochen.

gruss

Zacherl 8. Jun 2018 15:05

AW: TWinControl via TInterfacedObject via TInterfacedPersistent
 
Zitat:

Zitat von Schokohase (Beitrag 1404274)
Wenn du dir selber aber die Interface Referenz nicht merkst [...] und du gibst eine Interface-Referenz davon heraus, dann tickt ab da die RefCount-Bombe und die kann zu jedem Zeitpunkt platzen.

Stimmt, das hatte ich nicht bedacht :?

Generell sollte es aber doch trotzdem möglich sein (wenn man sich dann eben 1 Interface-Referenz merkt), ein Leaken zu vermeiden, wenn man entsprechende
Delphi-Quellcode:
AddRef
und
Delphi-Quellcode:
Release
Aufrufe immer ausschließlich in Paaren ausführt. Ich vermute, dass hier einfach irgendwo ein paar versteckte
Delphi-Quellcode:
AddRef
Aufrufe stattfinden (z.B. innerhalb eines API Calls) zu denen es kein
Delphi-Quellcode:
Release
gibt.

Zitat:

Zitat von EWeiss (Beitrag 1404286)
[Genau so habe ich es umgesetzt also self mit FProxy ersetzt.
Dann die ganzen OnEvents.. nach TAudioVolumeProxy portiert.

Da die ganzen Events strict private sind konnte ich von außen die Events nicht mehr zuweisen.
Danach habe ich es dann gelassen ;)

Jaa, also das
Delphi-Quellcode:
strict
kannst du auch weglassen, aber muss zugeben, dass ich die Komponente auch nicht in ihrer Gesamtheit angeschaut habe, weshalb ich nicht weiß, ob es den Aufwand wert ist.

Was du allerdings mal machen könntest, ist
Delphi-Quellcode:
AddRef
und
Delphi-Quellcode:
Release
zu überschreiben und Breakpoints zu setzen. Dann kannst du vielleicht nachvollziehen, wo es zu Aufrufen von
Delphi-Quellcode:
AddRef
kommt, ohne dass danach
Delphi-Quellcode:
Release
aufgerufen wird.

Schokohase 8. Jun 2018 16:32

AW: TWinControl via TInterfacedObject via TInterfacedPersistent
 
Hier mal ein Beispiel, wie so etwas gehen kann
Delphi-Quellcode:
type
  IFoo = interface ['{a895c8b4-db8c-40e9-a907-a0a4b01ff773}']
    procedure Bar;
  end;

  TFooImplementer = class(TObject,IFoo)
  private type
    IFooProxy = interface(IFoo) ['{20920822-173c-4e67-8f7b-8959366cc6b2}']
      procedure Release;
    end;
  strict private
    FProxy: IFooProxy;
    function GetFoo: IFoo;
  strict protected
    property Foo: IFoo read GetFoo implements IFoo;
  public
    constructor Create;
    destructor Destroy; override;

    procedure Bar; // Die Methode macht die echte Arbeit
  end;

  TInterfacedFooProxy = class(TInterfacedObject, IFoo, TFooImplementer.IFooProxy)
  strict private
    FFoo: TFooImplementer;
  strict protected
    procedure Bar;
  strict protected
    procedure Release;
  public
    constructor Create(AFoo: TFooImplementer );
    destructor Destroy; override;
  end;

  { TFooImplementer }

procedure TFooImplementer.Bar;
begin
  WriteLn('*** TFooImplementer.Bar ***');
end;

constructor TFooImplementer.Create;
begin
  inherited Create;
  FProxy := TInterfacedFooProxy.Create(Self);
end;

destructor TFooImplementer.Destroy;
begin
  WriteLn('TFooImplementer.Destroy');
  FProxy.Release;
  inherited;
end;

function TFooImplementer.GetFoo: IFoo;
begin
  Result := FProxy;
end;

{ TInterfacedFooProxy }

procedure TInterfacedFooProxy.Bar;
begin
  WriteLn('TInterfacedFooProxy.Bar');
  if Assigned(FFoo)
  then
    FFoo.Bar;
end;

constructor TInterfacedFooProxy.Create(AFoo: TFooImplementer);
begin
  inherited Create;
  FFoo := AFoo;
end;

destructor TInterfacedFooProxy.Destroy;
begin
  WriteLn('TInterfacedFooProxy.Destroy');
  inherited;
end;

procedure TInterfacedFooProxy.Release;
begin
  FFoo := nil;
end;
und ein kleiner Test dazu
Delphi-Quellcode:
procedure Main;
var
  fooObj : TFooImplementer;
  fooIntf: IFoo;
begin
  fooObj := TFooImplementer.Create;
  try
    fooIntf := fooObj;
    // direkter Aufruf
    fooObj.Bar;
    // indirekter Aufruf durch IFoo
    fooIntf.Bar;
  finally
    fooObj.Free;
    fooObj := nil;
  end;

  // indirekter Aufruf durch IFoo, der aber ins Leere geht
  fooIntf.Bar;
  fooIntf := nil;
end;
und was dabei herauskommt
Code:
*** TFooImplementer.Bar ***
TInterfacedFooProxy.Bar
*** TFooImplementer.Bar ***
TFooImplementer.Destroy
TInterfacedFooProxy.Bar
TInterfacedFooProxy.Destroy


Alle Zeitangaben in WEZ +1. Es ist jetzt 11:10 Uhr.

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz