VB.NET Applied Remoting

Rockford Lhotka

Magenic Technologies

May 11, 2003

About 18 months ago I wrote an article for this column comparing Web Services to Remoting (Remoting and XML Web Services in Visual Basic .NET). As time has passed, more and more people are developing applications based on the .NET Framework and there are an increasing number of questions about the role of remoting and how to make use of it. There are related questions about the use of COM+ and how to create client applications that call a ServicedComponent (a component running in COM+) across the network.

While there are numerous articles on the web discussing remoting, along with some books on the subject, I thought it worthwhile to revisit the topic in this column. In this article we’ll walk through the basic use of remoting for client/server and peer-to-peer communication. We’ll also examine how to use remoting to communicate between a client workstation and a ServicedComponent running in COM+ on a server.

In many ways remoting is the logical successor to DCOM (Distributed COM). It provides us with a set of capabilities roughly analogous to DCOM, and at the same time provides a set of capabilities similar to Web Services. To a large degree, remoting gives us the best of both worlds.

Using remoting we can create various classes of network-based application (typical VS.NET application types are shown in parenthesis):

Class

Client mode

Server mode

Client/server

Intelligent, stateful or stateless

(Windows Form or Web Form)

Stateless, providing services

(ASP.NET)

Automation

Intelligent, stateful or stateless

(Windows Form or Web Form)

Somewhat like an ActiveX EXE

(Windows Form)

Server-based

Intelligent, minimal state

(Windows Form or Web Form)

Stateful, maintaining data and providing services

(Windows Service)

Peer-to-peer

Intelligent, stateful

(Windows Form)

Intelligent, stateful

(Windows Form)

One key thing to notice is that in each case the client is intelligent. It is not possible to have a browser-based client interact directly with an object via remoting. A browser can interact with a Web Form in ASP.NET, and that Web Form can include code to interact with a remote object. In such a case, the Web Form is the intelligent client and as we’ll see, the code to use remoting in a Web Form is identical to what we’d write in a Windows Form.

The client/server scenario is very similar to how most people have used DCOM and MTS or COM+ in the past. The client may be a Windows Forms or Web Forms application that is calling stateless objects on the server as needed. In a VB.NET application these server-side objects may or may not be running in COM+ depending on whether we need the features of EnterpriseServices (see O COM+, Where Art Thou?).

The automation scenario is somewhat similar to the use of an ActiveX EXE in previous versions of VB. In this case the ‘server’ is actually a Windows Forms application. A user may or may not be directly interacting with the application, but other client applications can use remoting to interact with the application programmatically. These client applications may be on the same machine or on another machine across the network.

The server-based scenario is somewhat like client/server, except the objects on the server can be stateful and long-lived. While this particular architecture has fallen out of favor over the past few years, there are still applications where large, stateful and possible processor-intensive objects must run on a server. Remoting, like DCOM before it, supports this type of architecture if needed.

The peer-to-peer scenario is one where each client is also a server. In this case, clients can create and interact with objects on other clients. Each client is not only calling remote objects, but is exposing its own objects via remoting.

On Networking

Before we walk through implementations of each application scenario, we need to have a brief discussion about networking, IP addresses and how they affect remoting. The network that exists between any client and server can have a profound effect on how (or if) remoting will meet our needs.

Remoting uses standard TCP sockets to communicate from client to server (or server to client). This means that the client opens a connection to the server by using the server’s IP address (or domain name, which is translated to an IP address) on a specific port. Obviously the port used by the client must be the same as the port on which the server is listening.

If the server is hosted in ASP.NET, the port on which it listens is defined by IIS for the web server as a whole. The default is port 80, which means that remoting can easily use port 80 just like Web Services or other HTTP traffic. If the server is hosted in a Windows Forms application, a Windows Service or some other application type, it is up to the server programmer to specify the port. This means that for non-ASP.NET hosts, remoting can listen on virtually any unused port.

The point of all this is that remoting is firewall-friendly. Only one port needs to be open for clients to use remoting to communicate with our server. Contrast this with DCOM, where many ports had to be opened, and the Internet security benefits of remoting over DCOM become clear.

Also, remoting is connectionless, which means that each method call from client to server (or server to client) creates a connection across the network, makes the call and closes the connection.

It turns out that remoting is only logically connectionless. In reality, connections are sometimes pooled behind the scenes to improve performance. However, even with these pooled connections, the issues I’m discussing here are of critical importance.

What this means is that a client can only call a method on a server-side object if the client can reach the server through the TCP/IP network. This implies that the client has an IP address for the server, and that the IP address is reachable.

Figure 1. Client can reach server via network.

One way to think of this is that if the client can’t ping the server then remoting won’t work. (this isn’t always accurate, since the server may refuse pings, but in most cases it is a good test)

This also means that for the server to call the client the client must have an IP address that is reachable from the server. The server will call the client any time a server-side object raises an event or calls a delegate provided by the client. This is particularly common in the server-based object and peer-to-peer scenarios.

Again, one way to think of this is that if the server can’t ping the client then remoting won’t work.

Figure 2. Server can reach client via network.

While the server will get the client’s IP address automatically when the client calls the server, many times our client machines have virtual or non-routable IP addresses. This often happens when there is a firewall or NAT router between the client and server. In such a case, while the server’s IP address is typically well-known and fixed, the client’s IP address is virtual and is unreachable from the server.

Figure 3. Server can not reach client’s virtual IP address.

There is no official solution to this issue. The primary options are to write or find a connection-based remoting channel or to avoid remoting in favor of using raw TCP socket programming. There is one open-source connection-based TCP channel available at http://www.ingorammer.com/Software/OpenSourceRemoting/OpenSourceMain.html.

Now that we have a basic understanding of how networking can impact our use of remoting, let’s look at each of our remoting scenarios.

Client/Server Remoting

The client/server scenario is one in which the client (typically either Windows Forms or Web Forms) needs to call some code running on a server across the network. This server-side code may or may not be in COM+, so we’ll look at both situations. Fortunately they are quite similar.

For this type of situation it is easiest to host our server-side code in ASP.NET. ASP.NET offers configuration and management, as well as providing built-in security features.

The server-side code itself will be in a class in a Class Library project. In order to be available via remoting and also run on the server, the class needs to inherit from MarshalByRefObject. Alternately, if we need the features of EnterpriseServices, the class can inherit from ServicedComponent (which itself inherits from MarshalByRefObject).

For regular server-side code (not using COM+) we’d have a class like this:

Public Class ServerSide

Inherits MarshalByRefObject

End Class

For server-side code that should run in COM+ we’d have a class like this:

<Transaction(TransactionOption.Required), _

EventTrackingEnabled(True)> _

Public Class ServerInComPlus

Inherits ServicedComponent

End Class

Notice that in the EnterpriseServices version we also apply attributes to the class to indicate which specific EnterpriseServices features we are using. In this case we’re using distributed 2-phase transactions and we’re providing information so we can see the object’s status in the Component Services console.

Also remember that the assembly containing a ServicedComponent must reference System.EnterpriseServices.dll and must have a strong name. This means that the AssemblyInfo.vb file must have an entry pointing to a key file, as well as entries configuring the COM+ Application:

' Strong name

<Assembly: AssemblyKeyFile("c:\mykey.snk")>

' Enterprise Services attributes

<Assembly: ApplicationName("MyComPlusService")>

<Assembly: Description("My service")>

<Assembly: ApplicationActivation(ActivationOption.Library)>

Any public methods in either of these classes can be made available via remoting. In the code for this article each of these classes has an UpdateDB method to illustrate how this works. Both are transactional, the regular class relying on ADO.NET transactions and the ServicedComponent relying on COM+ transactions.

In this example we’re only updating a single database, so ADO.NET transactions are perfectly acceptable. It is important to note that the ADO.NET version will run about twice as fast as the COM+ version in this case. For a complete comparison of transaction performance characteristics see http://msdn.microsoft.com/architecture/default.aspx?pull=/library/en-us/dnbda/html/bdadotnetarch13.asp.

Now that we understand how to create both regular and COM+ server-side code, we can move on to expose the code to clients through remoting. The easiest and typically best way to do this is to create an empty ASP.NET project in VS.NET. In the code download you can refer to the vbAppliedRemotingService project.

Since the project will be hosting our server-side code, it should reference our Class Library projects. This causes VS.NET to automatically copy the compiled DLLs into the bin directory when the solution is built. In order to be available for remoting, our DLLs must be in the bin directory of the virtual root.

Add a web.config file to the ASP.NET project. It is in this configuration file that we’ll configure remoting to listen for client requests. We’ll define a section in web.config to configure remoting:

<system.runtime.remoting>

<application>

<service>

<wellknown

mode="SingleCall"

objectUri="ServerSide.rem"

type="RegularLibrary.ServerSide, RegularLibrary" />

<wellknown

mode="SingleCall"

objectUri="ServerInComPlus.rem"

type="ComPlusLibrary.ServerInComPlus, ComPlusLibrary" />

</service>

</application>

</system.runtime.remoting>

Notice how we’re defining well-known entry points for each of our classes.

Since our code is designed to be stateless we’re using the SingleCall mode. This means that each method call from a client will cause the creation of an object on the server for just that method. The object is not reused by subsequent method calls from clients. This is the same behavior as we get with Web Services or when using JIT activation with DCOM and COM+.

With this configuration we’ve now defined two URLs clients can use to call our objects:

http://localhost/vbAppliedRemotingService/ServerSide.rem

http://localhost/vbAppliedRemotingService/ServerInComPlus.rem

We can test these by navigating to these URLs in a browser with ‘?wsdl’ appended to the URL. The result in the browser should be XML describing the service.

Figure 4. WSDL for a remote object.

Before we can use the ServerInComPlus from a client we need to register the assembly with COM+. The regular VB.NET class, ServerSide, is all ready for use, but the ServicedComponent must be registered first.

The .NET Framework will attempt to automatically register the assembly with COM+ the first time the assembly is called. The challenge is that our server-side code is running under the ASP.NET user account, which doesn’t have security rights to register the assembly. This means we must register it manually before any client attempts to call the service.

Registering an assembly with COM+ is done by using the regsvcs.exe command line utility. Open a Visual Studio .NET Command Prompt window and navigate to the bin directory in our ASP.NET virtual root. Then run the utility:

> regsvcs ComPlusLibrary.dll

With this done, ASP.NET will be able to invoke the ServicedComponent within COM+ when it is called by a client application.

The client for remoted services will typically be either a Windows Forms or Web Forms application. Either way, the client application needs to configure itself to use the server-side objects through remoting. This can be done in the application configuration file or in code. I typically configure the client through code, since it provides a more controlled environment.

Remoting needs to be configured once, as the client application starts up. In a Windows Forms application this is typically done in the Form_Load event for the main form. In a Web Forms application this is typically done in the Application_Start event in Global.asax. Either way the code is the same. First we configure the HTTP channel to use the binary formatter:

' this configures the http channel to use the faster binary formatter

Dim properties As New Hashtable()

properties("name") = "HttpBinary"

ChannelServices.RegisterChannel(New HttpChannel(Nothing, _

New BinaryClientFormatterSinkProvider(), Nothing))

The reason for this is that the BinaryFormatter is faster and consumes less bandwidth than the default SoapFormatter. To transfer the same data across the network, the BinaryFormatter generates about 30% the number of bytes as the SoapFormatter. Additionally, it is much less CPU intensive to convert our data into a binary format than into a text representation such as XML to send it and then convert it back to binary data on the receiving end.

Once we’ve configured the HTTP channel, we specify which classes are to be invoked via remoting:

' the following configures for remoting access

RemotingConfiguration.RegisterWellKnownClientType( _

GetType(ComPlusLibrary.ServerInComPlus), _

"http://localhost/vbAppliedRemotingService/ServerInComPlus.rem")

RemotingConfiguration.RegisterWellKnownClientType( _

GetType(RegularLibrary.ServerSide), _

"http://localhost/vbAppliedRemotingService/ServerSide.rem")

From this point forward in our client code, any attempt to create an instance of either ComPlusLibrary.ServerInComPlus or RegularLibrary.ServerSide will automatically trigger the use of remoting to create the object on the server.

This means we can write code like this to call the regular component:

Dim svc As New RegularLibrary.ServerSide()

svc.UpdateDB()

Notice that there’s no special code here. The remoting subsystem is already configured, so our attempt to create a ServerSide object is automatically routed through remoting so when we call UpdateDB the ServerSide object is created on the server, the method is invoked, the result returned and the object destroyed.

The same is true for calling our ServicedComponent:

Dim svc As New ComPlusLibrary.ServerInComPlus()

svc.UpdateDB()

The basic process is the same here, except that on the server this code will run within COM+.

Whether the server-side code is a simple VB.NET object or a ServicedComponent running in COM+, remoting provides us with a good mechanism for client/server development.

Automation

Microsoft Office introduced the concept of OLE Automation in the mid-90’s. With OLE Automation it is possible to create a client application that uses COM to call objects in another ‘server’ application such as Word or Excel. If the server application wasn’t already running, OLE Automation would automatically start it in its own process.

VB followed suit, allowing us to create our own ‘server’ applications in the form of an ActiveX EXE. We could then create client applications that used COM to call objects in our ActiveX EXE.

There is no direct equivalent to an ActiveX EXE in VB.NET. Nor does remoting completely provide the functionality we need to replicate OLE Automation. Remoting allows us to create client applications that call objects in a server application, and it allows us to create a server application that exposes objects. However, there’s no mechanism for the client to start the server application if it isn’t already running, nor is there a mechanism for the client to automatically figure out the IP port on which the server is listening.

A server application is typically a Windows Forms application that is configured as a remoting listener. This means that it exposes one or more classes for use by client applications.

The classes it exposes should be placed in a separate Class Library project. This is important, because not only does the server application need to reference these classes, but any client application does as well. Unlike in VB6 where we could reference a COM EXE, in VS.NET we can only reference DLLs.

Creating a server application is also complicated by the fact that all remoting calls will run on background threads. Code running on a background thread can not directly interact with any Windows Forms UI components such as a Form or TextBox control. I discussed this issue and one solution in Implementing a Background Process in Visual Basic .NET.

The full code for the server is in the AutomationServer project in the download. The key is that the main form configures remoting as the application starts:

' set application name (virtual root name)

RemotingConfiguration.ApplicationName = mname

' set up channel to listen for clients

ChannelServices.RegisterChannel(New TcpServerChannel(mPort))

' register our class

RemotingConfiguration.RegisterActivatedServiceType( _

GetType(AutomationLibrary.Application))

We specify the application’s name and port, which define the URL clients will use to interact with the server application. Note that this implies that our project references the System.Runtime.Remoting.dll assembly. In our example the URL is:

tcp://localhost:15123/AutomationServer

Notice that it is using the tcp:// protocol, because our server is listening using the TcpServerChannel rather than the HttpServerChannel.

It is also important to note that we are registering an activated type rather than a wellknown type here. An activated type is stateful and long-lived. A client can make many method calls to the same activated object, which is the type of behavior we would get from a COM object in an ActiveX EXE.

In the code we also use delegates and the Invoke method on the form in order to safely take the client’s method calls and have them interact with the UI. The SafePrint method, in particular, runs on a background thread to service the client’s request, and it uses the form’s Invoke method to transfer the call to the UI thread where it can safely update the display:

Public Sub SafePrint(ByVal Text As String)

Dim del As New PrintDelegate(AddressOf Print)

Me.Invoke(del, New Object() {Text})

End Sub

The actual Application class, which is called directly by the client, has a reference to this SafePrint method through a delegate. When we want to update the UI the Application object invokes this delegate, thus calling the SafePrint method. For instance, if the client calls the SayHi method, the delegate is invoked to print some text in the UI:

Public Sub SayHi(ByVal From As String)

mPrint.Invoke(From & " says hi")

End Sub

The AutomationClient is also a Windows Forms application. It has a reference to the AutomationLibrary assembly so it has access to our Application class. It also references System.Runtime.Remoting.dll so we can make use of remoting.

As usual, the first thing we do when the application starts is to configure remoting:

' the following configures for remoting access

RemotingConfiguration.RegisterActivatedClientType( _

GetType(AutomationLibrary.Application), _

"tcp://localhost:15123/AutomationServer")

Here we have configured remoting to know that the AutomationLibrary.Application class is an activated type that is found at the URL. If the server application is not running when this code executes we’ll get a runtime exception. This is a key difference from the ActiveX EXE world, where the server would be automatically launched if it were not already running.

Also note that the port number we use here must match the port number on which the server application is listening. If they don’t match the client won’t find the server and we’ll get the same runtime exception that the server can’t be found.

Once remoting has been configured, interacting with the server application is straightforward. We simply create an instance of the Application class and call methods on it:

mApp = New AutomationLibrary.Application()

mApp.SayHi(txtName.Text)

mApp.SayBye(txtName.Text)

Remember that each method call is going to the same object in the server application. This works just like OLE Automation, where the objects in the server application are stateful and long-lived.

Though this application just displays text on the server application’s form, it could be enhanced by adding extra methods that perform more sophisticated automation functions. It is quite possible to provide client applications with a programmatic interface that entirely drives the functionality of the server application.

Server-Based Objects

Sometimes we may have applications which need to maintain substantial state on the server over a period of time. In this case, a model relying on stateless server-side objects is not ideal. Instead, we need a model that has stateful server-side objects.

Remoting provides us with two options in this case. The first and most common is to use activated objects like we did in the previous automation scenario. The other possibility is to use a singleton object on the server.

A singleton object exists on the server and is shared by all clients. As the name implies, there is one such object at a time. Contrast this with activated objects, where there are many such objects and they are tied to the clients that created them.

Activated objects are typically easier to deal with since they provide some level of isolation and minimize issues around multi-threading. Since each object is tied to a specific client, we don’t have to worry about multiple clients interacting with the same object’s data. Also, unless the client application itself is multi-threaded, we don’t need to worry about multiple threads running within the objects at the same time.

A singleton object is shared by all clients, so there is no implied isolation at all. This is complicated by the fact that multiple method calls from different clients may be executing within the object on different threads at the same time. It is up to us to implement isolation and manage these threads within our own code.

In either case, our server needs to provide a consistent Windows process in which our server-side objects can reside. While ASP.NET can act as a remoting host for stateful objects, it is not ideal. It is relatively easy to get ASP.NET to terminate its current AppDomain and start up a new one, thus losing all our objects. All we need to do, for example, is edit the web.config file to trigger this restart process.

If we are counting on maintaining long-lived server-side objects we are better off creating our own Windows Service as a host application on the server. This provides us with more control over when or if the AppDomain or process should be closed or restarted.

In the Remoting and XML Web Services in Visual Basic .NET article I demonstrated how to create a Windows Service host for remoting. In the code for this article you’ll find the ObjectServer project, which is a Windows Service. The key thing to note in this project is how the server-side classes are configured for remoting:

RemotingConfiguration.RegisterWellKnownServiceType( _

GetType(ObjectLibrary.Singleton), _

"Singleton.rem", _

WellKnownObjectMode.Singleton)

RemotingConfiguration.RegisterActivatedServiceType( _

GetType(ObjectLibrary.StateFul))

First we configure a class as a Singleton. This is a well-known type, but only one object will exist at a time for all clients. Next we configure an activated stateful object. In this case, clients will create their own server-side objects.

The StateFul class typically won’t have to worry about isolation of data or threading. The only requirement, as always, is that it inherit from MarshalByRefObject:

Public Class StateFul

Inherits MarshalByRefObject

Private mState As String

Public Sub SetState(ByVal Data As String)

mState = Data

End Sub

Public Function GetState() As String

Return mState

End Function

End Class

The Singleton class is more complex. At a minimum it must deal with multi-threading issues. We might also need to implement some type of isolation if we care to keep the data from one client separate from the data of other clients. In this example, we’ll use the SyncLock statement to handle the threading issue and we’ll allow the data to be shared across all clients:

Public Class Singleton

Inherits MarshalByRefObject

Private mState As String

Public Sub SetState(ByVal Data As String)

SyncLock Me

mState = Data

End SyncLock

End Sub

Public Function GetState() As String

SyncLock Me

Return mState

End SyncLock

End Function

End Class

The SyncLock statement is used to ensure that only one thread can run the code within the block at a time. Any other threads attempting to enter the SyncLock block are suspended until the current thread exits the block.

This is the simplest example of thread management. Implementing a singleton class for a real project is typically far more complex than this.

Note that the Singleton and StateFul classes are both in a Class Library project, ObjectLibrary. This allows them to be referenced by both the Windows Service and client projects.

Before trying to build or run a client, make sure to run the installutil.exe command line utility to install the Windows service and then start the service.

The client may be Windows Forms or Web Forms. In our example it is a Windows Forms project that calls both the Singleton and StateFul objects. By running multiple client applications at the same time you can experiment with the differences in state management between the singleton and activated object types.

When the client starts up it needs to configure remoting so it knows that the Singleton and StateFul classes are to be called in a remote server:

RemotingConfiguration.RegisterWellKnownClientType( _

GetType(ObjectLibrary.Singleton), _

"tcp://localhost:15124/ObjectServer/Singleton.rem")

RemotingConfiguration.RegisterActivatedClientType( _

GetType(ObjectLibrary.StateFul), _

"tcp://localhost:15124/ObjectServer")

We also create a instances of the StateFul and Singleton objects at this point:

mSingleton = New ObjectLibrary.Singleton()

mStateFul = New ObjectLibrary.StateFul()

These two statements look the same but have very different semantics. When we ‘create’ the Singleton object, all we’re doing is creating a reference to the shared server-side object. All clients reference the same exact object on the server. When we create the StateFul object on the other hand, a new server-side object is created for our use. No other client has access to this new object.

The rest of the client code calls methods on these objects to set or retrieve their values. If you run two client applications at the same time you can see how changing the Singleton value in one also changes the value in the other client. You can also see how changing the StateFul value in one client has no impact on the other client at all. Each client has its own StateFul object.

Peer-to-Peer Remoting

The final scenario we’ll discuss is peer-to-peer (P2P). In this case each client is also a server. The primary difference between this model and the previous examples is that our application will configure remoting to listen for client requests and yet it will also invoke classes through remoting.

In most P2P applications, a given peer will connect to multiple other peers at the same time. This means we can’t use the same type of static configuration for remoting that we’ve used so far. In our previous examples we’ve statically bound a class (such as the StateFul class in the previous example) to a specific remote server name and port. In a P2P environment this won’t work, because we need to create remote objects from the same class on multiple peers.

To accomplish this we won’t register the remote class with remoting at all. Instead we’ll use a special method from the .NET Framework to create the remote object. This is somewhat like using the CreateObject method in VB6 to create an object using DCOM.

In VB.NET this method is Activator.GetObject, and it takes two parameters. The first is the type of the object we want to create, and the second is the URL for the remoting host where the object should be created. This is the same URL we would use if we were doing static configuration.

The Peer project in the code download implements the P2P model. It allows us to specify the port on which the peer will listen for other peers, and we can start and stop listening. When we start listening, we configure remoting:

' set our app name

RemotingConfiguration.ApplicationName = "Peer"

' create the channel

mChannel = New TcpChannel(CInt(txtPort.Text))

ChannelServices.RegisterChannel(mChannel)

' expose our listener class

RemotingConfiguration.RegisterWellKnownServiceType( _

GetType(Listener), _

"Listener.rem", _

WellKnownObjectMode.SingleCall)

This is the same process we used to configure remoting in our Windows Service earlier in the article. We set the application name, configure a TcpChannel to listen on the specified port and register our Listener class so it is available to other peers through remoting. The URL defined by this code is:

tcp://localhost:port/Peer/Listener.rem

Substitute ‘port’ for the port number provided by the user to the application.

Once the peer is listening, other peers can connect to it and interact with a Listener object. The Listener object exposes a property so we can retrieve the user-entered name of the peer.

To retrieve the name of another peer, we need to know the name or IP address of that peer and the port on which it is listening. Using that information we can construct the URL for the remote peer and we can create a Listener object:

Dim listener As Listener

Dim url As New UriBuilder()

With url

.Host = txtRemoteServer.Text

.Port = CInt(txtRemotePort.Text)

.Scheme = "tcp"

.Path = "Peer/Listener.rem"

End With

listener = Activator.GetObject(GetType(Listener), url.ToString)

txtRemoteName.Text = listener.Name

I’m using the UriBuilder class from the .NET system class library to help create the URL. We then use that URL to create a remote Listener object by calling Activator.GetObject. If the remote peer can’t be found, this call will result in an exception, so in a robust application we’d wrap this with a Try..Catch block.

Once we have a reference to the remote Listener object we can uses its Name property to retrieve the name of the remote peer.

You can try this by running two Peer applications on your machine. Just make sure each of them is set up to listen on a different port number. Use localhost for the server name and you should be able to have them trade names.

Conclusion

Remoting sits in the intersection between DCOM and Web Services. It provides many of the features of both technologies and is uniquely suited for building many types of applications in today’s distributed world. These include client/server, server-based, automation and peer-to-peer applications.