6.2 Aufbau des Servers
Der Server besteht ebenfalls aus einem Notebook und einer Listbox. Über das Notebook wird der Server gesteuert und in der Listbox werden die Ergebnisse und Anfragen des Clients protokolliert. Der Server wird InPlace
betrieben, das heißt, er nutzt die Fläche innerhalb eines Fensters des Clients für seine Darstellung.
Wenn eine Verbindung mit dem Server besteht, der Server aber noch nicht aktiviert worden ist, wird im Bereich des Clients ein Bild dargestellt. Nach der Aktivierung erscheinen dann die Objekte des Servers im Client.
Damit der Server ablauffähig ist, wurde dem Dialog und dem Control-Objekt jeweils eine eindeutige UUID zugewiesen.
dialog IdmTest { .uuid "499593d0-a159-11d1-a7e3-00a02444c34e"; } tile TiPropMethEvent "IMD_IMAGES:isaicon.gif"; default control CONTROL { integer Count := 0; on start { CONTROL.Count := CONTROL.Count + 1; } on finish { CONTROL.Count := CONTROL.Count - 1; if CONTROL.Count=0 then exit(); endif } } control PropMethEvent { .mode mode_server; .uuid "499593d1-a159-11d1-a7e3-00a02444c34e"; .picture TiPropMethEvent; }
6.2.1 Bereitstellung des Servers
Anders als beim Client müssen beim Server einige Schritte durchgeführt werden, damit der Server als OLE-Server betrieben werden kann.
Zunächst wird mit Hilfe der Option ‑writeole des IDM-Simulationsprogramms die zur Registrierung des OLE-Servers notwendige reg-Datei sowie die zum Bereitstellen der Ablaufkomponenten notwendige idl-Datei erzeugt. Die Kommandozeile sieht dabei wie folgt aus:
$(THIS).reg $(THIS).idl: $(THIS).dlg $(IDM) $(THIS).dlg -localserver "$(IDM) $(THIS).dlg \ -IDMenv MODLIB=$(THISDIR) -IDMerrfile $(THIS).log" \ -writeole $(THIS) regedit $(THIS).reg
Mit dem Befehl regedit wird der Server gleich im System registriert.
Anschließend wird die erzeugte idl-Datei mit Hilfe des MIDL-Compilers übersetzt.
$(THIS).tlb $(THIS)_p.c $(THIS).h dlldata.c: $(THIS).idl midl /ms_ext /app_config /c_ext /tlb $(THIS).tlb /Zp1 \ /env win32 /Os $(THIS).idl
Zum Schluss wird aus den Objektdateien eine DLL generiert.
$(OUTFILE) : $(OBJS) $(TARGET).res $(DEFFILE) echo ++++++++++ echo Linking $@ echo $(LINK) > $(TARGET).lrf echo $(ENTRY) >> $(TARGET).lrf echo -def:$(THIS).def >> $(TARGET).lrf echo -out:$(OUTFILE) >> $(TARGET).lrf echo -machine:IX86 >> $(TARGET).lrf echo -subsystem:windows5.01 >> $(TARGET).lrf echo -align:0x1000 >> $(TARGET).lrf echo $(OBJS1) >> $(TARGET).lrf echo $(OBJS2) >> $(TARGET).lrf echo $(OBJS3) >> $(TARGET).lrf echo $(OBJS4) >> $(TARGET).lrf echo $(OBJS5) >> $(TARGET).lrf echo $(OBJS6) >> $(TARGET).lrf echo $(TARGET).res >> $(TARGET).lrf echo $(LIBS) >> $(TARGET).lrf echo $(LIBS32) >> $(TARGET).lrf link @$(TARGET).lrf del $(TARGET).lrf
Danach kann der Server von einem Client benutzt werden.
Das zugehörige Makefile sieht wie folgt aus:
THISDIR=h:\ole\clntserv THIS=$(THISDIR)\server IDM=idmole.exe CL32 = -G3s WX = LINKDLL = /DLL DEFDLL = -D_DLL ENTRY = -entry:LibMain32 DEFUNICODE = -DWIN32ANSI LINKD32 = -debug:full $(LINKDLL) -debugtype:cv LINKN32 = -debug:none $(LINKDLL) DEFS32 = -DWIN32 $(DEFDLL) -D_X86_=1 $(DEFUNICODE) TLBDEFS = -DWIN32 #BOOKLIB = inole.lib LIBS32A = msvcrt.lib kernel32.lib user32.lib gdi32.lib comdlg32.lib advapi32.lib LIBS32B = ole32.lib oleaut32.lib uuid.lib LIBS32 = $(LIBS32A) $(LIBS32B) $(BOOKLIB) LIBS = rpcrt4.lib CONTIN = CFLAGS = -c -Od -Z7 -Ze -W3 -nologo $(CL32) \ -D_WIN32_WINNT=0x400 LINK = $(LINKD32) /NOD DEFS = $(DEFS32) -DSTRICT -DDEBUG .SUFFIXES: .h .obj .exe .dll .cpp .res .rc .tlb .odl OUTFILE = $(THIS).dll TARGET = $(THIS) DEFFILE = $(THIS).def OBJS1 = $(THIS)_i.obj $(THIS)_p.obj OBJS2 = dlldata.obj libmain.obj OBJS3 = "" OBJS4 = "" OBJS5 = "" OBJS6 = "" OBJS = $(OBJS1) $(OBJS2) ############################################################### .c.obj: echo ++++++++++ echo Compiling $*.c cl $(CFLAGS) $(DEFS) $*.c .cpp.obj: echo ++++++++++ echo Compiling $*.c cl $(CFLAGS) $(DEFS) $*.cpp .rc.res: echo ++++++++++ echo Compiling Resources rc -r $(DEFS) $(DOC) -fo$@ $*.rc ############################################################### all: $(THIS).reg $(THIS).idl $(THIS).tlb $(THIS).dll clean: del $(THIS).tlb del $(THIS).reg del $(THIS).dll del $(THIS).odl del $(THIS)_i.c del $(THIS)_p.c del $(THIS).h del dlldata.c del *.obj del *.exe del *.lrf $(THIS).reg $(THIS).idl: $(THIS).dlg $(IDM) $(THIS).dlg -localserver "$(IDM) $(THIS).dlg \ -IDMenv MODLIB=$(THISDIR) -IDMerrfile $(THIS).log" \ -writeole $(THIS) regedit $(THIS).reg $(THIS).tlb $(THIS)_p.c $(THIS).h dlldata.c: $(THIS).idl midl /ms_ext /app_config /c_ext /tlb $(THIS).tlb /Zp1 \ /env win32 /Os $(THIS).idl $(THIS)_p.obj: $(THIS)_p.c $(THIS)_i.obj: $(THIS)_i.c dlldata.obj: dlldata.c libmain.obj: libmain.cpp ############################################################## $(OUTFILE) : $(OBJS) $(TARGET).res $(DEFFILE) echo ++++++++++ echo Linking $@ echo $(LINK) > $(TARGET).lrf echo $(ENTRY) >> $(TARGET).lrf echo -def:$(THIS).def >> $(TARGET).lrf echo -out:$(OUTFILE) >> $(TARGET).lrf echo -machine:IX86 >> $(TARGET).lrf echo -subsystem:windows5.01 >> $(TARGET).lrf echo -align:0x1000 >> $(TARGET).lrf echo $(OBJS1) >> $(TARGET).lrf echo $(OBJS2) >> $(TARGET).lrf echo $(OBJS3) >> $(TARGET).lrf echo $(OBJS4) >> $(TARGET).lrf echo $(OBJS5) >> $(TARGET).lrf echo $(OBJS6) >> $(TARGET).lrf echo $(TARGET).res >> $(TARGET).lrf echo $(LIBS) >> $(TARGET).lrf echo $(LIBS32) >> $(TARGET).lrf link @$(TARGET).lrf del $(TARGET).lrf
6.2.2 Abfragen und Setzen von Attributen
Damit der Client Attribute am Server abfragen kann, muss im Server nichts programmiert werden. Es können automatisch alle am Control-Objekt definierten Attribute vom Client erfragt und gesetzt werden.
Im Beispiel sind die Attribute I
, S
und B
vom Client abfragbar.
control PropMethEvent { integer I := 123; string S := "Dialog Manager"; boolean B := true; }
6.2.3 Aufruf von Methoden
Damit der Client Methoden im Server aufrufen kann, müssen diese als ganz normale Methoden des Control-Objektes definiert werden. Diese Methoden können dann auf alle im Dialog definierten Objekte und Attribute zugreifen.
In diesem Beispiel sehen die Methoden wie folgt aus:
control PropMethEvent { .message[1] Msg1; .message[2] Msg2; .message[3] Msg3; .message[4] Msg4; .message[5] Msg5; .message[6] Msg6; rule void M1 { Info("M1() called."); sendevent(this, Msg1); } rule integer M2 (integer I input) { Info("M2(" + I + ") called. Return -123456789"); return -123456789; } rule string M3 (string S input) { Info("M3(" + S + ") called. Return \"Bye\""); return "Bye"; } rule boolean M4 (boolean B input) { Info("M4(" + B + ") called. Return " + ( not B )); return ( not B ); } rule void M5 (integer I input, string S input, boolean B input) { Info("M5(" + I + ", " + S + ", " + B + ") called."); } rule string M6 (integer P1 input, string P2 input, boolean P3 input, integer P4 input, string P5 input, boolean P6 input, integer P7 input, string P8 input) { Info("M6(" + P1 + ", " + P2 + ", " + P3 + ", " + P4 + ", " + P5 + ", " + P6 + ", " + P7 + ", " + P8 + ") called. Return \"Abracadabra!\""); return "Abracadabra!"; } }
6.2.4 Versenden von Ereignissen
Wenn der Server dem Client Ereignisse schicken soll, müssen diese im Server definiert und entsprechend programmiert werden. Die Definition solcher Ereignisse erfolgt mit Hilfe der Ressource Message. Die Ereignisse werden dann mit der eingebauten Funktion sendevent an den Client geschickt.
Diese Ereignisse sind im Beispiel wie folgt definiert:
message Msg1; message Msg2 (integer I); message Msg3 (string S); message Msg4 (boolean B); message Msg5 (integer I, string S, boolean B); message Msg6 (integer P1, string P2, boolean P3, integer P4, string P5, boolean P6);
Zusätzlich müssen die von einem Control verschickbaren Ereignisse am Control-Objekt im Attribut .message[integer] definiert werden. Die in diesem Attribut definierten Messages werden dann an den Client weitergeleitet.
control PropMethEvent { .mode mode_server; .uuid "499593d1-a159-11d1-a7e3-00a02444c34e"; .picture TiPropMethEvent; .message[1] Msg1; .message[2] Msg2; .message[3] Msg3; .message[4] Msg4; .message[5] Msg5; .message[6] Msg6; }
Das Versenden der Ereignisse erfolgt nach Selektion des Pushbuttons Send
in folgender Regel:
child pushbutton PbSend { .yauto -1; .height 1; .text "Send"; .defbutton true; integer I shadows instance NpEvents.Integer.Value; boolean B shadows instance NpEvents.Boolean.Value; string S shadows instance NpEvents.String.Value; on select { case this.parent.LbEvents.activeitem in 1: sendevent(this.control, Msg1); Info("sendevent(Msg1);"); in 2: sendevent(this.control, Msg2, this.I); Info("sendevent(Msg2, " + this.I + ");"); in 3: sendevent(this.control, Msg3, this.S); Info("sendevent(Msg3, " + this.S + ");"); in 4: sendevent(this.control, Msg4, this.B); Info("sendevent(Msg4, " + this.B + ");"); in 5: sendevent(this.control, Msg5, this.I, this.S, this.B); Info("sendevent(Msg5, " + this.I + ", " + this.S + ", " + this.B + ");"); in 6: sendevent(this.control, Msg6, this.I, this.S, this.B, this.I, this.S, this.B); Info("sendevent(Msg6, " + this.I + ", " + this.S + ", " + this.B + ", " + this.I + ", " + this.S + ", " + this.B + ");"); otherwise: Info("Error - unknown event"); endcase } }
6.2.5 Versenden von Benachrichtigungen (Notifications)
Um Benachrichtigungen über die Änderungen von Attributen an den Client zu verschicken, muss im Server nichts programmiert werden. Sobald sich ein Attribut an dem Control ändert, wird automatisch eine Benachrichtigung an den Client verschickt.
Der Regelcode für die Zuweisung innerhalb des Servers sieht wie folgt aus:
child pushbutton PbApply { .yauto -1; .height 1; .text "Apply"; .defbutton true; on select { this.control.I := this.parent.Integer.Value; this.control.S := this.parent.String.Value; this.control.B := this.parent.Boolean.Value; } }
6.2.6 Der Server Dialog
dialog IdmTest { .uuid "499593d0-a159-11d1-a7e3-00a02444c34e"; } tile TiPropMethEvent "IMD_IMAGES:isaicon.gif"; message Msg1; message Msg2(integer I); message Msg3(string S); message Msg4(boolean B); message Msg5(integer I, string S, boolean B); message Msg6(integer P1, string P2, boolean P3, integer P4, string P5, boolean P6); model groupbox MInteger { .height 1; .xauto 0; integer Value := 123; child statictext { .text "Integer:"; } child edittext Et { .xleft 8; .xauto 0; .format "%-9d"; .content "123"; on charinput { if fail(this.parent.Value := atoi(this.content)) then this.parent.Value := 0; endif } } on .Value changed { this.Et.content := itoa(this.Value); } } model groupbox MString { .height 1; .xauto 0; string Value shadows instance MString.Et.content; child statictext { .text "String:"; } child edittext Et { .xleft 8; .xauto 0; .content "Dialog Manager"; } } model groupbox MBoolean { .height 1; boolean Value shadows instance Cb.active; child statictext { .text "Boolean:"; } child checkbox Cb { .xleft 8; .text ""; } } default control CONTROL { integer Count := 0; on start { CONTROL.Count := CONTROL.Count + 1; } on finish { CONTROL.Count := CONTROL.Count - 1; if CONTROL.Count=0 then exit(); endif } } model control PropMethEvent { .mode mode_server; .uuid "499593d1-a159-11d1-a7e3-00a02444c34e"; .picture TiPropMethEvent; .message[1] Msg1; .message[2] Msg2; .message[3] Msg3; .message[4] Msg4; .message[5] Msg5; .message[6] Msg6; integer I := 123; string S := "Dialog Manager"; boolean B := true; rule void M1 { Info("M1() called."); sendevent(this, Msg1); } rule integer M2 (integer I input) { Info("M2(" + I + ") called. Return -123456789"); return -123456789; } rule string M3 (string S input) { Info("M3(" + S + ") called. Return \"Bye\""); return "Bye"; } rule boolean M4 (boolean B input) { Info("M4(" + B + ") called. Return " + ( not B )); return ( not B ); } rule void M5 (integer I input, string S input, boolean B input) { Info("M5(" + I + ", " + S + ", " + B + ") called."); } rule string M6 (integer P1 input, string P2 input, boolean P3 input, integer P4 input, string P5 input, boolean P6 input, integer P7 input, string P8 input) { Info("M6(" + P1 + ", " + P2 + ", " + P3 + ", " + P4 + ", " + P5 + ", " + P6 + ", " + P7 + ", " + P8 + ") called. Return \"Abracadabra!\""); return "Abracadabra!"; } on .I changed { Info(".I changed := " + this.I); this.Gb.Nb.NpProperties.Integer.Value := this.I; } on .S changed { Info((".S changed := " + this.S)); this.Gb.Nb.NpProperties.String.Value := this.S; } on .B changed { Info((".B changed := " + this.B)); this.Gb.Nb.NpProperties.Boolean.Value := this.B; } child groupbox Gb { .xauto 0; .yauto 0; .borderwidth 0; child notebook Nb { .xauto 0; .yauto 1; .height 10; child notepage NpProperties { .active true; .title "Properties"; child MInteger Integer { } child MString String { .ytop 1; } child MBoolean Boolean { .ytop 2; .Value := true; } child pushbutton PbApply { .yauto -1; .height 1; .text "Apply"; .defbutton true; on select { this.control.I := this.parent.Integer.Value; this.control.S := this.parent.String.Value; this.control.B := this.parent.Boolean.Value; } } } child notepage NpMethods { .active false; .title "Methods"; child listbox LbMethods { .xauto 0; .yauto 0; .content[1] "void M1()"; .content[2] "integer M2(integer)"; .content[3] "string M3(string)"; .content[4] "boolean M4(boolean)"; .content[5] "void M5(integer, string, boolean)"; .content[6] "string M6(integer, string, boolean, ...\ integer, string, boolean, integer, string)"; .firstchar 1; } } child notepage NpEvents { .title "Events"; child listbox LbEvents { .xauto 1; .width 20; .yauto 0; .ybottom 1; .content[1] "Msg1()"; .content[2] "Msg2(integer)"; .content[3] "Msg3(string)"; .content[4] "Msg4(boolean)"; .content[5] "Msg5(integer, string, boolean)"; .content[6] "Msg6(integer, string, boolean, ...\ integer, string, boolean)"; .activeitem 1; .firstchar 1; on select { this.parent.Integer.sensitive := (0 <> stringpos(this.content[this.activeitem], "integer")); this.parent.String.sensitive := (0 <> stringpos(this.content[this.activeitem], "string")); this.parent.Boolean.sensitive := (0 <> stringpos(this.content[this.activeitem], "boolean")); } } child MInteger Integer { .sensitive false; .xleft 22; .yauto 1; .Et.active false; } child MString String { .sensitive false; .xleft 22; .yauto 1; .ytop 1; } child MBoolean Boolean { .sensitive false; .xleft 22; .yauto 1; .ytop 2; } child pushbutton PbSend { .yauto -1; .height 1; .text "Send"; .defbutton true; integer I shadows instance NpEvents.Integer.Value; boolean B shadows instance NpEvents.Boolean.Value; string S shadows instance NpEvents.String.Value; on select { case this.parent.LbEvents.activeitem in 1: sendevent(this.control, Msg1); Info("sendevent(Msg1);"); in 2: sendevent(this.control, Msg2, this.I); Info("sendevent(Msg2, " + this.I + ");"); in 3: sendevent(this.control, Msg3, this.S); Info("sendevent(Msg3, " + this.S + ");"); in 4: sendevent(this.control, Msg4, this.B); Info("sendevent(Msg4, " + this.B + ");"); in 5: sendevent(this.control, Msg5, this.I, this.S, this.B); Info("sendevent(Msg5, " + this.I + ", " + this.S + ", " + this.B + ");"); in 6: sendevent(this.control, Msg6, this.I, this.S, this.B, this.I, this.S, this.B); Info("sendevent(Msg6, " + this.I + ", " + this.S + ", " + this.B + ", " + this.I + ", " + this.S + ", " + this.B + ");"); otherwise: Info("Error - unknown event"); endcase } } } } child listbox LbInfo { .xauto 0; .yauto 0; .ytop 10; .firstchar 1; } } on extevent 1 { sendevent(this,Msg3,"Bye event"); this:sendevent(Msg4, true); } } rule void Info (string S input) { LbInfo.content[(LbInfo.itemcount + 1)] := S; LbInfo.topitem := LbInfo.itemcount; } on IdmTest extevent 1(object C) { Info("on IdmTest extevent 1"); sendevent(C, Msg2, 909); }