N/Direct - The .NET Interoperability Resource Center 

N/Direct Exclusives

 

 

 

Most Valuable Professional

What’s new for Interop in .NET Framework v1.1?
Author: Mattias Sjögren Written: 1/8/2003 Updated: 6/7/2003

There are some interesting new features in the version 1.1 release of the .NET Framework and the corresponding SDK.

Bug Fixes
First, and perhaps most important, a number of bugs have been fixed. While many of the bugs can be corrected by installing hotfixes or Service Packs for v1.0, it’s nice that things work “out of the box” in v1.1. Some issues that have been labelled as bugs in the Microsoft Knowledge Base were actually limitations in the CLR’s marshaling layer. For example, there was originally no support for marshaling string arrays or SAFEARRAYs inside structures (KB articles 324181 and 322172), but this is now implemented and supported, which makes COM interop with complex types work a bit smoother.

Side-by-side Execution and Registration-Free COM Interop
The assembly registration code has been updated to store COM registration information in version-dependent subkeys in the Registry. This enables multiple versions of the same assembly to be registered side-by-side on the machine, and a COM client application can be configured to use the most appropriate version. The SDK documentation also describes how you can use Win32 application manifests to enable registration-free activation of libraries. This is primarily an OS feature, first implemented in Windows XP. But there are some additional things to consider to get it working for managed libraries.

It Just Doesn't Work
Microsoft has recently published details on a bug that affects the loading of mixed mode assemblies written in managed C++. It's being referred to as the loader lock bug, and can result in a deadlock. Any IJW assembly could potentially cause a deadlock when loaded. The risk is amplified if the assembly has a strong name. The compiler included in Visual Studio .NET 2003 (v13.10) includes a new warning to bring this issue to your attention, and a header file to help you implement the workarounds recommended by Microsoft. However, the bug will not be fixed in the runtime until the next major release (codename Whidbey). Follow the link below to the MSDN article Mixed DLL Loading Problem for more details.

Customer Debug Probes
Customer Debug Probes (CDP) is a great help for debugging interop related problems. It's a set of hooks, or probes, in the CLR that, when enabled, helps you locate and diagnose problems that would otherwise be hard to find, such as incorrect calling conventions or invalid pointers. All the current probes are one way or another related to interop issues. There's a lot more to say about CDP, and you'll probably see more information about it here on N/Direct in the future. In the meantime, I would recommend trying it out for yourself, and reading Adam Nathan's weblog for more details about CDP. The .NET Framework SDK includes a CDP demo solution, located in the Tool Developers Guide\Samples\cdp directory.

Turn Off Best-fit Character Mapping
There are three new attributes added to the System.Runtime.InteropServices namespace. The first of these is a new marshaling attribute called BestFitMappingAttribute. It can be applied to assemblies and types, and affects any native methods declared in that scope. The purpose is to control how the runtime does Unicode to ANSI conversion of strings. The default, if the attribute is missing, is to perform best-fit mapping of Unicode characters that lacks an exact match in the ANSI character set. This is also the same behavior as in v1.0 or the runtime. For example, as you’ll see in the example below, the character Ĉ (a capital C with a circumflex, U+0108) would be converted to a plain C when using the ANSI code page 1252 (default for U.S. English). Using the BestFitMapping attribute, you can turn off this best-fit mapping, and cause all unmappable characters to be replaced by a ‘?’.

The attribute also has a ThrowOnUnmappableChar boolean field. If you set it to true, the system will throw an ArgumentException if there are any unmappable characters in the string. Note that some characters might be unmappable even when best-fit mapping is turned on.

For readers familiar with the WideCharToMultiByte Win32 API, using BestFitMapping(false) is equivalent to including the WC_NO_BEST_FIT_CHARS flag in a call to WideCharToMultiByte.

As stated earlier, the BestFitMapping attribute can only be applied to assemblies and types. But this option is available for individual P/Invoke methods as well, thanks to two new fields on the DllImportAttribute. They are, not surprisingly, called BestFitMapping and ThrowOnUnmappableChar.

For those of you who like low level facts, I can add that these two DllImport flags are represented as

      bestfit:on / bestfit:off
      charmaperror:on / charmaperror:off
    

in IL assembler, inside the pinvokeimpl() attribute. The CorPinvokeMap enum defined in CorHdr.h contains new groups of members (pmBestFit* and pmThrowOnUnmappableChar*) that represent these flags in metadata.

Now let’s look at an example of how this makes a difference. The code calls the ANSI version of the good old MessageBox API, with a circumflex decorated capital C as the message.

C#
using System;
using System.Runtime.InteropServices;
class BestFitTest
{
  [DllImport("user32.dll", CharSet=CharSet.Ansi /*, BestFitMapping=false*/)]
  static extern int MessageBoxA(IntPtr hWnd, string lptext, string lpCaption, uint uType);
  static void Main()
  {
    MessageBoxA( IntPtr.Zero, "Ĉ", null, 0 );
  }
}
    

When I compile and run the code as is on either v1.0 or v1.1 of the runtime, I get this result; a plain C without the circumflex.

But when I uncomment the BestFitMapping setting in the DllImport attribute and recompile for v1.1, I get this question mark instead.

Now, the obvious question is: why would I want to use this attribute? The prime reason is security. For example, some characters are mapped to a backslash, which can be a security risk if you’re working with file system paths.

Preserve GUIDs with ComCompatibleVersionAttribute
The second new attribute is called ComCompatibleVersion. It’s used to ensure that GUIDs generated for a new assembly version is the same as an older version, when exported to COM. Unless you explicitly set the GUIDs you want with the Guid attribute, the type library exporter will generate CLSIDs for classes and a LIBID for the typelib for you, based on a hash of, among other things, the assembly version. Say that you release version 1.2.3.0 of an assembly, and its corresponding typelib. Later on, you fix some bugs and compile version 1.2.3.4, fully compatible with the old version. When the old version is replaced by the new, it could break clients that were compiled against version 1.2.3.0. The solution is to add [assembly: ComCompatibleVersion(1,2,3,0)] to the new assembly. This version number will then be used instead of the assembly version to generate the GUIDs, which ensures that you get the same result as the old version. It’s your responsibility to ensure that the new version really is fully compatible, so don’t use ComCompatibleVersion without proper testing.

The TypeLibVersionAttribute
The last of the new attributes is TypeLibVersion. As the name indicates, it’s used to explicitly set the version of the typelib generated by the type library exporter. Typelib versions only have two parts; major and minor. If you don’t use this attribute, you get the same behavior as in v1.0 of the .NET framework, where the major and minor parts of the assembly version number are used as the typelib version, and the build and revision parts are discarded. So v1.2.3.0 and v1.2.3.4 of an assembly would both export a type library with version 1.2. If you prefer, you can now use the TypeLibVersion attribute to customize this. You can, for example, specify that the old assembly version produces type library version 1.230, and then new one 1.234.

Type Library Importer Transforms
Finally, the TlbImp tool has got a new option, /transform. You use it to perform transformations on the metadata generated by the importer. Currently there’s only one transform available; DispRet. But the generic nature of the option could be a hint that future versions will allow even more ways to customize the output.

You use /transform:dispret to tell the importer to change [out, retval] parameters on dispinterface methods to return values. This happens automatically for regular (dual or IUnknown derived) interfaces, but not for dispinterfaces. For example

[uuid(...)]
dispinterface Foo {
  properties:
  methods:
    HRESULT Bar([out, retval] BSTR* r);
};
    

imports to

void Bar(out string r);
    

by default, but when you use the DispRet transform it becomes

string Bar();
    

If you’re using the managed TypeLibConverter class, this option is enabled with the new TransformDispRetVals member of the TypeLibImporterFlags enum.

See also
FIX: Marshaling of Fixed Arrays of BSTR Fields in .NET Framework (324181)
FIX: Marshalling Structures that Contain SafeArray Fields (322172)
Adam Nathan's Interop-Centric CLR Blog
Mixed DLL Loading Problem