Wim ten Brink
asked on
[COM] System-wide singleton COM component...
Okay, simple question. I need sample code of a COM/ActiveX systemwide singleton component. In other words, it is created only once and applications that call this component will get access to this component. Thus, a component that can share data between many applications.
"This is impossible" is worth 0 points. A working example will receive 500 points with an A grade. Anything else will only be awarded if no working example is provided within the next two days. (I will close this Q within 2 days anyway.)
Yes guys (and girls), this is a challenge! In two days I will have time to solve it myself. But this might save me some time. ;-)
"This is impossible" is worth 0 points. A working example will receive 500 points with an A grade. Anything else will only be awarded if no working example is provided within the next two days. (I will close this Q within 2 days anyway.)
Yes guys (and girls), this is a challenge! In two days I will have time to solve it myself. But this might save me some time. ;-)
ASKER
Ehm, it is a bit more complex than this... I forgot to add something, I noticed... The COM component must send an event to ALL connected applications under certain conditions. Thus, application A executes a method, and the method fires an event on A, B, C, ... for all attached applications...
In other words, the system must be data-aware...
In other words, the system must be data-aware...
if you are going to write data-aware application, you should use Multithreaded COM. This is the better solution...
There is a class TRemoteDataModule. Use it to define your COM interface.
There is a class TRemoteDataModule. Use it to define your COM interface.
ASKER
Ehm, I found what I was looking for already.
http://www.blong.com/Conferences/IConUK2000/DelphiMoreAutomation/More%20Automation%20In%20Delphi.htm offers more information about the techniques I need. Too bad he dared to call his COM component TApplication in the client applications. A sure way to create lots of conflicts... And another nasty trick is that this source code is polling the latest information all the time.
And by data-aware I just meant that all clients must respond to changes in the services. Nothing database-related and nothing complicated either. Still, not wanting to waste 500 good points, I'll reward them to whomever can find similar examples. :-)
http://www.blong.com/Conferences/IConUK2000/DelphiMoreAutomation/More%20Automation%20In%20Delphi.htm offers more information about the techniques I need. Too bad he dared to call his COM component TApplication in the client applications. A sure way to create lots of conflicts... And another nasty trick is that this source code is polling the latest information all the time.
And by data-aware I just meant that all clients must respond to changes in the services. Nothing database-related and nothing complicated either. Still, not wanting to waste 500 good points, I'll reward them to whomever can find similar examples. :-)
a simple observer pattern :)
when the com obj is acquired a method should be called like 'COMObj.RegisterObserver(R eferenceTo Me)' which would add a reference of somesort (a window handle, event handle, ...) to a list
maybe there's a way to do this automaticaly like with _AddRef in interfaces
then when any other methods that require notifications get called the comobject would traverse the list and notify the Observers
I just might create an example .. sounds fun .. but time is not on my side :)
when the com obj is acquired a method should be called like 'COMObj.RegisterObserver(R
maybe there's a way to do this automaticaly like with _AddRef in interfaces
then when any other methods that require notifications get called the comobject would traverse the list and notify the Observers
I just might create an example .. sounds fun .. but time is not on my side :)
ASKER
Time isn't on my side either... :-)
you might have seen this already, about com server, client
http://www.techvanguards.com/stepbystep/comdelphi/
com server with events to clients is not so easy, never did get mine to work right
http://www.techvanguards.com/stepbystep/comdelphi/
com server with events to clients is not so easy, never did get mine to work right
Not sure how this code might help you (or confuse you) , I used the Delphi Automation Object wizard to create the TLB file and the ComSerInCom file, I added code to both files. I hope you are used to working with the Delphi Automation Object wizard, or you will need to mess with it and get to see how it works, no easy task, it helps if you keep all of your results and parameters as Interfaces or OLEVarient types, , I.E. use BSTR for all string types
What this server does is allow info sharing between clients, it keeps an Array of the TNameRec with FirstNames and lastNames, so each client can connect to server and add to the array, or read and write to the array. To read or write to the array, you set the IIntFacEvent5.Index to the index of the Array you want, and then read or set the FirstName and LastName of the IIntFacEvent5.
unit ComSerIn_TLB;
{$TYPEDADDRESS OFF} // Unit must be compiled without type-checked pointers.
interface
uses Windows, ActiveX, Classes, Graphics, OleServer, OleCtrls, StdVCL;
const
// TypeLibrary Major and minor versions
ComSerInMajorVersion = 1;
ComSerInMinorVersion = 0;
LIBID_ComSerIn: TGUID = '{F7987220-F81E-11D8-B122- AC2DC9C498 50}';
IID_IIntFacEvent5: TGUID = '{F7987221-F81E-11D8-B122- AC2DC9C498 50}';
CLASS_IntFacEvent5: TGUID = '{F7987223-F81E-11D8-B122- AC2DC9C498 50}';
IID_ISerEvents3: TGUID = '{F7987225-F81E-11D8-B122- AC2DC9C498 50}';
type
IIntFacEvent5 = interface;
IIntFacEvent5Disp = dispinterface;
ISerEvents3 = interface;
ISerEvents3Disp = dispinterface;
IntFacEvent5 = IIntFacEvent5;
IIntFacEvent5 = interface(IDispatch)
['{F7987221-F81E-11D8-B122 -AC2DC9C49 850}']
function Get_Index: Integer; safecall;
procedure Set_Index(Value: Integer); safecall;
function Get_High: Integer; safecall;
procedure Add(const FirstName1: WideString; const LastName1: WideString); safecall;
function Get_LastName: WideString; safecall;
procedure Set_LastName(const Value: WideString); safecall;
function Get_FirstName: WideString; safecall;
procedure Set_FirstName(const Value: WideString); safecall;
procedure setEvents(const Callback: ISerEvents3; const Name: WideString); safecall;
function Get_Connect_ID: Integer; safecall;
function Get_NoName: WordBool; safecall;
procedure Set_NoName(Value: WordBool); safecall;
property Index: Integer read Get_Index write Set_Index;
property High: Integer read Get_High;
property LastName: WideString read Get_LastName write Set_LastName;
property FirstName: WideString read Get_FirstName write Set_FirstName;
property Connect_ID: Integer read Get_Connect_ID;
property NoName: WordBool read Get_NoName write Set_NoName;
end;
IIntFacEvent5Disp = dispinterface
['{F7987221-F81E-11D8-B122 -AC2DC9C49 850}']
property Index: Integer dispid 1;
property High: Integer readonly dispid 2;
procedure Add(const FirstName1: WideString; const LastName1: WideString); dispid 3;
property LastName: WideString dispid 4;
property FirstName: WideString dispid 5;
procedure setEvents(const Callback: ISerEvents3; const Name: WideString); dispid 6;
property Connect_ID: Integer readonly dispid 8;
property NoName: WordBool dispid 7;
end;
ISerEvents3 = interface(IDispatch)
['{F7987225-F81E-11D8-B122 -AC2DC9C49 850}']
procedure OnChange(NewIndex: Integer); safecall;
procedure NewName(const nName: WideString); safecall;
procedure NewHigh(currentHigh: Integer); safecall;
end;
ISerEvents3Disp = dispinterface
['{F7987225-F81E-11D8-B122 -AC2DC9C49 850}']
procedure OnChange(NewIndex: Integer); dispid 1;
procedure NewName(const nName: WideString); dispid 2;
procedure NewHigh(currentHigh: Integer); dispid 4;
end;
CoIntFacEvent5 = class
class function Create: IIntFacEvent5;
// The function below was ADDED to this TLB file AFTER the automation object wizard creation
class function Create_Name(const Callback: ISerEvents3; const Name: WideString): IIntFacEvent5;
class function CreateRemote(const MachineName: string): IIntFacEvent5;
end;
implementation
uses ComObj;
class function CoIntFacEvent5.Create: IIntFacEvent5;
begin
Result := CreateComObject(CLASS_IntF acEvent5) as IIntFacEvent5;
end;
class function CoIntFacEvent5.Create_Name (const Callback: ISerEvents3; const Name: WideString): IIntFacEvent5;
begin
// this function adds the setEvents, so the ISerEvents3 can be placed in the new IIntFacEvent5
Result := CreateComObject(CLASS_IntF acEvent5) as IIntFacEvent5;
if (CallBack <> nil) then
Result.setEvents(Callback, Name);
end;
class function CoIntFacEvent5.CreateRemot e(const MachineName: string): IIntFacEvent5;
begin
Result := CreateRemoteComObject(Mach ineName, CLASS_IntFacEvent5) as IIntFacEvent5;
end;
end.
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
unit ComSerInCom;
interface
uses
ComObj, Classes, ActiveX, ComSerIn_TLB, StdVcl;
type
TNameRec = record
// record in array to share amoung clients
UserName, FirstName, LastName: WideString;
ID, AccNum: Integer;
end;
TConect1 = class
// This is a class that just stores Each IntFacEvent5 connections data
public
FUserName: WideString;
FID: Integer;
FNoName: WordBool;
FCallBack: ISerEvents3;
destructor Destroy; override;
end;
TConectList1 = class
// this is a class that does a TList to store all of the TConect1
private
FConList: TList;
public
constructor Create;
destructor Destroy; override;
procedure setEvents(const Callback: ISerEvents3; const Name: WideString; ID: Integer);
procedure SetNoName(conID: Integer; NoName: WordBool);
function GetNoName(conID: Integer): WordBool;
procedure StopEvents(conID: Integer);
procedure OnChange(NewIndex: Integer);
procedure NewName(const nName: WideString);
procedure NewHigh(currentHigh: Integer);
end;
TIntFacEvent5 = class(TAutoObject, IIntFacEvent5)
// this is the Interface Class that is used to add and share information from the Array aryNameRec
protected
{ Protected declarations }
FconID: Integer;
function Get_FirstName: WideString; safecall;
function Get_High: Integer; safecall;
function Get_Index: Integer; safecall;
function Get_LastName: WideString; safecall;
procedure Add(const FirstName1, LastName1: WideString); safecall;
procedure Set_FirstName(const Value: WideString); safecall;
procedure Set_Index(Value: Integer); safecall;
procedure Set_LastName(const Value: WideString); safecall;
procedure setEvents(const Callback: ISerEvents3; const Name: WideString);
safecall;
//procedure StopEvents; safecall;
function Get_Connect_ID: Integer; safecall;
function Get_NoName: WordBool; safecall;
procedure Set_NoName(Value: WordBool); safecall;
public
{ Public declarations }
procedure Initialize; override;
destructor Destroy; override;
function getCont: TConectList1;
end;
implementation
uses ComServ, SysUtils, ComSerIn1;
var
aryNameRec: Array of TNameRec;
ConectNum: Integer = 0; // number of connections to server
AccNum: Integer = 406000;
aHigh: Integer = -1; // current High of aryNameRec
curIndex: Integer = -1; // current Index for aryNameRec access
IDNum: Integer = 100;
ContLst: TConectList1;
// I think it would be better to have created ContLst in the initialization and free in Finalization
destructor TConect1.Destroy;
begin
FCallBack := nil; // free interface reference
inherited Destroy;
end;
// TConectList1
constructor TConectList1.Create;
begin
FConList := TList.Create;
end;
destructor TConectList1.Destroy;
var
i: Integer;
Con1: TConect1;
begin
for i := 0 to FConList.Count - 1 do
begin
Con1 := TConect1(FConList[i]);
FreeAndNil(Con1);
end;
inherited Destroy;
end;
procedure TConectList1.setEvents(con st Callback: ISerEvents3; const Name: WideString; ID: Integer);
var
Con1: TConect1;
begin
Con1 := TConect1.Create;
Con1.FCallBack := Callback;
Con1.FID := ID{FconID};
Con1.FUserName := Name;
Con1.FNoName := False;
FConList.Add(Con1);
end;
procedure TConectList1.StopEvents(co nID: Integer);
var
i: Integer;
Con1: TConect1;
begin
for i := 0 to FConList.Count - 1 do
begin
Con1 := TConect1(FConList[i]);
if Con1.FID = conID then
begin
FreeAndNil(Con1);
FConList.Delete(i);
end;
end;
end;
procedure TConectList1.SetNoName(con ID: Integer; NoName: WordBool);
var
i: Integer;
begin
for i := 0 to FConList.Count - 1 do
if TConect1(FConList[i]).FID = conID then
begin
TConect1(FConList[i]).FNoN ame := NoName;
Break;
end;
end;
function TConectList1.GetNoName(con ID: Integer): WordBool;
var
i: Integer;
begin
Result := False;
for i := 0 to FConList.Count - 1 do
if TConect1(FConList[i]).FID = conID then
begin
Result := TConect1(FConList[i]).FNoN ame;
Break;
end;
end;
// the 3 procedures below are called for the ISerEvents3 events
procedure TConectList1.OnChange(NewI ndex: Integer);
var
i: Integer;
begin
for i := 0 to FConList.Count - 1 do
TConect1(FConList[i]).FCal lBack.OnCh ange(NewIn dex);
end;
procedure TConectList1.NewName(const nName: WideString);
var
i: Integer;
begin
for i := 0 to FConList.Count - 1 do
if not TConect1(FConList[i]).FNoN ame then
TConect1(FConList[i]).FCal lBack.NewN ame(nName) ;
end;
procedure TConectList1.NewHigh(curre ntHigh: Integer);
var
i: Integer;
begin
for i := 0 to FConList.Count - 1 do
TConect1(FConList[i]).FCal lBack.NewH igh(curren tHigh);
end;
// TIntFacEvent5 interface
procedure TIntFacEvent5.Initialize;
begin
inherited Initialize;
Inc(ConectNum);
FconID := IDNum;
Inc(IDNum);
Form1.Label2.Caption := IntToStr(ConectNum)+' Conections';
end;
destructor TIntFacEvent5.Destroy;
begin
Dec(ConectNum);
getCont.StopEvents(FconID) ;
Form1.Label2.Caption := IntToStr(ConectNum)+' Conections';
inherited Destroy;
end;
function TIntFacEvent5.getCont: TConectList1;
begin
if ContLst = nil then
ContLst := TConectList1.Create;
Result := ContLst;
end;
function TIntFacEvent5.Get_FirstNam e: WideString;
begin
if curIndex > -1 then
Result := aryNameRec[curIndex].First Name
else
Result := '';
end;
function TIntFacEvent5.Get_High: Integer;
begin
Result := aHigh;
end;
function TIntFacEvent5.Get_Index: Integer;
begin
Result := curIndex;
end;
function TIntFacEvent5.Get_LastName : WideString;
begin
if curIndex > -1 then
Result := aryNameRec[curIndex].LastN ame
else
Result := '';
end;
procedure TIntFacEvent5.Add(const FirstName1, LastName1: WideString);
begin
SetLength(aryNameRec, Length(aryNameRec)+1);
aHigh := High(aryNameRec);
aryNameRec[aHigh].LastName := LastName1;
aryNameRec[aHigh].FirstNam e := FirstName1;
aryNameRec[aHigh].AccNum := AccNum;
Inc(AccNum);
getCont.NewName(aryNameRec [aHigh].Fi rstName+' '+aryNameRec[aHigh].LastNa me);
getCont.NewHigh(aHigh);
if curIndex = -1 then
begin
curIndex := 0;
getCont.OnChange(curIndex) ;
end;
Form1.Label3.Caption := IntToStr(aHigh)+' Array High';
end;
procedure TIntFacEvent5.Set_FirstNam e(const Value: WideString);
begin
if curIndex > -1 then
aryNameRec[curIndex].First Name := Value;
end;
procedure TIntFacEvent5.Set_Index(Va lue: Integer);
begin
if (Value < Length(aryNameRec)) and (Value > -1) then
begin
curIndex := Value;
getCont.OnChange(curIndex) ;
end;
end;
procedure TIntFacEvent5.Set_LastName (const Value: WideString);
begin
if curIndex > -1 then
aryNameRec[curIndex].LastN ame := Value;
end;
procedure TIntFacEvent5.setEvents(co nst Callback: ISerEvents3;
const Name: WideString);
begin
getCont.setEvents(CallBack , Name, FconID);
getCont.NewHigh(aHigh);
getCont.OnChange(curIndex) ;
end;
function TIntFacEvent5.Get_Connect_ ID: Integer;
begin
Result := FconID;
end;
function TIntFacEvent5.Get_NoName: WordBool;
begin
Result := getCont.GetNoName(FconID);
end;
procedure TIntFacEvent5.Set_NoName(V alue: WordBool);
begin
getCont.SetNoName(FconID, Value);
end;
initialization
TAutoObjectFactory.Create( ComServer, TIntFacEvent5, Class_IntFacEvent5,
ciMultiInstance, tmApartment);
end.
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
I wanted a server that can be specific for each client, so I created an Interface for the Events, ISerEvents3 which has 3 events that are activated in the client. Also I needed a way to turn on and off some of these events to specific clients, you will see this in the TIntFacEvent5.NoName boolean, if the client sets this to true, then it will NOT get the NewName event, but it will still get the other two (OnChange and NewHigh events).
The form for this has NO code in it just 3 or 4 TLables
What this server does is allow info sharing between clients, it keeps an Array of the TNameRec with FirstNames and lastNames, so each client can connect to server and add to the array, or read and write to the array. To read or write to the array, you set the IIntFacEvent5.Index to the index of the Array you want, and then read or set the FirstName and LastName of the IIntFacEvent5.
unit ComSerIn_TLB;
{$TYPEDADDRESS OFF} // Unit must be compiled without type-checked pointers.
interface
uses Windows, ActiveX, Classes, Graphics, OleServer, OleCtrls, StdVCL;
const
// TypeLibrary Major and minor versions
ComSerInMajorVersion = 1;
ComSerInMinorVersion = 0;
LIBID_ComSerIn: TGUID = '{F7987220-F81E-11D8-B122-
IID_IIntFacEvent5: TGUID = '{F7987221-F81E-11D8-B122-
CLASS_IntFacEvent5: TGUID = '{F7987223-F81E-11D8-B122-
IID_ISerEvents3: TGUID = '{F7987225-F81E-11D8-B122-
type
IIntFacEvent5 = interface;
IIntFacEvent5Disp = dispinterface;
ISerEvents3 = interface;
ISerEvents3Disp = dispinterface;
IntFacEvent5 = IIntFacEvent5;
IIntFacEvent5 = interface(IDispatch)
['{F7987221-F81E-11D8-B122
function Get_Index: Integer; safecall;
procedure Set_Index(Value: Integer); safecall;
function Get_High: Integer; safecall;
procedure Add(const FirstName1: WideString; const LastName1: WideString); safecall;
function Get_LastName: WideString; safecall;
procedure Set_LastName(const Value: WideString); safecall;
function Get_FirstName: WideString; safecall;
procedure Set_FirstName(const Value: WideString); safecall;
procedure setEvents(const Callback: ISerEvents3; const Name: WideString); safecall;
function Get_Connect_ID: Integer; safecall;
function Get_NoName: WordBool; safecall;
procedure Set_NoName(Value: WordBool); safecall;
property Index: Integer read Get_Index write Set_Index;
property High: Integer read Get_High;
property LastName: WideString read Get_LastName write Set_LastName;
property FirstName: WideString read Get_FirstName write Set_FirstName;
property Connect_ID: Integer read Get_Connect_ID;
property NoName: WordBool read Get_NoName write Set_NoName;
end;
IIntFacEvent5Disp = dispinterface
['{F7987221-F81E-11D8-B122
property Index: Integer dispid 1;
property High: Integer readonly dispid 2;
procedure Add(const FirstName1: WideString; const LastName1: WideString); dispid 3;
property LastName: WideString dispid 4;
property FirstName: WideString dispid 5;
procedure setEvents(const Callback: ISerEvents3; const Name: WideString); dispid 6;
property Connect_ID: Integer readonly dispid 8;
property NoName: WordBool dispid 7;
end;
ISerEvents3 = interface(IDispatch)
['{F7987225-F81E-11D8-B122
procedure OnChange(NewIndex: Integer); safecall;
procedure NewName(const nName: WideString); safecall;
procedure NewHigh(currentHigh: Integer); safecall;
end;
ISerEvents3Disp = dispinterface
['{F7987225-F81E-11D8-B122
procedure OnChange(NewIndex: Integer); dispid 1;
procedure NewName(const nName: WideString); dispid 2;
procedure NewHigh(currentHigh: Integer); dispid 4;
end;
CoIntFacEvent5 = class
class function Create: IIntFacEvent5;
// The function below was ADDED to this TLB file AFTER the automation object wizard creation
class function Create_Name(const Callback: ISerEvents3; const Name: WideString): IIntFacEvent5;
class function CreateRemote(const MachineName: string): IIntFacEvent5;
end;
implementation
uses ComObj;
class function CoIntFacEvent5.Create: IIntFacEvent5;
begin
Result := CreateComObject(CLASS_IntF
end;
class function CoIntFacEvent5.Create_Name
begin
// this function adds the setEvents, so the ISerEvents3 can be placed in the new IIntFacEvent5
Result := CreateComObject(CLASS_IntF
if (CallBack <> nil) then
Result.setEvents(Callback,
end;
class function CoIntFacEvent5.CreateRemot
begin
Result := CreateRemoteComObject(Mach
end;
end.
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
unit ComSerInCom;
interface
uses
ComObj, Classes, ActiveX, ComSerIn_TLB, StdVcl;
type
TNameRec = record
// record in array to share amoung clients
UserName, FirstName, LastName: WideString;
ID, AccNum: Integer;
end;
TConect1 = class
// This is a class that just stores Each IntFacEvent5 connections data
public
FUserName: WideString;
FID: Integer;
FNoName: WordBool;
FCallBack: ISerEvents3;
destructor Destroy; override;
end;
TConectList1 = class
// this is a class that does a TList to store all of the TConect1
private
FConList: TList;
public
constructor Create;
destructor Destroy; override;
procedure setEvents(const Callback: ISerEvents3; const Name: WideString; ID: Integer);
procedure SetNoName(conID: Integer; NoName: WordBool);
function GetNoName(conID: Integer): WordBool;
procedure StopEvents(conID: Integer);
procedure OnChange(NewIndex: Integer);
procedure NewName(const nName: WideString);
procedure NewHigh(currentHigh: Integer);
end;
TIntFacEvent5 = class(TAutoObject, IIntFacEvent5)
// this is the Interface Class that is used to add and share information from the Array aryNameRec
protected
{ Protected declarations }
FconID: Integer;
function Get_FirstName: WideString; safecall;
function Get_High: Integer; safecall;
function Get_Index: Integer; safecall;
function Get_LastName: WideString; safecall;
procedure Add(const FirstName1, LastName1: WideString); safecall;
procedure Set_FirstName(const Value: WideString); safecall;
procedure Set_Index(Value: Integer); safecall;
procedure Set_LastName(const Value: WideString); safecall;
procedure setEvents(const Callback: ISerEvents3; const Name: WideString);
safecall;
//procedure StopEvents; safecall;
function Get_Connect_ID: Integer; safecall;
function Get_NoName: WordBool; safecall;
procedure Set_NoName(Value: WordBool); safecall;
public
{ Public declarations }
procedure Initialize; override;
destructor Destroy; override;
function getCont: TConectList1;
end;
implementation
uses ComServ, SysUtils, ComSerIn1;
var
aryNameRec: Array of TNameRec;
ConectNum: Integer = 0; // number of connections to server
AccNum: Integer = 406000;
aHigh: Integer = -1; // current High of aryNameRec
curIndex: Integer = -1; // current Index for aryNameRec access
IDNum: Integer = 100;
ContLst: TConectList1;
// I think it would be better to have created ContLst in the initialization and free in Finalization
destructor TConect1.Destroy;
begin
FCallBack := nil; // free interface reference
inherited Destroy;
end;
// TConectList1
constructor TConectList1.Create;
begin
FConList := TList.Create;
end;
destructor TConectList1.Destroy;
var
i: Integer;
Con1: TConect1;
begin
for i := 0 to FConList.Count - 1 do
begin
Con1 := TConect1(FConList[i]);
FreeAndNil(Con1);
end;
inherited Destroy;
end;
procedure TConectList1.setEvents(con
var
Con1: TConect1;
begin
Con1 := TConect1.Create;
Con1.FCallBack := Callback;
Con1.FID := ID{FconID};
Con1.FUserName := Name;
Con1.FNoName := False;
FConList.Add(Con1);
end;
procedure TConectList1.StopEvents(co
var
i: Integer;
Con1: TConect1;
begin
for i := 0 to FConList.Count - 1 do
begin
Con1 := TConect1(FConList[i]);
if Con1.FID = conID then
begin
FreeAndNil(Con1);
FConList.Delete(i);
end;
end;
end;
procedure TConectList1.SetNoName(con
var
i: Integer;
begin
for i := 0 to FConList.Count - 1 do
if TConect1(FConList[i]).FID = conID then
begin
TConect1(FConList[i]).FNoN
Break;
end;
end;
function TConectList1.GetNoName(con
var
i: Integer;
begin
Result := False;
for i := 0 to FConList.Count - 1 do
if TConect1(FConList[i]).FID = conID then
begin
Result := TConect1(FConList[i]).FNoN
Break;
end;
end;
// the 3 procedures below are called for the ISerEvents3 events
procedure TConectList1.OnChange(NewI
var
i: Integer;
begin
for i := 0 to FConList.Count - 1 do
TConect1(FConList[i]).FCal
end;
procedure TConectList1.NewName(const
var
i: Integer;
begin
for i := 0 to FConList.Count - 1 do
if not TConect1(FConList[i]).FNoN
TConect1(FConList[i]).FCal
end;
procedure TConectList1.NewHigh(curre
var
i: Integer;
begin
for i := 0 to FConList.Count - 1 do
TConect1(FConList[i]).FCal
end;
// TIntFacEvent5 interface
procedure TIntFacEvent5.Initialize;
begin
inherited Initialize;
Inc(ConectNum);
FconID := IDNum;
Inc(IDNum);
Form1.Label2.Caption := IntToStr(ConectNum)+' Conections';
end;
destructor TIntFacEvent5.Destroy;
begin
Dec(ConectNum);
getCont.StopEvents(FconID)
Form1.Label2.Caption := IntToStr(ConectNum)+' Conections';
inherited Destroy;
end;
function TIntFacEvent5.getCont: TConectList1;
begin
if ContLst = nil then
ContLst := TConectList1.Create;
Result := ContLst;
end;
function TIntFacEvent5.Get_FirstNam
begin
if curIndex > -1 then
Result := aryNameRec[curIndex].First
else
Result := '';
end;
function TIntFacEvent5.Get_High: Integer;
begin
Result := aHigh;
end;
function TIntFacEvent5.Get_Index: Integer;
begin
Result := curIndex;
end;
function TIntFacEvent5.Get_LastName
begin
if curIndex > -1 then
Result := aryNameRec[curIndex].LastN
else
Result := '';
end;
procedure TIntFacEvent5.Add(const FirstName1, LastName1: WideString);
begin
SetLength(aryNameRec, Length(aryNameRec)+1);
aHigh := High(aryNameRec);
aryNameRec[aHigh].LastName
aryNameRec[aHigh].FirstNam
aryNameRec[aHigh].AccNum := AccNum;
Inc(AccNum);
getCont.NewName(aryNameRec
getCont.NewHigh(aHigh);
if curIndex = -1 then
begin
curIndex := 0;
getCont.OnChange(curIndex)
end;
Form1.Label3.Caption := IntToStr(aHigh)+' Array High';
end;
procedure TIntFacEvent5.Set_FirstNam
begin
if curIndex > -1 then
aryNameRec[curIndex].First
end;
procedure TIntFacEvent5.Set_Index(Va
begin
if (Value < Length(aryNameRec)) and (Value > -1) then
begin
curIndex := Value;
getCont.OnChange(curIndex)
end;
end;
procedure TIntFacEvent5.Set_LastName
begin
if curIndex > -1 then
aryNameRec[curIndex].LastN
end;
procedure TIntFacEvent5.setEvents(co
const Name: WideString);
begin
getCont.setEvents(CallBack
getCont.NewHigh(aHigh);
getCont.OnChange(curIndex)
end;
function TIntFacEvent5.Get_Connect_
begin
Result := FconID;
end;
function TIntFacEvent5.Get_NoName: WordBool;
begin
Result := getCont.GetNoName(FconID);
end;
procedure TIntFacEvent5.Set_NoName(V
begin
getCont.SetNoName(FconID, Value);
end;
initialization
TAutoObjectFactory.Create(
ciMultiInstance, tmApartment);
end.
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
I wanted a server that can be specific for each client, so I created an Interface for the Events, ISerEvents3 which has 3 events that are activated in the client. Also I needed a way to turn on and off some of these events to specific clients, you will see this in the TIntFacEvent5.NoName boolean, if the client sets this to true, then it will NOT get the NewName event, but it will still get the other two (OnChange and NewHigh events).
The form for this has NO code in it just 3 or 4 TLables
the code for the Client form
unit evntComCli1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, OleServer, ActiveX, ComObj, ComSerIn1_TLB;
type
TEvents1 = class(TAutoIntfObject, ISerEvents3)
procedure OnChange(NewIndex: Integer); safecall;
procedure NewName(const nName: WideString); safecall;
procedure NewHigh(currentHigh: Integer); safecall;
end;
TForm1 = class(TForm)
but_Connect: TButton;
Label1: TLabel;
Label2: TLabel;
Label3: TLabel;
but_AddNames: TButton;
but_NoName: TButton;
Label4: TLabel;
Edit1: TEdit;
Edit2: TEdit;
Edit3: TEdit;
but_GetNames: TButton;
but_SetNames: TButton;
procedure but_ConnectClick(Sender: TObject);
procedure but_AddNamesClick(Sender: TObject);
procedure but_NoNameClick(Sender: TObject);
procedure but_GetNamesClick(Sender: TObject);
procedure but_SetNamesClick(Sender: TObject);
private
{ Private declarations }
aIndex, curHigh: Integer;
Server1: IIntFacEvent5;
Evt1: TEvents1;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
procedure TEvents1.OnChange(NewIndex : Integer);
begin
Form1.aIndex := NewIndex;
Form1.Label1.Caption := 'Index is '+IntToStr(NewIndex);
end;
procedure TEvents1.NewName(const nName: WideString);
begin
Form1.Label2.Caption := nName;
end;
procedure TEvents1.NewHigh(currentHi gh: Integer);
begin
Form1.curHigh := currentHigh;
Form1.Label4.Caption := 'High is '+IntToStr(Form1.curHigh);
end;
procedure TForm1.but_ConnectClick(Se nder: TObject);
var
TypLib: ITypeLib;
begin
OLECheck(LoadRegTypeLib(LI BID_ComSer In,1,0,0, TypLib));
Evt1 := TEvents1.Create(TypLib, ISerEvents3);
if Evt1 <> nil then
Server1 := CoIntFacEvent5.Create_Name (Evt1, 'Wes1')
else
Showmessage('No Evt1');
but_Connect.Enabled := False;
but_AddNames.Enabled := True;
end;
procedure TForm1.but_AddNamesClick(S ender: TObject);
begin
if Server1 <> nil then
begin
if Edit1.Text <> '' then
Server1.Add(Edit1.Text, Edit2.Text);
end;
end;
procedure TForm1.but_NoNameClick(Sen der: TObject);
begin
if Server1 = nil then Exit;
Server1.NoName := not Server1.NoName;
if Server1.NoName then
but_NoName.Caption := 'turn NoName OFF'
else
but_NoName.Caption := 'turn NoName ON';
end;
procedure TForm1.but_GetNamesClick(S ender: TObject);
var
sIndex: Integer;
begin
if Server1 = nil then Exit;
sIndex := StrToIntDef(Edit3.Text, 0);
if (sIndex < 0) or (sIndex > curHigh) then
begin
Showmessage('Index is OUT of BOUNDS');
Exit;
end;
Server1.Index := sIndex;
Edit1.Text := Server1.FirstName;
Edit2.Text := Server1.LastName;
end;
procedure TForm1.but_SetNamesClick(S ender: TObject);
var
sIndex: Integer;
begin
if Server1 = nil then Exit;
sIndex := StrToIntDef(Edit3.Text, 0);
if (sIndex < 0) or (sIndex > curHigh) then
begin
Showmessage('Index is OUT of BOUNDS');
Exit;
end;
Server1.Index := sIndex;
Server1.FirstName := Edit1.Text;
Server1.LastName := Edit2.Text;
end;
end.
unit evntComCli1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, OleServer, ActiveX, ComObj, ComSerIn1_TLB;
type
TEvents1 = class(TAutoIntfObject, ISerEvents3)
procedure OnChange(NewIndex: Integer); safecall;
procedure NewName(const nName: WideString); safecall;
procedure NewHigh(currentHigh: Integer); safecall;
end;
TForm1 = class(TForm)
but_Connect: TButton;
Label1: TLabel;
Label2: TLabel;
Label3: TLabel;
but_AddNames: TButton;
but_NoName: TButton;
Label4: TLabel;
Edit1: TEdit;
Edit2: TEdit;
Edit3: TEdit;
but_GetNames: TButton;
but_SetNames: TButton;
procedure but_ConnectClick(Sender: TObject);
procedure but_AddNamesClick(Sender: TObject);
procedure but_NoNameClick(Sender: TObject);
procedure but_GetNamesClick(Sender: TObject);
procedure but_SetNamesClick(Sender: TObject);
private
{ Private declarations }
aIndex, curHigh: Integer;
Server1: IIntFacEvent5;
Evt1: TEvents1;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
procedure TEvents1.OnChange(NewIndex
begin
Form1.aIndex := NewIndex;
Form1.Label1.Caption := 'Index is '+IntToStr(NewIndex);
end;
procedure TEvents1.NewName(const nName: WideString);
begin
Form1.Label2.Caption := nName;
end;
procedure TEvents1.NewHigh(currentHi
begin
Form1.curHigh := currentHigh;
Form1.Label4.Caption := 'High is '+IntToStr(Form1.curHigh);
end;
procedure TForm1.but_ConnectClick(Se
var
TypLib: ITypeLib;
begin
OLECheck(LoadRegTypeLib(LI
Evt1 := TEvents1.Create(TypLib, ISerEvents3);
if Evt1 <> nil then
Server1 := CoIntFacEvent5.Create_Name
else
Showmessage('No Evt1');
but_Connect.Enabled := False;
but_AddNames.Enabled := True;
end;
procedure TForm1.but_AddNamesClick(S
begin
if Server1 <> nil then
begin
if Edit1.Text <> '' then
Server1.Add(Edit1.Text, Edit2.Text);
end;
end;
procedure TForm1.but_NoNameClick(Sen
begin
if Server1 = nil then Exit;
Server1.NoName := not Server1.NoName;
if Server1.NoName then
but_NoName.Caption := 'turn NoName OFF'
else
but_NoName.Caption := 'turn NoName ON';
end;
procedure TForm1.but_GetNamesClick(S
var
sIndex: Integer;
begin
if Server1 = nil then Exit;
sIndex := StrToIntDef(Edit3.Text, 0);
if (sIndex < 0) or (sIndex > curHigh) then
begin
Showmessage('Index is OUT of BOUNDS');
Exit;
end;
Server1.Index := sIndex;
Edit1.Text := Server1.FirstName;
Edit2.Text := Server1.LastName;
end;
procedure TForm1.but_SetNamesClick(S
var
sIndex: Integer;
begin
if Server1 = nil then Exit;
sIndex := StrToIntDef(Edit3.Text, 0);
if (sIndex < 0) or (sIndex > curHigh) then
begin
Showmessage('Index is OUT of BOUNDS');
Exit;
end;
Server1.Index := sIndex;
Server1.FirstName := Edit1.Text;
Server1.LastName := Edit2.Text;
end;
end.
ASKER
Slick, thanks for the example but I am not unfamiliar with COM itself. ;-) The biggest problem I have is just making sure I have a singleton COM object. I.E. there's only one object in memory, no matter how many processes are connected to it. Thus, I could have 10 applications using the Object Server1 and they would all be looking at the same object. Thus if process A makes a change to one of the values, it would affect all other processes too. (Thus I want to notify other processes that a value has changed.)
The big problem isn't sharing data between multiple clients. The problem is notifying all clients that something has changed. The problem when you're not using a singleton object for this is that the object itself only knows about one client. The problem with maintaining a list of clients in the server as a global variable is that clients might disappear without these variables being notified about this correctly. Of course there also a few threading issues to consider and a few other nasty problems that need to be solved this way. But there's some existing functionality for this within the COM system and I'm just looking for that.
The big problem isn't sharing data between multiple clients. The problem is notifying all clients that something has changed. The problem when you're not using a singleton object for this is that the object itself only knows about one client. The problem with maintaining a list of clients in the server as a global variable is that clients might disappear without these variables being notified about this correctly. Of course there also a few threading issues to consider and a few other nasty problems that need to be solved this way. But there's some existing functionality for this within the COM system and I'm just looking for that.
I read your last comment, and I have not com to an understanding of what you mean by
" singleton COM object "
in my code there is this
TAutoObjectFactory.Create( ComServer, TIntFacEvent5, Class_IntFacEvent5,
ciMultiInstance, tmApartment);
and as far as I can tell )?, ?) you will nedd to have a MultiInstance, and tmApartment type of come server, I really do NOT think that a single Instance com server will allow you to share any info OR Events, , , but I am not to clear on what you mean by singleton, , anyway. .
as far as I can tell from your last comments, The com server code that I gave you, does all you have mentioned. .
there is ONE server operational at any time, no matter how many clients there are running
it shares data between several clients, data which is stored on the server
AND with certain data changes (add to the data record, , change Index, , ect) ALL the clients are Notified with a event from the ISerEvents3 interface, I am under the impression that thread sync is handled by the com interface
also my server keeps a LIST of ALL currently connected clients (and their sign in "Name"), And it has the ability to Turn off and On certain notification events to certain clients (Server1.NoName)
I guess I missed your needs, sorry
" singleton COM object "
in my code there is this
TAutoObjectFactory.Create(
ciMultiInstance, tmApartment);
and as far as I can tell )?, ?) you will nedd to have a MultiInstance, and tmApartment type of come server, I really do NOT think that a single Instance com server will allow you to share any info OR Events, , , but I am not to clear on what you mean by singleton, , anyway. .
as far as I can tell from your last comments, The com server code that I gave you, does all you have mentioned. .
there is ONE server operational at any time, no matter how many clients there are running
it shares data between several clients, data which is stored on the server
AND with certain data changes (add to the data record, , change Index, , ect) ALL the clients are Notified with a event from the ISerEvents3 interface, I am under the impression that thread sync is handled by the com interface
also my server keeps a LIST of ALL currently connected clients (and their sign in "Name"), And it has the ability to Turn off and On certain notification events to certain clients (Server1.NoName)
I guess I missed your needs, sorry
ofcourse you can do one ...
create a descendant of TAutoObjectFactory that handles the singleton
.. will create an example shortly :)
create a descendant of TAutoObjectFactory that handles the singleton
.. will create an example shortly :)
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
the object singleton trick is a reference from: http://howtodothings.com/showarticle.asp?article=170 :)
ASKER
Seems fine to me. :-)
in fact you don't need singleton handling at the object level .. only at factory :)
here's the *simpler* version:
unit MyComObjU;
{$WARN SYMBOL_PLATFORM OFF}
interface
uses
ComObj, Classes, ActiveX, SrvApp_TLB, StdVcl;
type
TMyComObj = class(TAutoObject, IMyComObj)
private
FTotal: Integer;
FList: TList;
protected
procedure Append(AValue: Integer); safecall;
procedure RegisterObserver(AHandle: Integer); safecall;
procedure UnRegisterObserver(AHandle : Integer); safecall;
public
procedure AfterConstruction; override;
procedure BeforeDestruction; override;
end;
TMyFactory = class(TAutoObjectFactory)
public
function CreateComObject(const Controller: IUnknown): TComObject; override;
end;
implementation
uses ComServ, Windows, Messages, SrvFrmU;
var
Instance: TMyComObj = nil;
procedure TMyComObj.AfterConstructio n;
begin
inherited;
FTotal:=0;
FList:=TList.Create;
end;
procedure TMyComObj.Append(AValue: Integer);
var wnd, I: Integer;
begin
FTotal:=FTotal + AValue;
for I:=FList.Count-1 downto 0 do
begin
wnd:=Integer(FList.Items[I ]);
if IsWindow(wnd) then
PostMessage(wnd, WM_APP + 1, AValue, FTotal)
else
UnRegisterObserver(wnd); // unregister nonexistant observers
end;
end;
procedure TMyComObj.BeforeDestructio n;
begin
FList.Free;
inherited;
end;
procedure TMyComObj.RegisterObserver (AHandle: Integer);
begin
if FList.IndexOf(Pointer(AHan dle)) = -1 then
begin
FList.Add(Pointer(AHandle) );
SrvFrm.ObserverRegistered;
end;
end;
procedure TMyComObj.UnRegisterObserv er(AHandle : Integer);
begin
if FList.Remove(Pointer(AHand le)) > -1 then
SrvFrm.ObserverUnRegistere d;
end;
{ TMyFactory }
function TMyFactory.CreateComObject (
const Controller: IInterface): TComObject;
begin
if not Assigned(Instance) then
Instance:=TMyComObj(inheri ted CreateComObject(Controller ));
Result:=Instance;
end;
initialization
TMyFactory.Create(ComServe r, TMyComObj, Class_MyComObj,
ciMultiInstance, tmSingle);
end.
here's the *simpler* version:
unit MyComObjU;
{$WARN SYMBOL_PLATFORM OFF}
interface
uses
ComObj, Classes, ActiveX, SrvApp_TLB, StdVcl;
type
TMyComObj = class(TAutoObject, IMyComObj)
private
FTotal: Integer;
FList: TList;
protected
procedure Append(AValue: Integer); safecall;
procedure RegisterObserver(AHandle: Integer); safecall;
procedure UnRegisterObserver(AHandle
public
procedure AfterConstruction; override;
procedure BeforeDestruction; override;
end;
TMyFactory = class(TAutoObjectFactory)
public
function CreateComObject(const Controller: IUnknown): TComObject; override;
end;
implementation
uses ComServ, Windows, Messages, SrvFrmU;
var
Instance: TMyComObj = nil;
procedure TMyComObj.AfterConstructio
begin
inherited;
FTotal:=0;
FList:=TList.Create;
end;
procedure TMyComObj.Append(AValue: Integer);
var wnd, I: Integer;
begin
FTotal:=FTotal + AValue;
for I:=FList.Count-1 downto 0 do
begin
wnd:=Integer(FList.Items[I
if IsWindow(wnd) then
PostMessage(wnd, WM_APP + 1, AValue, FTotal)
else
UnRegisterObserver(wnd); // unregister nonexistant observers
end;
end;
procedure TMyComObj.BeforeDestructio
begin
FList.Free;
inherited;
end;
procedure TMyComObj.RegisterObserver
begin
if FList.IndexOf(Pointer(AHan
begin
FList.Add(Pointer(AHandle)
SrvFrm.ObserverRegistered;
end;
end;
procedure TMyComObj.UnRegisterObserv
begin
if FList.Remove(Pointer(AHand
SrvFrm.ObserverUnRegistere
end;
{ TMyFactory }
function TMyFactory.CreateComObject
const Controller: IInterface): TComObject;
begin
if not Assigned(Instance) then
Instance:=TMyComObj(inheri
Result:=Instance;
end;
initialization
TMyFactory.Create(ComServe
ciMultiInstance, tmSingle);
end.
for Instancing choose Single Instance. Threading Model: Single
Working with COM like this this, you will have to use Critical Sections when you use global resources, like ADOQuery for example or global variable. This can reflect on the performance...