Next Previous Up Top Contents Index

A.2 An example COM server and client

A.2.2 Creating the server

Now we create the actual server application.

1. Choose File > New... from the main window.
2. Select Project and click OK.
The New Project wizard appears.
3. In the Project Type section, select "GUI Application (EXE)" and click Next.
4. Name the project RotNExample-server.
5. Make sure that the "Include any available templates" option is not checked.
6. Make sure that "Production mode" is selected in the Compilation Mode section of the Advanced Project Settings dialog.
This option will be set already if you have been following all steps from the start of this chapter.
7. Click Next to continue.
8. Choose the "Simple" libraries option and click Next to continue.
9. Choose the "Standard IO streams and string formatting" option from "IO and system support", and click Next.
10. Choose the "Win32 API" option from "GUI support", and click Next.
11. Choose the "OLE Automation" option from "OLE Automation support" and click Next.
12. Choose the "NoneÝ" option from "Database support" and click Next.
13. Click Finish.
The RotNExample-server project window appears.
14. In the new project's window, edit library.dylan, and add to the define library declaration the following line:
  use RotNExample-server-stubs;

15. Add the same line to the define module declaration in module.dylan.

To implement the IRotNExample interface, we will create a subclass of <IRotNExample>. Because <IRotNExample> was created with define dispatch-interface, we must use define COM-interface to create the subclass.

Note: The remainder of this section of the example involves adding code to RotNExample-server.dylan. A version of this file with all the code we add in this section is available in the Functional Developer installation folder, under Examples\Documentation\RotNExample\RotNExample-server.dylan. You may want to copy that file into place in your project folder rather than typing code in.

16. Add the following code to RotNExample-server.dylan.
define COM-interface <IRotN-implementation> (<IRotNExample>)
  slot IRotNExample/key :: 
    type-union(<integer>, <machine-word>) = 13;
end;

If you add this by hand, make sure not to put it after the top-level call to main.

We provide here an implementation for the IRotNExample/key slot, which was defined as a virtual slot in the superclass. This slot must accept the <machine-word> type, since any 32-bit integer which does not fit in the range of a Dylan <integer> will be passed as a <machine-word>.

The next task is to define the IRotNExample/encrypt and IRotNExample/decrypt methods. Although it is not obvious from the definition of <IRotNExample>, these methods must take as their first parameter the instance of <IRotN-implementation> they operate on, and return as a first result a COM error code.

17. Add the following code to RotNExample-server.dylan.
define method IRotNExample/encrypt 
  (this :: <IRotN-implementation>, pre :: <string>)
    => (result :: <HRESULT>, post :: <string>)

  if (instance?(this.IRotNExample/key, <integer>))
    let post = make(<string>, size: pre.size);
    for (char keyed-by index in pre)
      post[index] := rot-char-by-n(char, this.IRotNExample/key);
    end for;
    values($S-OK, post)
  else
    values($E-INVALIDARG, "")
  end if
end;

define method IRotNExample/decrypt (this :: <IRotN-implementation>, pre :: <string>) => (result :: <HRESULT>, post :: <string>) if (instance?(this.IRotNExample/key, <integer>)) let post = make(<string>, size: pre.size); for (char keyed-by index in pre) post[index] := rot-char-by-n(char, -this.IRotNExample/key); end for; values($S-OK, post) else values($E-INVALIDARG, "") end if end;

Note that this code is careful not to crash when IRotNExample/key is a <machine-word>. $S-OK represents success. $E-INVALIDARG is a generic failure representing some kind of invalid argument value.

The above method uses the rot-char-by-n function, which we must also add.

18. Add the following code to RotNExample-server.dylan.
define function rot-char-by-n 
  (char :: <character>, n :: <integer>) 
    => (r :: <character>)

  let char-as-int = as(<integer>, char);
  local method rot-if-in-range 
    (lower :: <integer>, upper :: <integer>) => ()

    if (lower <= char-as-int & char-as-int <= upper)
      char-as-int := lower + modulo(char-as-int - lower + n, 
                                    upper - lower + 1);
    end if;
  end method;
  rot-if-in-range(as(<integer>, 'a'), as(<integer>, 'z'));
  rot-if-in-range(as(<integer>, 'A'), as(<integer>, 'Z'));
  as(<character>, char-as-int)
end;

This function rotates alphabetic characters forward n positions, wrapping around if the character passes "Z". When n is 13, this implements the classic Rot13 cipher often used to hide offensive material on USENET.

In order to create our server, we must also create a COM class for it.

19. Add the following code to RotNExample-server.dylan.
You may want to copy the define coclass code from stubs.dylan in the RotNExample-server-stubs project and modify it.
define coclass $RotNExample-type-info
  name "RotNExample";
  uuid $RotNExample-class-id;
  default interface <IRotN-implementation>;
end coclass;

Now we simply have to add a Windows event loop as the main body of the server program.

20. Modify the main method in RotNExample-server.dylan to look like the following.
define method main () => ()
  if (OLE-util-register-only?())
    register-coclass($RotNExample-type-info,
      "FunDev.RotNExample");
  else
    let factory :: <class-factory>
      = make-object-factory($RotNExample-type-info);

    with-stack-structure (pmsg :: <PMSG>)
      while (GetMessage(pmsg, $NULL-HWND, 0, 0))
        TranslateMessage(pmsg);          
        DispatchMessage(pmsg);
      end while;     
    end with-stack-structure;

    revoke-registration(factory);
  end if;
end method main;

With this code in place, if the server is invoked from the command line with /RegServer as an argument, OLE-util-register-only? will return #t. The call to register-coclass creates a type library (with extension .TLB) and registers the type library and the server itself in the Windows registry.

Note that the server provides no way to exit. We can make it exit whenever our interface object is destroyed. This is a little simplistic, since it does not correctly handle the case in which two servers are created, but it will suffice for our example.

21. Add the following code to RotNExample-server.dylan.
define method terminate (this :: <IRotN-implementation>) => ()
  next-method();
  PostQuitMessage(0);  // Cause main event loop to terminate.
end;

The PostQuitMessage call causes the next call to GetMessage (in the main event loop) to return #f, and thus cause the program to exit.

22. Build the project with Project > Build.
During the build, you will be prompted for the location of the project file RotNExample-server-stubs.hdp.

Getting Started with Functional Developer - 31 MAR 2000

Next Previous Up Top Contents Index