.NET Remoting in Visual C++

Remoting is one of the three techniques available on the CLR for creating distributed applications. Roughly speaking, the times to use the three techniques are:

  • Use Web Services when you want to offer functionality to a variety of client applications, including those written by programmers who are not on your team (or don't even work for the same company) and those running on different platforms.
  • Use Enterprise Services when you need transactions, reliable messaging, pooling, and other enterprise features you may recall from COM+ or MTS.
  • Use Remoting for the highest performance, with the richest type system, when the same team is writing both the code that offers functionality and the code that consumes the functionality, or when you need specific Remoting functionality such as events.

In the future, Indigo will combine these three "stacks" so that you don't have to make a choice about the technology you use to create distributed applications. But for today, if you need what remoting offers, then build you application with remoting. You will be able to migrate to Indigo when the time comes.

The best way to develop a distributed application with remoting is to get it working as a single-machine application, then pull it apart into a remoted solution. You use configuration files to "redirect" requests for a particular class so that the framework executes the calls to that class on a different machine. The rest of your application is unaffected by the change.

The Remoted Class

Typically, a remoted object is part of a business or data layer. The code for this object executes not in the client process but on the server. Your reasons for this are generally to protect an asset such as a database, or to protect your intellectual property. If you are concerned about placing a database on a server with the SQL port open and allowing client applications to connect to it, you can offer a remoted data layer on the SQL machine, and make your connections only from this data layer. If you are concerned that a user might disassemble your application and discover proprietary information, leave those calculations and constants in a remoted business layer. Because it doesn't execute on the user's machine, they can't access the code to disassemble it.

The remoted calls in my sample are, of course, very simple. I have one method that takes a string and returns a string, and one that returns a data set. You can think of these as starting points for your real remoted objects.

Here are the implementations of two functions in a class called Greeter:

String* Greeter::Greet()
{
   return "Hello!";
}
String* Greeter::Greet(String* name)
{
   Text::StringBuilder* ret = new Text::StringBuilder(S"Hello, ");
   ret = ret->Append(name);
   ret = ret->Append(S"!");
   return ret->ToString();
}
Data::DataSet* Greeter::Records()
{
   Data::DataSet* ds = new Data::DataSet();
   ds->ReadXml("c:\\people.xml");
   return ds;
}

You can see that there's nothing in these implementations to say "this is a remoted class." The declaration of the class holds one small clue:

   public __gc class Greeter: public MarshalByRefObject
   {
   public:
      String* Greet(String* name);
      String* Greet();
      Data::DataSet* Records();
   };

The class inherits from MarshalByRefObject. That's because instances of this class are going to be provided to the calling code as references, not copies, and when the calling code calls a method of the class, it will execute on the server, not in the calling code's application domain. All of the parameters to and return values from your remoted methods must either be serializable or inherit from MarshalByRefObject. Both String and DataSet are serializable and will be passed and returned by value.

The XML file is just to spare you from yet another sample application that does something with the Northwind database. Filling a dataset from XML is a snap. Here's the XML I used while I was developing the sample:

<?xml version="1.0" encoding="utf-8"?>
<people>
   <person>
      <name>Kate Gregory</name>
      <website>www.gregcons.com</website>
   </person>
   <person>
      <name>Joe Somebody</name>
      <website>www.somewhere.org</website>
   </person>
   <person>
      <name>Sue Whoever</name>
      <website>www.wherever.net</website>
   </person>
</people>

The Client

The client can be anything you like. A WinForms application is the most popular, but you could write a console application, a service, or any other kind of application that might need to consume the functionality your remoted class offers. This sample has a pretty simple user interface:

The buttons use the object, which is not yet remoted, in pretty simple ways:

private: System::Void Greet_Click(System::Object *  sender,
                                  System::EventArgs *  e)
   {
      Greeting->Text = greet->Greet();
   }

private: System::Void GreetName_Click(System::Object *  sender,
                                      System::EventArgs *  e)
   {
      Greeting->Text = greet->Greet(Namebox->Text);
   }

private: System::Void GetRecords_Click(System::Object *  sender,
                                       System::EventArgs *  e)
   {
      Data::DataSet* ds = greet->Records();
      RecordsGrid->DataSource = ds->Tables->get_Item(0);
   }

Here, Greeting is a label on the surface of the form and greet is the soon-to-be-remoted object. It's a private member variable of the form, a Greeting::Greeter*, and it's initialized in the form constructor with a simple call to new:

greet = new Greeting::Greeter();

Up to this point I could have built (heck, I did build) the remoted object (Greeter) and the client in the same solution: one class library project called Greeting to hold Greeter and one WinForms project as the client. A project reference between them makes sure that Greeter methods are known to the client. This approach lets you test in the debugger and easily step through both the client and the remoted object.

The Server

You can host a remoted object in a Windows application, a console application (typically three lines long), a Windows service, or even IIS. A Windows application can monitor and display the information the remoted object uses, and can control the behavior of the remoted object. I just added another WinForm project to the solution I had going, and put a button and a label on it. The button (Listen) starts the hosting of the remoted object. You could just put the code in the form load handler if you prefer: I put it on a button so I could deliberately start the server. The handler is only two lines long:

private: System::Void Listen_Click(System::Object *  sender,
                                   System::EventArgs *  e)
   {
      Runtime::Remoting::RemotingConfiguration::Configure(
              "GreetingServer.exe.config");
      ListeningLabel->Text = "Listening";
   }

The only work here is loading the configuration file, GreetingServer.exe.config. All applications that run on the CLR can be controlled by a file called <application name>.config. You can create and edit this file with Notepad, but a much better plan is to add a configuration file to your project. In C++, you have to add a custom build step to copy the app.config file to <application name>.config in the Debug or Release directory (whichever you're building.) This is the way C# and VB behave, so for consistency I hand-tweak my C++ projects to behave that way. Here's what to do:

  • Right-click the project and choose Add, Add New Item. Choose a Configuration file.
  • Right-click the project and choose Properties. Expand the Build Events section and select Post Build Event.
  • Enter copy app.config "$(TargetPath).config" as the command line.
  • Click OK on the properties dialog.

From now on, each time you build a debug or release version of the application, the app.config file will be copied to the appropriate folder, named as name of the application (including the exe extension) followed by .config—this is just what you want.

The configuration file controls the remoting. That's why you only need to add a single line to the server, simply to load the file. Here's what the configuration file looks like for the server:

<configuration>
   <system.runtime.remoting>
      <application>
         <service>
             <wellknown mode="Singleton"
                        type="Greeting.Greeter, Greeting"
                        objectUri="Greeter" />
         </service>
         <channels>
            <channel ref="tcp" port="9555">
               <serverProviders>
                  <formatter ref="binary" typeFilterLevel="Full" />
               </serverProviders>
            </channel>
         </channels>
      </application>
   </system.runtime.remoting>
</configuration>

Most of this file is boilerplate: It will be the same for every application you put together. The parts that are bolded are under your control.

There are three options for the creation and lifetime management of the remoted objects used by client applications:

  • Server Activated (wellknown) Singleton objects. Only one object exists no matter how many requests for it come from clients. The server creates it, all requests are funneled through it, and when it goes unused for a span of time (default five minutes) the server deletes it.
  • Server Activated (wellknown) SingleCall objects. Only one object exists at a time on the server. As each call comes in the server creates an object, routes the call to it, and then deletes it immediately. These objects can't preserve state between calls.
  • Client Activated objects. Each request from the client (using new) creates another instance on the server, belonging to that client. It can preserve state for that client and will not be deleted until a span of time (default five minutes) elapses without that client using it.

Because Greeter has no need for state, there's no point in creating multiple copies of it, or repeatedly creating and deleting it. A wellknown Singleton is the right choice for Greeter, and the first bold line in the config file arranges that. Notice the type attribute—it's Namespace.Class,Assembly—and the objectUri—it's the string of your choice. You also can choose to use TCP or HTTP for your protocol (TCP is faster) and a binary or soap formatter (binary is faster, soap is a little easier to debug with).

Be careful as you create the config files for remoting. Like all XML, they are case sensitive and picky as can be.

Client Configuration File

To change the client from using the local version of Greeter to the remoted version, you need to add a configuration file (following the same steps as for the server) and add a line of code to load that file.

In the constructor for the client form, before the line that creates a new instance of Greeter and stores the pointer in greet, add this line:

Runtime::Remoting::RemotingConfiguration::Configure(
         "GreetingClient.exe.config");

The configuration file for the client must match the settings in the server configuration file. Here's what mine looks like:

<configuration>
   <system.runtime.remoting>
      <application>
         <client>
            <wellknown type="Greeting.Greeter, Greeting"
            url="tcp://111.22.33.44:9555/Greeter" />
         </client>
         <channel ref="tcp" port="0">
            <serverProviders>
               <formatter ref="binary" typeFilterLevel="Full" />
            </serverProviders>
         </channel>
      </application>
   </system.runtime.remoting>
</configuration>

Note the IP address in the URL: replace that with the machine where you plan to copy the remoted object and the server. The port in the URL must match the port in the server config file. The string after the port (/Greeter in this case) must match the object Uri in the server config file.

At this point, rebuild the entire solution (use rebuild solution to be sure all three projects build even if they haven't changed lately) so that the app.config files are copied to client.exe.config and server.exe.config. Now comes the fun part.

Find another machine on your network where you can copy files. Copy only these three files:

  • greetingserver.exe
  • greetingserver.exe.config
  • greeting.dll

Don't edit any of these files or change anything in them. Double-click greetingserver.exe to run it, and click the listen button. Now go back to your client machine. Make sure that client.exe.config has the right IP address in the URL. Double-click GreetingCient.exe to run it. Enter a name in the text box and click Greet. If you see a message, it's working!

Proving it

It's easy to wonder whether anything is really happening when you execute the client. After all, it used to call methods of the Greeter class before you added remoting. To prove what's happening, try deleting greeting.dll from the Debug directory where you're running the client. It should still work perfectly. Alternatively, put different code in the local copy of Greeter, like this:

String* Greeter::Greet()
{
   return "Hello from the local copy!";
}

Build the solution, but don't copy anything to the remote machine. Run your client again—you should see the old greeting, not the new. That proves you're running the remoted version of the class library.

There's more to cover about remoting. Watch for future columns about how identity passes over remoting, and how to raise and catch events across an application boundary.

About the Author

Kate Gregory is a founding partner of Gregory Consulting Limited (www.gregcons.com). In January 2002, she was appointed MSDN Regional Director for Toronto, Canada. Her experience with C++ stretches back to before Visual C++ existed. She is a well-known speaker and lecturer at colleges and Microsoft events on subjects such as .NET, Visual Studio, XML, UML, C++, Java, and the Internet. Kate and her colleagues at Gregory Consulting specialize in combining software develoment with Web site development to create active sites. They build quality custom and off-the-shelf software components for Web pages and other applications. Kate is the author of numerous books for Que, including Special Edition Using Visual C++ .NET.




Comments

  • this will work if you do the following

    Posted by srosenberg on 04/17/2008 03:27pm

    1. change the C++ to conform to Managed C++ (the /clr:oldsyntax is no longer required). 2. change the app.config file as follows: ....server app.config file xml version="1.0" encoding="utf-8" ?> /configuration> ------------------------------------- ...client app.config file ?xml version="1.0" encoding="utf-8" ?> /configuration> --------------------------------------------- 3. and last but not lease make sure when you call the... RemotingConfiguration::Configure("Client.exe.config", false); ...method one the client and server that you set the flag to false unless you are implementing some kind of encryption / security within the app.config file. Have fun!!

    Reply
  • this will work if you do the following

    Posted by srosenberg on 04/17/2008 03:25pm

    1. change the C++ to conform to Managed C++ (the /clr:oldsyntax is no longer required). 2. change the app.config file as follows: ....server app.config file ------------------------------------- ...client app.config file --------------------------------------------- 3. and last but not lease make sure when you call the... RemotingConfiguration::Configure("Client.exe.config", false); ...method one the client and server that you set the flag to false unless you are implementing some kind of encryption / security within the app.config file. Have fun!!

    Reply
  • this will work if you do the following

    Posted by srosenberg on 04/17/2008 03:24pm

    1. change the C++ to conform to Managed C++ (the /clr:oldsyntax is no longer required).
    
    2. change the app.config file as follows:
    
    ....server app.config file
    
    
    
        
        
            
                
                
                    
                        
                        
                        
                            
                        
                    
                
                
                
                
                    
                    
                    
                
                
                
                
            
        
    
    
    -------------------------------------
    ...client app.config file
    
    
    
        
        
            
              
              
                  
                  
              
              
              
               
                  
                
                  
                  
                  
              
            
        
    
    
    ---------------------------------------------
    
    3. and last but not lease make sure when you call the...
    
    RemotingConfiguration::Configure("Client.exe.config", false);
    
    ...method one the client and server that you set the flag to false unless you are implementing some kind of encryption / security within the app.config file.
    
    Have fun!!

    Reply
  • compiled solution?

    Posted by srosenberg on 04/15/2008 06:19pm

    Do you have a working solutions that uses VS2005 or 2003?

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • On-demand Event Event Date: September 10, 2014 Modern mobile applications connect systems-of-engagement (mobile apps) with systems-of-record (traditional IT) to deliver new and innovative business value. But the lifecycle for development of mobile apps is also new and different. Emerging trends in mobile development call for faster delivery of incremental features, coupled with feedback from the users of the app "in the wild." This loop of continuous delivery and continuous feedback is how the best mobile …

  • Packaged application development teams frequently operate with limited testing environments due to time and labor constraints. By virtualizing the entire application stack, packaged application development teams can deliver business results faster, at higher quality, and with lower risk.

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds