|
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
|