Interop (.NET 1.1) Performance Guidelines - Design Considerations

From Guidance Share
Jump to navigationJump to search

- J.D. Meier, Srinath Vasireddy, Ashish Babbar, and Alex Mackman

Design Chunky Interfaces to Avoid Round Trips

When you design code that is to be called through P/Invoke or COM interop, design interfaces that reduce the number of calls required to complete a logical unit of work. This guideline is particularly important for interfaces that handle calls to COM components located on a remote server, where the performance impact of using chatty interfaces is significant.

The following code fragment illustrates a chatty component interface that uses property getters and setters and requires the caller to cross the managed/unmanaged code boundary three times, performing data marshaling, security checks, and thread switches each time.

  MyComponent.Firstname = "bob";
  MyComponent.LastName = "smith";

The following code fragment shows a chunky interface designed to perform the same tasks. The number of round trips between managed and unmanaged code is reduced to one, which significantly reduces the overhead required to complete the logical operation.

  MyComponent.SaveCustomer( "bob", "smith");

Reduce Round Trips with a Facade

Often, you cannot design the interfaces of the unmanaged libraries that you use because they are provided for you. If you must use a preexisting unmanaged library with a chatty interface, consider wrapping the calls in a facade. Implement the facade on the boundary side that exposes the chatty interface. For example, given a chatty Win32 API, you would create a Win32 facade. Creating a .NET facade would still incur the same number of managed/unmanaged boundary crossings. The following is an example of a chatty unmanaged interface wrapped with an unmanaged facade.

  public bool MyWrapper( string first, string last )
    ChattyComponent myComponent = new ChattyComponent();
    myComponent.Firstname = first;
    myComponent.LastName = last;
    return myComponent.SaveCustomer();

Performance is improved because the facade reduces the required number of round trips crossing the managed/unmanaged boundary. You can apply the same principle to calling a chatty interface within a COM DLL created with Microsoft Visual Basic 6. You can create a facade DLL in Visual Basic 6 to reduce the required number of round trips, as shown in the following example.

  Function MyWrapper(first As String, last As String ) As Boolean
    Dim myComponent As ChattyComponent
    Set myComponent = New ChattyComponent
    myComponent.Firstname = first
    myComponent.LastName = last
    MyWrapper  = myComponent.SaveCustomer
  End Function

Implement IDisposable if You Hold Unmanaged Resources Across Client Calls

Holding shared server resources across remote client calls generally reduces scalability. When you build managed objects, you should acquire and release shared unmanaged resources on a per-request basis whenever possible. The platform can then provide optimizations, such as connection pooling, to reduce the resource-intensive operations for per-request calls.

If you acquire and release resources within a single request, you do not need to explicitly implement IDisposable and provide a Dispose method. However, if you hold on to server resources across client calls, you should implement IDisposable to allow callers to release the resources as soon as they are finished with them.

Reduce or Avoid the Use of Late Binding and Reflection

COM objects support two styles of binding: early binding and late binding. You use early binding when you program against the types defined within an interop assembly. You can use late binding to program against a COM object from managed code by using reflection.

To use late binding in C# or C++, you must explicitly program against types defined inside the System.Reflection namespace. In Visual Basic .NET, the compiler adds in automatic support for late binding.

To use late binding from Visual Basic .NET, you must disable the Option Strict compile-time setting and program against the Object type. The following code activates a COM object, by using a string-based ProgID, and calls methods by using late binding.

  Option Strict Off
  Imports System
  Imports System.Runtime.InteropServices
  Class MyApp
    Shared Sub Main()
      Dim ComType1 As Type = Type.GetTypeFromProgID("ComLibrary1.Customer")
      Dim obj As Object = Activator.CreateInstance(ComType1)
      '*** call to COM object through late binding
      Dim result As String = obj.GetInfo()
    End Sub
  End Class

Late binding provides significantly poorer performance than early binding because it requires that a caller discover method bindings at run time by using the IDispatch interface. In addition, it requires the conversion of parameters and return values to the COM VARIANT data type as they are passed between caller and object. For these reasons, you should avoid late binding where possible. However, it does provide a few noteworthy advantages.

When you use late binding from managed code, you eliminate the need to generate and deploy an interop assembly. You also avoid dependencies on GUIDs, such as the CLSID and the default interface identifier (IID) for a COM CoClass. This can be useful if you are working with several different versions of a Visual Basic 6 DLL that has been rebuilt without using the binary compatibility mode of Visual Basic 6. Code that uses late binding works with different builds of a COM DLL, even when the value for the default IID has changed.

ASP.NET and Late Binding

If you have an ASP.NET client, calls such as Server.CreateObject and Server.CreateObjectFromClsid use reflection, which slows performance. If you use the <object> tag to create a COM object, calls to that object are serviced by using late binding as well.

The use of late binding always involves tradeoffs. You gain code that is more flexible and adaptable, but at the expense of type safety, run-time performance, and scalability.