Partager via


Tutorial: Creating Windows Services in F#

Applies to: Functional Programming

Authors: Tomas Petricek and Jon Skeet

Referenced Image

Get this book in Print, PDF, ePub and Kindle at manning.com. Use code “MSDN37b” to save 37%.

Summary: This tutorial shows how to develop a Windows Service in F#. It creates a service that runs a chat server and makes it accessible using .NET Remoting.

This topic contains the following sections.

  • Introducing Windows Services
  • Defining a Public Service Interface
  • Creating a Windows Service
  • Testing a Service Interactively
  • Summary
  • Additional Resources
  • See Also

This article is associated with Real World Functional Programming: With Examples in F# and C# by Tomas Petricek with Jon Skeet from Manning Publications (ISBN 9781933988924, copyright Manning Publications 2009, all rights reserved). No part of these chapters may be reproduced, stored in a retrieval system, or transmitted in any form or by any means—electronic, electrostatic, mechanical, photocopying, recording, or otherwise—without the prior written permission of the publisher, except in the case of brief quotations embodied in critical articles or reviews.

Introducing Windows Services

Windows Services are applications that run as background processes and provide some functionality for other applications. When implementing long-running applications such as a web server or an application that communicates via sockets, they should be implemented as Windows Services. This way, the application can automatically start when the system boots and continue running without any user interaction.

Windows Services do not have a user interface. They typically communicate with other applications using the network. There are several options:

  • A service can expose functionality using a well-known protocol such as HTTP.

  • A service can use .NET Remoting or Windows Communication Foundation (WCF) to export interfaces that can be called by other .NET applications.

This tutorial creates a service that implements a simple chat room and makes it accessible to other .NET applications using .NET Remoting. This architecture could be used when developing an online chat application. This article uses remoting over the TCP channel, which means that the chat room service could run on a single backend server and could be accessed by frontend web servers that run a web application implementing a user interface and interaction with a database of users.

This tutorial uses the ChatRoom type that was developed in Step 2: Encapsulating Agents into Objects. The declaration of the ChatRoom type from that tutorial needs to be added to the project created in this article. Alternatively, a complete source code can be downloaded from the link at the end of this article.

Defining a Public Service Interface

A Windows service can be created by starting with a new "Console application" F# project (the service needs to be an executable file .exe). To expose functionality using .NET Remoting, the application needs to start by declaring a .NET interface that specifies the functionality. The clients connecting to the service can then ask the system for an implementation of the interface that marshals method calls to an object that lives on the server. The interface of the chat room is very simple. It allows the user to post messages to the chat room and to get contents of the chat room:

namespace ChatDemo
open System

/// Specifies members that chat room service provieds
type IChatRoomService = 
    abstract GetMessages : unit -> string
    abstract SendMessage : string -> unit

The next step is to create an object that implements the interface. The concrete object will be instantiated in the Windows Service but will be accessed from clients using remoting. To make the remote access possible, the object must inherit from the MarshalByRefObject type:

/// Concrete implementation of chat room service
type ChatRoomService() =
    inherit MarshalByRefObject()
    let agent = new ChatRoom()

    interface IChatRoomService with
        member this.GetMessages() = agent.GetContent()
        member this.SendMessage(msg) = agent.SendMessage(msg)

As already mentioned, the implementation uses the ChatRoom type that we created in an Tutorial: Creating a Chat Server Using Mailboxes. The service simply instantiates a new chat room and implements the IChatRoomService methods using methods provided by ChatRoom. Note that the service exposes only a synchronous version of the GetContent method. Unfortunately, asynchronous workflows (Async<'T>) cannot be automatically returned via remoting. An asynchronous communication could be implemented explicitly using delegates. More information about this topic can be found at the end of the article.

The above object is ready to be exposed from a Windows Service using .NET Remoting. The next section discusses how to do that.

Creating a Windows Service

The implementation of a Windows service consists of several steps. The main part is an object that inherits from ServiceBase. This object will be called by the system when the service starts and stops. In this tutorial, it will expose the chat room implemented earlier. Then, the service needs to contain a service installer, which defines its components and how to start them. Finally, the service can be installed using a simple command-line tool.

Implementing a Service Object

Before implementing the service object, the project needs references to the assembly that provides functionality for creating Windows services (System.ServiceProcess.dll) and a library for .NET Remoting (System.Runtime.Remoting.dll). Then, it is possible to create a simple object that inherits from ServiceBase and overrides the method that is executed when the service starts. The service implementation can be placed into a separate .fs file to separate the chat interface and the details of the Windows service implementation:

namespace ChatDemo

open System.ServiceProcess
open System.Runtime.Remoting
open System.Runtime.Remoting.Channels

type ChatWindowsService() =
    inherit ServiceBase(ServiceName = "FsChatService")
  
    override x.OnStart(args) =
        let channel = new Tcp.TcpChannel(42042)
        ChannelServices.RegisterChannel(channel, false)
        RemotingConfiguration.RegisterWellKnownServiceType
          ( typeof<ChatRoomService>, "FsChatService",
            WellKnownObjectMode.Singleton )
    override x.OnStop() = ()

When calling the inherited constructor of ServiceBase type, the type specifies the name of the service. The name will be needed later when creating the installer. When the system starts the service, it invokes the OnStart method. The above implementation of the method creates a new TCP channel running on the port 42042 and then exposes the ChatRoomService type via the created channel.

The last parameter of the RegisterWellKnownServiceType specifies that the object should be created as a singleton. All clients connecting to the service will get a reference to the same object. An alternative is to create a new object for every call. In the current scenario, the object contains the state of the chat room, so creating a new instance for every call would reset the chat room content.

The next step in creating a Windows service is to add an installer that instructs command-line tools how to install our service.

Adding the Service Installer

In a more complex service, the core part of the service can be a .NET library (.dll) and the installer that hosts the service can be a .NET executable (.exe). This tutorial keeps things simple by developing both components together in a single application. Before implementing the installer, the project needs a reference to System.Configuration.Install.dll, which contains the Installer class. The next snippet implements the installer type that inherits from this class:

open System
open System.ComponentModel
open System.Configuration.Install
    
[<RunInstaller(true)>]
type FSharpServiceInstaller() =
    inherit Installer()
    do 
        // Specify properties of the hosting process
        new ServiceProcessInstaller
          (Account = ServiceAccount.LocalSystem)
        |> base.Installers.Add |> ignore

        // Specify properties of the service running inside the process
        new ServiceInstaller
          ( DisplayName = "F# Chat Service", 
            ServiceName = "FsChatService",
            StartType = ServiceStartMode.Automatic )
        |> base.Installers.Add |> ignore

// Run the chat service when the process starts
module Main =
    ServiceBase.Run [| new ChatWindowsService() :> ServiceBase |]

The installer is implemented by the FSharpServiceInstaller type. The type is marked with the RunInstaller attribute, which instructs the command-line tool for installing services to use this type when performing the installation. In the above implementation, all work is done in the constructor of the type.

In the first step, the installer registers a ServiceProcessInstaller. This type instructs the system to use the current executable as the process that hosts the service and to run the executable using the local system account. The second command registers a single ServiceInstaller type. This informs the system about the services running as part of the process. The installer specifies the name that should appear in the list of Windows Services and defines that the service should automatically start with the system.

Finally, the source code also contains the Main module with the startup code that is executed when the process starts. It uses ServiceBase.Run to start a new instance of the chat service. This is all that's needed to create a fully functional Windows Service. Next, it can be installed using a simple command.

Installing a Service Using Command Line

After building the service, the InstallUtil.exe tool can execute the installation procedure defined by our FSharpServiceInstaller type. The tool is available in the Microsoft® .NET directory (usually C:\Windows\Microsoft.NET\Framework\v4.0.30319). The utility registers the service within the system so it needs to be executed using Administrator privileges. The service can be installed using the following command:

installutil ChatService.exe

To uninstall the service later, the tool can be invoked with the /u parameter:

installutil /u ChatService.exe

After installing the service, it will appear in the Computer Management console. Figure 1 shows a screenshot resulting from the installation of the service. The manager shows some of the properties that were specified in the installer (namely, the startup type and the user account used for running the service).

Figure 1. Computer Management console showing F# Chat Service

Referenced Image

After installing the service, it can be selected and started. Any exceptions that may happen when starting the service will appear in the system Event Log (in the Application log). Once the service is running, it can be called from a simple F# script to verify that it works as expected.

Testing a Service Interactively

As mentioned in the introduction, the chat service could be used, for example, by web servers that implement an online chat user interface. This tutorial focuses on developing the service, so it just demonstrates how to connect to the service using F# Interactive. The following listing uses .NET Remoting to get the instance of the IChatService interface that was declared earlier. To do that, the script first needs to reference the compiled .NET assembly that defines the interface. In F# Interactive, this is done using the #r directive:

#r @"bin\debug\chatservice.exe"
open System
open ChatDemo

// Get remote instance of the chat service object
let url = "tcp://localhost:42042/FsChatService"
let service = 
    Activator.GetObject(typeof<IChatService>, url) 
    :?> IChatService

// Test the chat service
service.SendMessage("Welcome to F# chat!")
service.SendMessage("Another chat message...")
service.GetMessages()

The object exposed using remoting is identified by an URL that specifies the machine and port where the service is running (localhost:42042) and the name of the service specified when calling RegisterWellKnownServiceType. An instance of the IChatService interface can be obtained by calling Activator.GetObject and specifying the requested type together with the URL.

The call returns an object that serves as a proxy to the object running in the service. After obtaining the object, the example calls methods of the interface to send messages and receive the content of the chat room. The messages are automatically serialized and could be also transferred over the network.

Summary

This tutorial demonstrated how to create a Windows Service that exposes functionality using .NET Remoting. Server-side applications usually run without any direct interaction with users, so they can be installed as Windows Services that run in the background. They can be also configured to start automatically when the system boots. A service can serve as a server (accessed for example using HTTP) or it can provide some API that can be called from other .NET applications.

This tutorial used .NET Remoting to expose a simple interface using a TCP channel. This means that other .NET applications can connect to the service (from the local system or over the network) and call methods provided by the service. This architecture can be used, for example, when developing a back-end server that is accessed from multiple front-end web servers.

Additional Resources

This tutorial uses a ChatRoom object that is developed in a separate tutorial. The object is developed using F# agents. This architecture is extremely suitable for developing server-side objects with a shared state that can be accessed concurrently by multiple clients. More information about F# server-side development in general and about agent-based architecture can be found in the following articles:

To download the code snippets shown in this article, go to https://code.msdn.microsoft.com/Chapter-2-Concurrent-645370c3

See Also

This article is written as a companion to Real World Functional Programming: With Examples in F# and C#. Chapters related to the content of this article are:

  • Chapter 13: “Asynchronous and data-driven programming” explains how asynchronous workflows work and uses them to write an interactive script that downloads a large dataset from the Internet.

  • Book Chapter 14: “Writing parallel functional programs” explains how to use the Task Parallel Library to write data-parallel and task-based parallel programs. This approach complements agent-based parallelism in F#.

  • Book Chapter 16: “Developing reactive functional programs” discusses how to write reactive user interfaces using asynchronous workflows and events. This approach is related to agents but more suitable for creating user interfaces.

The following MSDN documents are related to the topic of this article:

Previous article: Step 4: Creating a Web Chat Application

Next article: How to: Create Reusable Agents