So we’ve upgraded our web farm to Windows Server 2003 x64, and in the process established IIS to run in native x64 mode. That part went like butter.
The problem is that portions of our web application rely on some various 3rd party 32bit objects. Of course, while x64 windows itself can host a 32bit process, a 64bit process can not instantiate a 32bit process. That is to say, while IIS is running in x64 mode, it can’t load and make calls to a 32bit object.
After contacting the various vendors of our 3rd party objects, we were told that it would be months, years or never for the release of native x64 versions of these objects. Of course, that just won’t do. Here is how we were able to resolve the issue and still maintain all of the functionality we had with the 32bit version of our software in 64bit mode:
.Net Remoting to the Rescue
Knowing that windows itself could spawn a 32bit process, and that process would have access to the 32bit objects, the decision was made to create a windows service to host the 32bit objects. With the windows service responsible for the creation of the objects and calling the methods of those objects, then it was simply a matter of marshalling the calls to the windows servers across the x64 <-> x86 boundary.

What we end up with is actually three separate projects. We’ve got our original web solution project, we’ve got a new Windows Service project (which is the remoting host) and we’ve got a shared assembly between the two that holds the interface contract (and the result object that’s marshaled back, but we’ll get into that later).
Working from the Server to the Client
All of the samples and examples that I was able to find using Google and MSDN dealt with making calls from the client and executing work on the server, but no real emphasis was given to returning usable and tangible results to the client from the server. One of the things I discovered along the way was exceptions are not accurately marshaled across the machine boundary. More importantly, if you raise an exception from the remote service, then that service no longer seems to respond to subsequent requests.
Creating the shared interface & assembly
In looking back at the diagram above, It’s apparent that the client has to know how to talk to the service, and the service needs to know how to talk to the client. That is to say that when the server calls something an apple, then the client has to know what an apple looks like. If the client didn’t know what an apple looked like on the server, then it wouldn’t know if it should put it in the orange or lemon basket.
The best way to describe an object between two separate systems is with the usage of a shared interface. So what we did was to create a separate lightweight assembly to house the common interfaces that both the client and server can consume and use.
Dealing with our results first
During the process of developing the solution, we found that if we raised an exception from our service, that exception was not marshaled to the client. Instead a remoting exception was raised. Another caveat of throwing an exception from the service was that after the exception was called, the service would fail to respond to subsequent requests. Due to this discovery, it was decided that we would return a common “Result” object from all of our remote method calls.
1: /// <summary>
2: /// provides a common generic interface for all remoting return calls
3: /// </summary>
4: /// <typeparam name="T"></typeparam>
5: public interface IRemoteResult<T>
6: {
7: /// <summary>
8: /// Gets a value indicating whether this <see cref="IRemoteResult<T>"/> result is successful or failed.
9: /// </summary>
10: /// <value><c>true</c> if success; otherwise, <c>false</c>.</value>
11: bool Result { get; }
12: /// <summary>
13: /// Gets the value of the result.
14: /// </summary>
15: /// <value>The value.</value>
16: T Value { get; }
17: /// <summary>
18: /// Gets the error message.
19: /// </summary>
20: /// <value>The error message.</value>
21: string ErrorMessage { get; }
22: /// <summary>
23: /// Gets the result exception.
24: /// </summary>
25: /// <value>The result exception.</value>
26: Exception ResultException { get; }
27: }
28:
29: /// <summary>
30: /// Remoting return call where return Value is a byte array
31: /// </summary>
32: public interface IRemoteResultByteArray : IRemoteResult<byte[]> {}
33:
34: /// <summary>
35: /// Remoting return call where return Value is a string
36: /// </summary>
37: public interface IRemoteResultString : IRemoteResult<string> {}
The IRemoteResult is the base interface for the result object. It takes a generic type param which is the return type of the data that is consumed by the client from the server. Notice that I created the IRemoteResultByteArray and IRemoteResultString interfaces based on the IRemoteResult. This allows me to incorporate a strongly typed result value without having to maintain definitions of each.
Also notice that the “RemotingResult” interface contains a true or false representing the result of the remote call, as well as providing the ability to marshal the exception raised by the remote method internally to the client. Of course, if the result is true, then the Value will contain the data returned from the remote call.
Now that we have the interface contract for the various Result objects, we can create the concrete implementation of those objects.
1: [Serializable]
2: public class RemoteResultByteArray : IRemoteResultByteArray
3: {
4: public bool Result { get; set; }
5: public byte[] Value { get; set; }
6: public string ErrorMessage { get; set; }
7: public Exception ResultException { get; set; }
8: }
9:
10: [Serializable]
11: public class RemoteResultString : IRemoteResultString
12: {
13: public bool Result { get; set; }
14: public string Value { get; set; }
15: public string ErrorMessage { get; set; }
16: public Exception ResultException { get; set; }
17: }
Notice that each of the concrete implementations are marked with the Serializable attribute. This allows .Net to marshal the result object across the machine boundary.
Now that we know what to expect from the remoting service, let’s talk about the “what to do”
Creating the Shared Service Contract
One of the 32bit components that we’re working with is the ActivePDF Toolkit It is basically a product that allows us to manipulate PDF documents. One of the features that we utilize with the toolkit is the ability to take several PDF documents and merge them into one document “on the fly”.
I’m going to show you the code for our actual implementation of the merge process as an example.. but the long and short of it is, the shared service contract is what defines the method calls to your client that your remoting server will process.
1: public interface IPdfToolkit
2: {
3: /// <summary>
4: /// Merges the source PDF Documents and returns the final merge as a byte array
5: /// </summary>
6: /// <param name="info">The PDFInfo object that describes the final document.</param>
7: /// <param name="pdfFiles">The PDF files.</param>
8: /// <returns></returns>
9: IRemoteResultByteArray MergePDFToArray(PDFInfo info, List<PDFMergeInfo> pdfFiles);
10:
11: /// <summary>
12: /// Merges the source documents into a single PDF and returns the file name / path to the created output document
13: /// </summary>
14: /// <param name="info">The PDFInfo object that describes the final document.</param>
15: /// <param name="pdfFiles">The PDF files.</param>
16: /// <returns></returns>
17: IRemoteResultString MergePDFToFile(PDFInfo info, List<PDFMergeInfo> pdfFiles);
18: }
So we’ve got a service contract with two methods, one merges a list of pdf’s to a single PDF and returns the result as a byte array, and the other returns a string, which is the path to the resulting file. PDFInfo and PDFMergeInfo are supporting structures which are also defined in the shared assembly:
1: [Serializable]
2: public struct PDFInfo
3: {
4: public string Title;
5: public string Subject;
6: public string Author;
7: public string Keywords;
8: public string OutputPath;
9: public bool DeleteAfterMerge;
10: public bool IncludeSeperatorPage;
11: public bool CreateSectionBookmarks;
12:
13: public PDFInfo(string title, string subject, string author, string keywords, string outputPath, bool deleteAfterMerge, bool includeSeperatorPage, bool createSectionBookmarks)
14: {
15: Title = title;
16: Subject = subject;
17: Author = author;
18: Keywords = keywords;
19: OutputPath = outputPath;
20: DeleteAfterMerge = deleteAfterMerge;
21: IncludeSeperatorPage = includeSeperatorPage;
22: CreateSectionBookmarks = createSectionBookmarks;
23: }
24: }
25:
26: [Serializable]
27: public struct PDFMergeInfo
28: {
29: public string FileName;
30: public string FriendlyName;
31: public bool Exists;
32: public bool Delete;
33: }
With this new shared service contract, the client and server can both agree on what work will be performed, and what the results of that work will be. So let’s move on to the Remoting Service itself…
Creating The Remoting Object (what actually does the work)
In the “Service” project we need to create an object that actually does the work we want it to do. This is the object that makes the calls into the 32bit components, does the tasks it needs to do, and returns the results that we agreed upon in the shared assembly. Since we’re working with the PDF Toolkit, I’ll go ahead and show you that…
1: public class PdfToolkit : MarshalByRefObject, IPdfToolkit
2: {
3: /// <summary>
4: /// Merges the source documents into a single PDF and returns the file name / path to the created output document
5: /// </summary>
6: /// <param name="info">The PDFInfo object that describes the final document.</param>
7: /// <param name="pdfFiles">The PDF files.</param>
8: /// <returns></returns>
9: public IRemoteResultString MergePDFToFile(PDFInfo info, List<PDFMergeInfo> pdfFiles)
10: {
11: var result = new RemoteResultString {Result = true};
12: if (string.IsNullOrEmpty(info.OutputPath))
13: {
14: result.Result = false;
15: result.ErrorMessage = "The OutputPath property can not be empty or null";
16: result.ResultException = new ArgumentException(result.ErrorMessage);
17: return result;
18: }
19:
20: try
21: {
22: // [... snip: Code that loops through the supplied list of files and merges them ...]
23: }
24: catch (Exception ex)
25: {
26: result.ErrorMessage = ex.Message;
27: result.ResultException = ex;
28: result.Result = false;
29: }
30: // finally return our result.. success or fail..
31: return result;
32: }
33:
34: /// <summary>
35: /// Merges the source PDF Documents and returns the final merge as a memory stream object
36: /// </summary>
37: /// <param name="info">The PDFInfo object that describes the final document.</param>
38: /// <param name="pdfFiles">The PDF files.</param>
39: /// <returns></returns>
40: public IRemoteResultByteArray MergePDFToArray(PDFInfo info, List<PDFMergeInfo> pdfFiles)
41: {
42: var result = new RemoteResultByteArray {Result = true};
43: var mergeResult = MergePDFToFile(info, pdfFiles);
44: if (!mergeResult.Result)
45: {
46: result.Result = false;
47: result.ErrorMessage = mergeResult.ErrorMessage;
48: result.ResultException = mergeResult.ResultException;
49: return result;
50: }
51: try
52: {
53: // read our output document into a stream object
54: using (var fs = File.OpenRead(mergeResult.Value))
55: {
56: result.Value = new byte[fs.Length];
57: fs.Read(result.Value, 0, (int) fs.Length);
58: }
59: // Delete our source file object since we're returning a stream
60: File.Delete(mergeResult.Value);
61: }
62: catch (Exception ex)
63: {
64: result.Result = false;
65: result.ErrorMessage = ex.Message;
66: result.ResultException = ex;
67: }
68: // Finally return our stream result object
69: return result;
70: }
71: }
Some things to be aware of here.. The PdfToolkit concrete object inherits from MarshalByRefObject and implements the shared contract IPdfToolkit which was defined in the shared assembly. The inheritance of MarshalByRefObject is what allows our PdfToolkit object to be accessed across application domain boundaries.
Notice that the return types of each method (as declared by the IPdfToolkit) is an interface and the concrete result of the method is the concrete object defined in the shared assembly. Since both our client (IIS) and server are aware and make use of the shared assembly, they are able to both call an apple an apple :)
Creating the Remoting Service
A point of definition here… Remoting Service != Windows Service. It is important to make that distinction. To define it simply (and probably wrongly) a remoting service is a collection of methods, objects, events, etc.. that are able to be accessed remotely.
Now a point of order here, a Remoting Service must be hosted within another process. The host of the remoting service can be any application that is running, or can be instantiated from a remote request. All of the example code that I’ve found used a simple console application as the host for the remoting service.
Enough background…
The Remoting Service is what’s responsible for creating the channel, and registering the remoting object with the service. For our needs, I set up a single RemoteSerivce object with a pair of public methods to setup and teardown the service and channels.
1: public class RemoteService
2: {
3: private const string CHANNELNAME = "x32bitHostServerChannel";
4: private const int TCPCHANNELPORT = 9083;
5:
6: private readonly PdfToolkit _pdfToolkit = new PdfToolkit();
7: private readonly TcpServerChannel _channel = new TcpServerChannel(CHANNELNAME, TCPCHANNELPORT);
8:
9: public void SetupService()
10: {
11: // Setup a channel for communication
12: ChannelServices.RegisterChannel(_channel, false);
13:
14: // Register our remoting services
15: RemotingConfiguration.RegisterWellKnownServiceType(typeof(PdfToolkit), "PdfToolkit", WellKnownObjectMode.Singleton);
16:
17: // bind our remoting objects with remoting service
18: RemotingServices.Marshal(_pdfToolkit, "PdfToolkit");
19: }
20:
21: public void TeardownService()
22: {
23: if (ChannelServices.GetChannel(CHANNELNAME) != null)
24: ChannelServices.UnregisterChannel(_channel);
25: }
26:
27: }
While my example only shows one service being registered, in our actual implementation we’ve got several services registered. The process for each is the same. Create a private instance of the remoting object, register the service type with a unique name, then set the marshal of the unique name to the private instance.
I could have easily used Ipc as my channel instead of Tcp, but in our instance, we’ve got several web head front ends that all communicate back to our central remoting server. This allows us to keep all of the remoting stuff on one box instead of having it installed on every one of our web hosts.
Creating the Service Host (Windows Service)
Alright, there are many samples and examples of creating a windows service.. making the installer, etc, etc.. so I won’t go into the nitty gritty of those details.. but I will show you the code for the actual service itself:
1: public partial class Remoting32bitHost : ServiceBase
2: {
3: private RemoteService _remoteService;
4:
5: public Remoting32bitHost()
6: {
7: InitializeComponent();
8: }
9:
10: protected override void OnStart(string[] args)
11: {
12: EventLog.WriteEntry(ServiceName + " Started", EventLogEntryType.Information);
13: _remoteService = new RemoteService();
14: _remoteService.SetupService();
15: EventLog.WriteEntry(ServiceName + " Channels Opened and Listening");
16: }
17:
18: protected override void OnStop()
19: {
20: EventLog.WriteEntry(ServiceName + " Tearing down channels");
21: _remoteService.TeardownService();
22: EventLog.WriteEntry(ServiceName + " Stopped");
23: }
24: }
So, as you can see the actual service is very simple. Just create a private instance of the RemoteSerivce object and instantiate it during the OnStart() event of the service. Once it’s instantiated, then we can call the SetupService method. When the service is stopped.. we call the TeardownService method.
You’re probably thinking that I could have just created the channel, registered the service types, and did the marshal binding right from the OnStart() event of the windows service. And you’d be absolutely correct. However, by having it as a separate object, I was able to utilize the RemoteService object from a test bed (windows console) application while I was developing and debugging… this meant I didn’t have to have the windows service actually installed and running on my machine (which of course, is a headache when you want to stop, start, recompile, etc).
Making the client talk to the service
The client is actually the easiest part of the whole process to build. Though, from trudging through all of the sample code and examples “out in the wild” you’d probably be just as inclined to talk a long walk off a short pier. That being the case, I’ll cut to the chase; We made a “remote wrapper” in the client application that is responsible for making the calls to the remote object.
1: /// <summary>
2: /// The PDFToolkit object is used to create a single merged PDF document from a list of several source pdf documents.
3: /// It has the ability to create "page seperators" for the stitched together pdfs, as well as internal bookmark links.
4: /// </summary>
5: public class PDFToolkit : IPdfToolkit
6: {
7: public IRemoteResultByteArray MergePDFToArray(PDFInfo info, List<PDFMergeInfo> pdfFiles)
8: {
9: IRemoteResultByteArray result = new RemoteResultByteArray { Result = false, ErrorMessage = "Unable to RPC to RemoteHost" };
10: // get the uri of the remote object we want to call
11: var uri = Common.GetRemotingUri("PdfToolkit");
12: // activate and get our remote object
13: var proxy = (IPdfToolkit)Activator.GetObject(typeof(IPdfToolkit), uri);
14: // build our result object
15: if (proxy != null)
16: result = proxy.MergePDFToArray(info, pdfFiles);
17: return result;
18: }
19:
20: public IRemoteResultString MergePDFToFile(PDFInfo info, List<PDFMergeInfo> pdfFiles)
21: {
22: IRemoteResultString result = new RemoteResultString{Result = false, ErrorMessage = "Unable to RPC to RemoteHost"};
23: // get the uri of the remote object we want to call
24: var uri = Common.GetRemotingUri("PdfToolkit");
25: // activate and get our remote object
26: var proxy = (IPdfToolkit)Activator.GetObject(typeof(IPdfToolkit), uri);
27: // build our result object
28: if (proxy != null)
29: result = proxy.MergePDFToFile(info, pdfFiles);
30: return result;
31: }
32: }
It truly is as simple as that. Use Activator.GetObject to create an instance of the type of remote object that you want using the proper Uri (I’ve got a helper method in our common library to give me the proper uri based on a remote type) then call the method you want.
The Big Gottcha!
In all of the samples and examples that I’ve found… for the client portion of the remoting solution, they all tell you that you have to create a channel on the client. That just simply isn’t the case. When you make a call to Activator.GetObject, it will create and register a channel for you if one is not already available. We ran into situation where we tried to do our own channel management on the client side where the code would work well for a period of time (from 20 minutes to hours), then all of the sudden start failing. As soon as we took the channel management code out of the client portion, we’ve not had a problem since.
Where’s the code?
I generally try to provide a code download with each of my articles. Unfortunately with this type of article, your usage is going to probably be very different than mine. Since so much of the code is dependant on 3rd party interfaces, there’s just no way that I could (given the time allotted for my day) put together a meaningful sample.
Hopefully the snippets that I’ve provided are enough to help you through the hurdles of interfacing your legacy 32bit objects within an x64 architecture. If you’ve got any questions, let me know. I’ll do what I can to answer them.