Copyright © 2006-2023 MultiMedia Soft

How to use the API in a Windows service

Previous pageReturn to chapter overviewNext page

Microsoft Windows services enable you to create long-running executable applications that run in their own Windows sessions. These services can be automatically started when the computer boots, can be paused and restarted, and do not show any user interface. For security reasons, Windows Vista  introduced a new concept called “Session 0 Isolation” which doesn't allow these services to create other windows nor communicate with existing ones through messages; for this reason Windows services can only communicate with other applications through a client/server mechanism such as remote procedure call (RPC) or Windows Communication Foundation (WCF) .

 

The limitation described above means that the API cannot be used to create the following user interface objects on an eventual client application that would use the Windows service as an audio server:

 

Visual feedbacks like VU-Meter, Spectrum, Waveform and Oscilloscope: (although VU-Meter and Spectrum visualizations could be still implemented on the client side by managing the CallbackVuMeterValueChange delegate and by invoking the Spectrum.GetTable method)
Waveform analyzer (although partially supported for analyzing the sound's waveform and for creating bitmaps of the waveform in various graphic formats)
Waveform scroller
Graphic bars manager
Enhanced spectrum

 

If you should need using the mentioned objects, you would need to instance them directly into the client application.

 

The tutorial How to add the API to your projects instructs about steps needed for adding a reference to the API to a project: most of the steps described can be applied also when using the API for developing a Windows service. Once the reference to the API has been added, you can proceed normally as described inside the tutorial How to use the API in your projects.

 

The architecture of the multimedia engine (AdjMmsEng.dll and AdjMmsEng64.dll) of our audio components is internally based upon windows messages so, when instanced by one of our audio components from a Windows service, needs to be instructed to work differently: this can be achieved by invoking the ContainerIsWindowsService method; it's recommended invoking this method immediately before invoking the InitRecordingSystem method ONLY when the container application is a Windows service.

 

When an application needs to connect to the Windows service containing the API for starting audio related operations and for receiving feedback about results and operations advancement, there is the need to create a bidirectional communication channel. The API's setup package comes with a C# sample (named "WindowsService") implementing client/server communication through the usage of Windows Communication Foundation (or WCF which can be implemented quite easily when developing in C# and Visual Studio) between a very basic Windows service and a client application having its own user interface; the communication is implemented through "named pipes" which are well wrapped within the WCF framework. Depending upon your background, you could obviously use a totally different way to let your application communicate with the Windows service.

 

Let's see a step by step procedure that could be followed when the Windows service needs to operate as a server for a client application:

 

Creating the project of the Windows service in Visual Studio

Defining and implementing a duplex contract inside the Windows service

Allowing the API to be invoked from the client

Creating the WCF endpoint (server side)

Defining and implementing the same duplex contract inside the client application

A few words about events generated from secondary threads

Running the Windows service and launching the client application

 

 

Creating the project of the Windows service in Visual Studio

 

Visual Studio comes with a template for creating projects that implement Windows services; the MSDN documentation can be used as a starting reference for this purpose. Once the project is created, we can start adding needed references and namespaces for accessing functionalities that help implementing WCF communication.

The first operation is to add a reference to the System.ServiceModel assembly, then we can add its namespace to the code of the Windows service:

 

Visual C#

 

using System.ServiceModel;

 

 

 

Defining and implementing a duplex contract inside the Windows service

 

A duplex contract allows clients and servers to communicate with each other independently so that either can initiate calls to the other and consists of two one-way contracts between the client and the server and does not require that the method calls be correlated. Use this kind of contract when your service must query the client for more information or explicitly raise events on the client.

 

In our case the server side is implemented by the Windows service (hereinafter referred to as "server") and the client side is implemented by the application having its own user interface (hereinafter referred to as "client").

The first thing to do inside the code of the server is defining the interface that makes up the server side of the duplex contract.

 

Visual C#

 

// WCF contract for sending commands from the client application to the Windows service

[ServiceContract(CallbackContract = typeof(IAudioSoundRecorderServiceCallback), SessionMode = SessionMode.Allowed)]

public interface IAudioSoundRecorderService

{

 [OperationContract]

 void PipeGetRecordingDevicesCount(ref short nDevices);

 [OperationContract]

 string PipeGetRecordingDeviceName(short nDevice);

 [OperationContract]

 enumErrorCodes PipeStartRecording(short nDevice, string strOutputPathname);

 [OperationContract]

 enumErrorCodes PipeStop(short nDevice);

 [OperationContract]

 void PipeStartRecordingFromFile (string strOutputPathname, string strInputPathname);

 [OperationContract]

 void PipeFreeMemory ();

 [OperationContract]

 void PipeSoundPlay ();

 [OperationContract]

 void PipeSoundPause ();

 [OperationContract]

 void PipeSoundStop ();

 [OperationContract]

 void PipeClose ();

}

 

 

methods defined inside this contract, and implemented inside the server's code, will be invoked by the client application; as you can see, the interface of this contract, that we have named IAudioSoundRecorderService, is linked to another interface, named IAudioSoundRecorderServiceCallback, that makes up the client side of the duplex contract:

 

 

Visual C#

 

// WCF contract for sending events from the Windows service to the client application

[ServiceContract(SessionMode = SessionMode.Allowed)]

public interface IAudioSoundRecorderServiceCallback

{

 [OperationContract(IsOneWay=true)]

 void PipeSendRecordingSize (Int32 nSizeInBytes);

 [OperationContract(IsOneWay=true)]

 void PipeSendRecordingDuration (string strDuration);

 [OperationContract(IsOneWay=true)]

 void PipeSendRecResult (enumErrorCodes nResult);

 [OperationContract(IsOneWay=true)]

 void PipeSendPercentage (short nPercentage);

 [OperationContract(IsOneWay=true)]

 void PipeOperationCompleted (string strOperation, enumErrorCodes nResult);

}

 

 

Through this callback interface the server can send information to the client about the current status of the API, about advancement of lengthy operations and so on. As you may easily understand, methods of this interface will be implemented inside the code of the client.

 

 

Allowing the API to be invoked from the client

 

Now that the contract has been defined, we need to allow the API to be linked to the same. For this purpose we can create a class, arbitrarily named AudioSoundRecorderApiInstance, derived from our API AudioSoundRecorderApi.AudioSoundRecorderApiObj, that implements methods defined inside the IAudioSoundRecorderService interface of the contract:

 

Visual C#

 

// class that implements the API and contract interfaces for WCF communication with the client application

[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]

public class AudioSoundRecorderApiInstance : AudioSoundRecorderApi.AudioSoundRecorderApiObj, IAudioSoundRecorderService

{

 ....

}

 

 

Initialization of the API and connection to the client through the IAudioSoundRecorderServiceCallback callback interface can be implemented inside the constructor of the API:

 

Visual C#

 

// class that implements the API and contract interfaces for WCF communication with the client application

[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]

public class AudioSoundRecorderApiInstance : AudioSoundRecorderApi.AudioSoundRecorderApiObj, IAudioSoundRecorderService

{

 // declare the WCF pipe channel callback

 IAudioSoundRecorderServiceCallback callback = null;

 

 public AudioSoundRecorderApiInstance ()

 {

         // initialize the API

         this.ContainerIsWindowsService ();

         this.InitRecordingSystem ();

 

         ...

 

         // set the WCF pipe channel callback

         callback = OperationContext.Current.GetCallbackChannel<IAudioSoundRecorderServiceCallback>();

 }

}

 

 

Once defined, we can implement methods of the primary IAudioSoundRecorderService interface inside the server's code and add management of events generated by the API (in this case we will add management for the CallbackVuMeterValueChange delegate and for the CallbackForRecorderEvents delegate):

 

Visual C#

 

// class that implements the API and contract interfaces for WCF communication with the client application

[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]

public class AudioSoundRecorderApiInstance : AudioSoundRecorderApi.AudioSoundRecorderApiObj, IAudioSoundRecorderService

{

 // declare the WCF pipe channel callback

 IAudioSoundRecorderServiceCallback        callback = null;

 

 // declare management functions for callback delegates

 CallbackVuMeterValueChange addrCallbackVuMeterValueChange;

 CallbackForRecorderEvents addrCallbackForRecorderEvents;

 

 void VuMeterValueChangeCallback(short nPeakLeft, short nPeakRight)

 {

         // notify the client application through the instance of the WCF pipe channel callback

         callback.PipeSendVuMeterValues (nPeakLeft, nPeakRight);

 }

 void RecorderCallback(enumRecorderEvents nEvent, Int32 nData1, Int32 nData2, Int32 nDataHigh3, Int32 nDataLow3, Int32 nDataHigh4, Int32 nDataLow4)

 {

         // notify the client application through the instance of the WCF pipe channel callback

         switch (nEvent)

         {

                 case enumRecorderEvents.EV_REC_SIZE:

                         callback.PipeSendRecordingSize (nDataLow4);

                         break;

                 case enumRecorderEvents.EV_REC_DURATION:

                         {

                         Int32        nDurationInMs = nData1;

                         string        strDuration = this.FromMsToFormattedTime (nDurationInMs, true, true, ":", ".", 3);

                         callback.PipeSendRecordingDuration (strDuration);

                         }

                         break;

                 case enumRecorderEvents.EV_REC_STOP:

                         break;

                 case enumRecorderEvents.EV_REC_FINALIZATION_DONE:

                         callback.PipeSendRecResult ((enumErrorCodes) nData1);

                         callback.PipeOperationCompleted (m_OperationToExec, (enumErrorCodes) nData1);

                         break;

                 case enumRecorderEvents.EV_REC_PERC:

                         callback.PipeSendPercentage ((short) nData1);

                         break;

                 default:

                         return;

                 }

         }

 }

 

 // class constructor

 public AudioSoundRecorderApiInstance ()

 {

         // initialize the API

         this.ContainerIsWindowsService ();

         this.InitRecordingSystem ();

 

         // predispose the callback that will notify about vu meter changes

         addrCallbackVuMeterValueChange = new CallbackVuMeterValueChange(VuMeterValueChangeCallback);

         this.CallbackVuMeterValueChangeSet(addrCallbackVuMeterValueChange);

 

         // predispose the callback that will notify about recorder events

         addrCallbackForRecorderEvents = new CallbackForRecorderEvents(RecorderCallback);

         this.CallbackForRecorderEventsSet(addrCallbackForRecorderEvents);

 

         ... do other stuffs

 

         // set the WCF pipe channel callback

         callback = OperationContext.Current.GetCallbackChannel<IAudioSoundRecorderServiceCallback>();

 }

 

 public void PipeGetRecordingDevicesCount(ref short nDevices)()

 {

         nDevices = this.GetInputDevicesCount ();

 }

 public string PipeGetRecordingDeviceName(short nDevice)

 {

         string        strName = this.GetInputDeviceDesc (nDevice);

         return strName;

 }

 public enumErrorCodes PipeStartRecording(short nDevice, string strOutputPathname)

 {

         ... do other stuffs like predisposing encoding parameters

         

         return this.StartFromDirectSoundDevice(nDevice, 0, strOutputPathname);

 }

 public enumErrorCodes PipeStop(short nDevice)

 {

         return this.Stop ();

 }

 public void PipeStartRecordingFromFile (string strOutputPathname, string strInputPathname)

 {

         this.StartFromFile (strOutputPathname, strInputPathname);

 }

 public void PipeFreeMemory ()

 {

         // invoke the RecordedSound.FreeMemory method of the API

         this.RecordedSound.FreeMemory ();

 }

 public void PipeSoundPlay()

 {

         // invoke the RecordedSound.Play method of the API

         this.RecordedSound.Play ();

 }

 public void PipeSoundPause()

 {

         // invoke the RecordedSound.Pause method of the API

         this.RecordedSound.Pause ();

 }

 public void PipeSoundStop()

 {

         // invoke the RecordedSound.Stop method of the API

         this.RecordedSound.Stop ();

 }

 public void PipeClose ()

 {

         // dispose the API and deallocate its internal resources

         this.Dispose();

 }

}

 

 

In production code it would be obviously recommended implementing some error checking that would detect eventual error codes returned by API's methods.

 

 

Creating the WCF endpoint (server side)

 

At this point the server can create the WCF endpoint that allows eventual clients to connect and request services; a good place to create and open the endpoint is the OnStart override function of the Windows service while the OnStop override function is a good place to close it:

 

Visual C#

 

ServiceHost host = null;

 

protected override void OnStart (string[] args)

{

 eventLog1.WriteEntry("In OnStart");

 

 ...

 

 // create and open the WCF host and the endpoint for communication with client applications

 host = new ServiceHost(typeof(AudioSoundRecorderApiInstance), new Uri("net.pipe://localhost"));

 host.AddServiceEndpoint(typeof(IAudioSoundRecorderService), new NetNamedPipeBinding(), "PipeAudioSoundRecorder");

 host.Open();

}

 

protected override void OnStop ()

{

 eventLog1.WriteEntry("In OnStop");

 

 ...

 

 // close WCF host

 host.Close ();

}

 

 

The snippet above, which implements WCF communication through "named pipes", defines an arbitrary name PipeAudioSoundRecorder for the endpoint: you would be obviously free to define your preferred name.

 

 

Defining and implementing the same duplex contract inside the client application

 

After having created the client application, that in our case will be a simple Winform application with a very basic user interface, we can add code needed to implement the duplex communication with the server. Although not elegant from a coding style perspective, the easier way to define the same duplex contract inside the client application is to simply copy & paste the definition of the IAudioSoundRecorderService and IAudioSoundRecorderServiceCallback interfaces inside the namespace of the client application. A more elegant solution, less prone to eventual errors, would be creating an assembly containing the definition of both interfaces and adding a reference to the new assembly in both client and server projects.

 

Also in this case we need to add a reference to the System.ServiceModel assembly, then we can add its namespace to the code of our client's form.

 

Once copied the definition of both interfaces inside the client's code, we need to make the class of our main application's form compatible with our duplex contract; for this reason we will have to change the declaration of the form's class from the following code:

 

Visual C#

 

public partial class Form1 : Form

{

 public Form1 ()

 {

         InitializeComponent ();

 }

 

 ....

}

 

 

to the following code:

 

Visual C#

 

// class that inherits contract interfaces for WCF communication with the client application

[CallbackBehavior(ConcurrencyMode = ConcurrencyMode.Reentrant, UseSynchronizationContext = false)]

public partial class Form1 : Form, IAudioSoundRecorderServiceCallback

{

 public Form1 ()

 {

         InitializeComponent ();

 }

 

 ....

}

 

 

Once defined, we can implement methods of the callback IAudioSoundRecorderServiceCallback interface and connect to the endpoint inside the client's code:

 

Visual C#

 

// class that inherits contract interfaces for WCF communication with the client application

[CallbackBehavior(ConcurrencyMode = ConcurrencyMode.Reentrant, UseSynchronizationContext = false)]

public partial class Form1 : Form, IAudioSoundRecorderServiceCallback

{

 public Form1 ()

 {

         InitializeComponent ();

 }

 

 // the pipe channel proxy for WCF communication

 IAudioSoundRecorderService pipeProxy = null;

 

 bool        m_bIsServiceAvailable = false;

 

 short        m_nVuMeterValueLeft = 0;

 short        m_nVuMeterValueRight = 0;

 short        m_nPercentage = 0;

 Int32        m_nRecSizeInBytes = 0;

 string        m_strRecDuration = "";

 

 public void PipeOperationCompleted (string strOperation, enumErrorCodes nResult)

 {

         ... do something

 }

 public void PipeSendRecResult (enumErrorCodes nResult)

 {

         ... do something

 }

 public void PipeSendVuMeterValues (short nLeft, short nRight)

 {

         // store passed value into variables

         m_nVuMeterValueLeft = nLeft;

         m_nVuMeterValueRight = nRight;

 }

 public void PipeSendPercentage (Int16 nPercentage)

 {

         // store passed value into a variable

         m_nPercentage = nPercentage;

 }

 public void PipeSendRecordingSize (Int32 nSizeInBytes)

 {

         // store passed value into a variable

         m_nRecSizeInBytes = nSizeInBytes;

 }

 public void PipeSendRecordingDuration (string strDuration)

 {

         // store passed value into a variable

         m_strRecDuration = strDuration;

 }

 

 private void Form1_Load (object sender, EventArgs e)

 {

         // create the WCF service and related pipe channel

         var factory = new DuplexChannelFactory<IAudioSoundRecorderService>(new InstanceContext(this),

                                                 new NetNamedPipeBinding(),

                                                 new EndpointAddress("net.pipe://localhost/PipeAudioSoundRecorder"));

         pipeProxy = factory.CreateChannel();

 

         // invoke one of the contract's methods in order to see if the WCF connection with the server was successful

         short        nDevices = 0;

         try

         {

                 pipeProxy.PipeGetRecordingDevicesCount (ref nDevices);

         }

         catch (Exception)

         {

                 MessageBox.Show ("Audio Sound Recorder API Windows Service has not been started.");

                 Close ();

                 return;

         }

 

         m_bIsServiceAvailable = true;

 }

 

 private void Form1_FormClosing (object sender, FormClosingEventArgs e)

 {

         // close the WCF pipe channel

         if (m_bIsServiceAvailable)

                 pipeProxy.PipeClose ();

 }

 

 ....

}

 

 

Also the snippet above implements WCF communication through "named pipes", as seen for the server side code, and tries to establish a connection to the same endpoint named PipeAudioSoundRecorder.

You may notice that implementation of methods of the IAudioSoundRecorderServiceCallback callback interface store values of parameters into variables instead of using them to modify directly values of controls instanced on the user interface: this requirement is due to the fact that methods of the callback interface will be invoked from secondary threads a situation which doesn't allow accessing controls instanced on the user interface; as seen inside the sample of client application (named AudioRecWindowsServiceClient) installed by our setup package, a simple way to manage the situation would be instancing on the form a timer that would use these variables to update values of the controls on a timely basis.

 

 

A few words about events generated from secondary threads

 

As you will see inside the tutorial How to synchronize the container application with the API, some of the methods exposed by the API, such as the StartFromFile method, may start a lengthy operation requiring a remarkable execution time that could block the user interface of the container application for a while: this is not a problem when the API is instanced directly inside a regular Winforms application because the component can keep the user interface of the container application alive, allowing to manage eventual events such as the percentage of advancement but, when the API is instanced inside a Windows service (which is isolated from other windows due to the “Session 0 Isolation” mentioned at the beginning of this tutorial) and you need to refresh a progress bar inside the client application, this could be a problem.

 

The best approach in these cases is to allow the server (our Windows service) to start a brand new thread for invoking the lengthy method, allowing the user interface of the client application to be refreshed when one of its WCF callback's methods is invoked from the server, so the implementation of the PipeStartRecordingFromFile method of the IAudioSoundRecorderService primary interface:

 

Visual C#

 

// class that implements the API and contract interfaces for WCF communication with the client application

[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]

public class AudioSoundRecorderApiInstance : AudioSoundRecorderApi.AudioSoundRecorderApiObj, IAudioSoundRecorderService

{

 ...

 

 public void PipeStartRecordingFromFile (string strOutputPathname, string strInputPathname)

 {

         this.StartFromFile (strOutputPathname, strInputPathname);

 }

 

 ...

}

 

 

could be replaced by something like this:

 

Visual C#

 

// class that implements the API and contract interfaces for WCF communication with the client application

[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]

public class AudioSoundRecorderApiInstance : AudioSoundRecorderApi.AudioSoundRecorderApiObj, IAudioSoundRecorderService

{

 ...

 

 // worker thread for performing lengthy operations that would block the user interface, not allowing to update eventual progress bars

 string        m_OperationToExec = "";

 string        m_strOutputPathname = "";

 string        m_strInputPathname = "";

 

 public void ExecuteOperation()

 {

         enumErrorCodes nResult = enumErrorCodes.ERR_NOERROR;

         switch (m_OperationToExec)

         {

                 case "RecordFromFile":

                         nResult = this.StartFromFile (m_strOutputPathname, m_strInputPathname);

                         break;

         }

 }

 

 public void PipeStartRecordingFromFile (string strOutputPathname, string strInputPathname)

 {

         m_strOutputPathname = strOutputPathname;

         m_strInputPathname = strInputPathname;

 

         ... do other stuffs like predisposing encoding parameters

 

         // create and start the thread that allows executing the requested operation without blocking the user interface

         m_OperationToExec = "RecordFromFile";

         Thread workerThread = new Thread(ExecuteOperation);

         workerThread.Start();

 }

 

 ...

 

}

 

 

 

 

 

Running the Windows service and launching the client application

 

Once both the Windows service and the client application have been compiled, you can install the Windows service on the target machine: for this purpose there are several solutions described inside the MSDN documentation. As mentioned before, the API's setup package comes with a C# sample (named "AudioRecWindowsService.exe") that is automatically installed inside the system during the installation phase: this service is not started automatically so, in order to test it in conjunction with the client application (named "AudioRecWindowsServiceClient.exe") you need to start it manually using the "Services" applet that can be launched through the "Start" Windows menu by selecting "All programs/Administrative tools/Services". Once launched, you will see something similar:

 

 

Click the evidenced item "Audio Sound Recorder API Windows service" with the mouse's right button and select "Start" from the context menu: once the "Status" of the service is reported as "Started" you may launch the client application named "AudioRecWindowsServiceClient.exe" (more than one instance is allowed).

After closing the client application you may stop the Windows service by clicking again the evidenced item "Audio Sound Recorder API Windows service" with the mouse's right button and by selecting "Stop" from the context menu.