Rick Strahl's Weblog  

Wind, waves, code and everything in between...
.NET • C# • Markdown • WPF • All Things Web
Contact   •   Articles   •   Products   •   Support   •   Advertise
Sponsored by:
Markdown Monster - The Markdown Editor for Windows

WCF Clients and COM Interop


:P
On this page:

One thing that may not be real obvious when moving WCF client code from say Web Service References is that the Web Service clients generated for WCF are not easily accessible via COM Interop. With ASMX services the proxy classes generated used to marshal fine over COM and so it was possible to access them directly via COM. The problem with the WCF proxies is that the clients inherit from a generic type - ClientBase<ServiceInterface> and COM Interop does not work with any type that includes Generics as part of its type hierarchy. Types that inherit from a generic type cannot be directly instantiated through COM and even if you use indirect referencing (say using Reflection and then accessing a preloaded reference) any calls trying to access members of the proxy will fail with a "Not enough storage is available for operation" COM error.

I'm working on a project that is responsible for using a WCF client to interact with a third party Web service and I haven't actually run into this issue because I tend to create wrapper methods for the proxy in a separate class rather than using the proxy instances directly. In most cases these wrapper methods are dumb - they're literally just wrappers around the service proxy methods, but in other cases these methods also perform various type fixups and other helper functionality that is useful to give the COM client a clean interface.

The service wrappers provide a couple of additional things:

  • A service factory that creates configured instances of the Service Proxy
  • ComVisible wrapper so that the interface is more discoverable to the client

A typical wrapper will look something like this:

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;

using WebServiceInterop.EnrolmentService;


namespace WebServiceInterop
{

    /// <summary>
    /// Wrapper for the Enrolment Service that is COM 
    /// accessible from FoxPro. 
    /// </summary>    
    [ComVisible(true)]
    [ClassInterface(ClassInterfaceType.AutoDual)]
    public class InteropEnrolmentService : BaseInteropService
    {

        #region Class Configuration
        /// <summary>
        /// Internal instance of the Web Service Proxy. Once loaded
        /// this client instance is cached for reuse. Set to null to
        /// clear explicitly or release the class reference
        /// </summary>
        public EnrolmentInterface_Binding_EnrolmentInterfaceClient Proxy = null;

        /// <summary>
        /// Gets an instance of the Enrolment Service Proxy
        /// </summary>
        /// <returns></returns>
        public override bool GetProxy()
        {
            if (this.Proxy != null)
                return true;
            
            this.Proxy = this.GetServiceProxy<EnrolmentInterface_Binding_EnrolmentInterfaceClient,
                                              EnrolmentInterface_Binding_EnrolmentInterface>();
            if (this.Proxy == null)
            {
                this.SetError("Unable to load Enrolment service client instance");
                return false;
            }

            return true;
        }
        #endregion

        /// <summary>
        /// Create a new Enrolment against the service. Takes an Enrollment
        /// input record and returns        
        public EnrolmentInterface_CreateEnrolment_Output CreateEnrolment(EnrolmentInterface_CreateEnrolment_Input input)
        {
            try
            {
                // *** Always Ensure that an instance of the service proxy is loaded (this.client)                
                if (!this.GetProxy())                
                    return null;

                // *** Call the actual CreateEnrolment method on the service
                EnrolmentInterface_CreateEnrolment_Output output = this.Proxy.CreateEnrolment(input);

                return output;
            }
            catch (Exception ex)
            {
                this.SetError(ex.Message);
            }

            return null;
        }
    }
}

where the latter method is one of the service methods.

Using the wrappers works, but it's a bit of work to create wrapper methods for each of the service methods if the service is large. And invariably my client asked: Can't I access the proxy directly. Uh... hang on a second while I check <s>...

So the question is: Is it possible to access the WCF client directly? It looks like it can't be done directly due to the generic class in the inheritance hierarchy. I tried instantiating the Client type and calling the method which failed as well as using the proxy approach above to store the reference and then casting to the service interface, which also fails mainly because the parameter types of the interface are different than the parameter types of the instance - the interface deals with the lower level messages (Request/Response objects, where the Client deals with concrete message classes). So no luck on that count.

However it is possible to use pass through Reflection calls. So I did the following:

  • Create a wrapper class for the service(s) (my existing service wrapper class)
  • Include a proxy factory method that instantiates the proxy and holds it in a public property (ie. what GetProxy() does above)
  • Provide  'generic' method that can be called to invoke a method on this proxy instance. (via a separate helper object that provided various Reflection services)

By implementing a generic invocation method that uses Reflection  - say InvokeMethod()  _ with the instance of the object (created in .NET) and the method name plus any parameters the service method can be accessed directly you can make the calls work over COM interop. I have a reusable bridge object I use for this functionality and it includes among other things an InvokeMethod method:

/// <summary>
/// Invokes a method with one parameter

/// </summary>

/// <param name="Instance"></param>

/// <param name="Method"></param>

/// <param name="Parm1"></param>

/// <returns></returns>

public object InvokeMethod(object Instance, string Method, object Parm1)

{

    return wwUtils.CallMethod(Instance, Method,Parm1);           

}

where CallMethod looks like this:

/// <summary>

/// Calls a method on an object dynamically.

/// </summary>

/// <param name="Params"></param>

/// 1st - Method name, 2nd - 1st parameter, 3rd - 2nd parm etc.

/// <returns></returns>

public static object CallMethod(object Object, string Method, params object[] Params)

{

    // *** Pick up parameter types so we can match the method properly

    Type[] ParmTypes = null;

    if (Params != null)

    {

        ParmTypes = new Type[Params.Length];

        for (int x = 0; x < Params.Length; x++)

        {

            ParmTypes[x] = Params[x].GetType();

        }

    }

 

    return Object.GetType().GetMethod(Method, wwUtils.MemberAccess | BindingFlags.InvokeMethod, null, ParmTypes, null).Invoke(Object, Params);

 

    // *** More reliable (works with COM objects) but slower

    //return Object.GetType().InvokeMember(Method,wwUtils.MemberAccess | BindingFlags.InvokeMethod,null,Object,Params);

}

Note that each InvokeMethod call needs an explicit method for each signature since variable parameter lists are not supported over COM interop. The COM client includes a reference to this bridge object that can perform these helper utilities. Then in the COM client code you can :

	CLEAR
	loEnrol = CREATEOBJECT("EnrolmentService")

	*** Initialize the service and pass location of interop dll
	? loEnrol.Initialize()

	*** Create a new Input Message object for NewEnrollment  (.NET object)
	loNew = loEnrol.oBridge.CreateInstance("EnrolmentInterface_CreateEnrolment_Input")
      *** Assign values
      loNew.TransactionId = SYS(2015)
      loNew.SourceSystemCode = "1-add"
*** Create Proxy on the Wrapper object as loEnrol.oService.client
loEnrol.oService.GetClient()	
	
*** Now use Reflection to call the Proxy method
*** - ie. .NET calls the method loResult = loEnrol.oBridge.InvokeMethod(loEnrol.oService.Proxy,"CreateEnrolment",loNew) ? loResult.ToString()
return

The bridge object provides a number of services including intantiation (which could also be done directly via COM here) and the InvokeMethod call on. Since all the code for this runs inside of the CLR rather than over the COM initiated boundaries these calls - that with direct access failed - now work.

It ain't elegant but it works and allows the customer access to the WCF proxy directly. However, even though they have the ability to do this now I still recommend using the wrapper - it's easier to call from the client and also makes the code more maintainable in the future if a different approach is requried to call the service. The extra abstraction is not a big price to pay IMHO. Ultimately the hope is to move the whole app into .NET so the these COM interop issues aren't an issue in the first place.

Posted in WCF  


West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2024