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;
}

Abbildung 17-19: Aktiver OLE-Server

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
  }
}

Abbildung 17-20: Verschicken von Ereignissen

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;
  }
}

Abbildung 17-21: Austausch von Benachrichtigungen nach Selektion des Apply-Pushbuttons

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);
}