N/Direct - The .NET Interoperability Resource Center 

N/Direct Exclusives

 

 

 

Most Valuable Professional

What’s new for Interop in .NET Framework v2.0?
Author: Mattias Sjögren Written: 11/15/2003 Updated: 11/15/2003

This article provides an overview of the news and changes related to interop in the upcoming v2.0 of the .NET Framework. It’s currently based on the alpha release, but will be updated as needed when betas and the final product are released.

Templates Simplify C++ Interop
It should be obvious now that most APIs published by Microsoft in the foreseeable future will be managed. This is especially true in the era of Windows Longhorn and WinFX. With that in mind, it’s clear that Visual C++ has to provide excellent interoperability with managed code for it to remain a viable language for future Windows development.

The VC++ team obviously understands this, and the Whidbey release will bring many new features to make it easier to write managed code or native code that interoperates with managed code in C++. This will strengthen the position of VC++ as the best option for complex interop scenarios.

As you may have heard, Microsoft are phasing out the Managed Extensions for C++ (though it’s still working and supported) in favour of the new C++ binding for the CLI. An in-depth description of the new C++/CLI syntax is beyond the scope of this article, but it will definitely make it easier to write and interact with managed code in C++.

The new release of VC++ will include some useful templates for simplifying C++ interop (formerly known as It Just Works (IJW)). One such template is marshal_as, which makes it easy to convert or marshal data between common managed and unmanaged formats, such as System::String and all the various C++ string types.

Other templates include auto_dispose and auto_close, which are similar to smart pointer templates and will automatically call Dispose or Close on an object when leaving the scope.

Since these bits weren’t available in the alpha or PDC release of Visual Studio “Whidbey”, there’s not that much information available yet. I’m sure there will be a lot more to write about this in the future, but in the meantime I encourage you to look at the slides and listen to the audio from the PDC sessions CLI390, TLS311 and TLS401 that are available from MSDN if you’re interested in C++ interop.

The interoperability story for MFC and Windows Forms should also be improved in Whidbey. You will be able to use Windows Forms dialogs in MFC, host Windows Forms controls, and use Forms as a CView and integrate it in MFC’s command routing infrastructure.

The C++ and CLR teams have also solved some subtle bugs or problems that exist in earlier versions of the CLR. These include the infamous loader-lock problem, and something known as the “double P/Invoke” issue that hurt performance.

Safer Handle Usage
A new class called SafeHandle has been added to the System.Runtime.InteropServices namespace. SafeHandle (and its subclasses – SafeHandle itself is abstract) is meant to be a wrapper for any type of handle you hold to unmanaged resources, and the runtime knows how to marshal it properly. What makes SafeHandle safer than the HandleRefs or plain IntPtrs that you’re currently using, is that its methods are guaranteed to run until completion; even in extreme situations such as if an asynchronous exception like ThreadAbortException is thrown. This provides more reliable resource clean-up.

SafeHandles depend on a feature called mustrun methods, which are methods that carry the mustrun metadata flag. This flag can be set with the MethodImplAttribute by specifying MethodImplOptions.MustRun.

There’s also a new HandleCollector class that can keep track of handle usage and trigger a garbage collection if it exceeds a certain threshold.

Delegate to Function Pointer Marshaling
The runtime now supports marshaling of function pointers to delegates. The reverse - delegate to function pointer - has been possible since version one. The marshaling happens automatically when you use a delegate type where something is returned from native code. The conversion can also be made explicitly with two new methods on the Marshal class; GetDelegateForFunctionPointer and GetFunctionPointerForDelegate.

Built-in WebBrowser Wrapper and Active Document Hosting
Windows Forms now comes with a built-in wrapper for the WebBrowser control, so there’s no need to AxImp Shdocvw.dll anymore. Note that this is still a wrapper around the existing ActiveX control; it has not been rewritten in managed code. You’ll also find some wrapper classes for the HTML document object model (DOM) included so you can navigate the content of a web page.

Another addition is the ActiveDocumentHost control, which lets you put active documents (or OLE documents) on your forms, similar to the old VB OLE control. Examples of active documents are Word documents and Excel spreadsheets.

The DefaultCharSet and ComDefaultInterface Attributes
The new DefaultCharSetAttribute can be used to set the default CharSet for structures and imported function declarations in a module. Using it can save you some typing by, for example, setting it to CharSet.Auto once at the module level, and then leave it out in your declarations. But it also means that it’s harder to just look at a declaration and know how it will behave, so personally I’m not sure if this is a good thing or not.

Note that it’s the compiler’s responsibility to support the DefaultCharSet attribute and do the right thing; the runtime ignores the attribute. In the alpha release, only the C# compiler has implemented support for DefaultCharSet.

The ComDefaultInterface attribute has been added so you can explicitly say which of the interfaces implemented by a class should be the default for the COM exported coclass. The default behavior is the same as in previous versions, which is to use the first interface listed in metadata. The problem with that is that a compiler may not preserve the interface order from your source code to metadata during compilation, so it could be hard to know which interface would be used.

The VariantWrapper Class
A new VariantWrapper class is provided to solve the problem of passing data as a VT_VARIANT|VT_BYREF VARIANT, which was hard to do in earlier versions. The default behavior, if you for example pass a string to a byref VARIANT parameter, is that the callee gets a VT_BSTR|VT_BYREF. By passing the string wrapped in a VariantWrapper, you get an extra level of indirection, and the callee sees VT_VARIANT|VT_BYREF instead. This also gives the callee to ability to change the VARIANT type on the way back. The VariantWrapper can be used in late bound COM interop scenarios (Type.InvokeMember) or when calling imported dispinterfaces.

Registration
A new method RegisterTypeForComClientsEx has been added to the RegistrationServices class. It’s basically a wrapper for the CoRegisterClassObject API. There’s also a corresponding UnRegisterTypeForComClients wrapping CoRevokeClassObject.

New Type Library Importer and Exporter Options
TlbExp and TlbImp have a few new options. For TlbExp, the additions are:

/transform which, just like the TlbImp option with the same name, changes how the exporter behaves in certain situations. The only transformation available right now is called 64BitIntPtr, and it causes IntPtr and UIntPtr parameters to be exported as 64-bit integers rather than 32-bit.

/tlbreference and /asmpath let you specify where TlbExp should look for typelibs and assemblies to resolve references.

These options are also available if you use the TypeLibConverter.ConvertAssemblyToTypeLib method, as Transform64BitIntPtr and CallerResolvedReferences in the TypeLibExporterFlags enum.

The only addition to TlbImp is the /tlbreference option.

Marshaling Metadata Retrievable with Reflection
Previously, the only way to inspect the interop related metadata was to use the unmanaged metadata COM APIs, because Reflection didn’t expose the information. Now, however, Reflection will re-construct pseudo-custom attributes such as MarshalAs, StructLayout and DllImport and return that from GetCustomAttributes methods.

Moved COM Interfaces
All the imported COM interfaces (such as UCOMITypeInfo) in the System.Runtime.InteropServices namespace, and their related structures, have been moved to a separate System.Runtime.InteropServices.ComTypes namespace, where the UCOM prefix has been removed. The existing types are still there, but have been marked with the Obsolete attribute.

Putting It All Together
To wrap up our tour around the new Whidbey interop features, let’s look at a (somewhat contrived) sample application that demonstrates some of them. What it does is to load a few system DLLs that all export a function DllGetVersion, to retrieve and print version information about them. The DLLs are loaded with LoadLibrary, and the library handle is stored as a SafeHandle called LibHandle. It then uses GetProcAddress to retrieve a pointer to the DllGetVersion function, which gets marshaled back to us as a delegate. The output on a typical Windows XP machine could look like this

      comctl32.dll: 5.82.2600
      shell32.dll: 6.0.2600
      shdocvw.dll: 6.0.2800
      shlwapi.dll: 6.0.2800
    

C#
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
[module: DefaultCharSet(CharSet.Auto)]
class LibHandle : SafeHandle
{
  [DllImport("kernel32.dll")]
  [MethodImpl(MethodImplOptions.MustRun)]
  static extern bool FreeLibrary(IntPtr hModule);
  public LibHandle() : base(IntPtr.Zero, true) {}
  public override bool IsInvalid
  {
    [MethodImpl(MethodImplOptions.MustRun)]
    get { return IsClosed || handle == IntPtr.Zero; }
  }
  [MethodImpl(MethodImplOptions.MustRun)]
  protected override bool ReleaseHandle()
  {
    return FreeLibrary( handle ); 
  }
}
struct DLLVERSIONINFO
{
  public uint cbSize, 
              dwMajorVersion, 
              dwMinorVersion, 
              dwBuildNumber, 
              dwPlatformID;
}
delegate uint DLLGETVERSIONPROC(ref DLLVERSIONINFO pdvi);
static class WhidbeyInterop
{
  [DllImport("kernel32.dll")]
  static extern LibHandle LoadLibrary(string lpFileName);
  [DllImport("kernel32.dll", CharSet=CharSet.Ansi)]
  static extern DLLGETVERSIONPROC GetProcAddress(LibHandle hModule, string lpProcName);
  static void Main()
  {
    string[] dlls = {"comctl32.dll", "shell32.dll", "shdocvw.dll", "shlwapi.dll"};
    foreach ( string dll in dlls ) {
      using( LibHandle hlib = LoadLibrary( dll ) ) {
        DLLGETVERSIONPROC dllGetVersion = GetProcAddress( hlib, "DllGetVersion" );
        DLLVERSIONINFO dvi = new DLLVERSIONINFO();
        dvi.cbSize = (uint)Marshal.SizeOf( typeof(DLLVERSIONINFO) );
        dllGetVersion( ref dvi );
        Console.WriteLine( "{0}: {1}.{2}.{3}", dll,
          dvi.dwMajorVersion, dvi.dwMinorVersion, dvi.dwBuildNumber );
      }
    }
  }
}
    

See also
Presentations from Microsoft Professional Developers Conference 2003
Longhorn SDK