Interoperability Between .NET and COM

Download Report

Transcript Interoperability Between .NET and COM

Microsoft .NET
Framework Interop
Brian Long
Master Consultant
Falafel Software
.NET Interoperability
Why Interoperability?
The .NET platform is new
The Win32 platform is well established
No one wants to start from scratch
Use of existing code in .NET applications is essential
Interoperability goes both ways
Interoperability Options
.NET clients can use:
•
Win32 COM server objects (RCW)
•
Win32 DLL exports (P/Invoke)
Win32 COM clients can use:
•
.NET objects (CCW)
Win32 clients can use:
•
.NET method exports (Inverse P/Invoke)
COM
• COM is dead!!!
• COM code equals legacy code
• If you don’t know COM, don’t start learning it
now
• Er, ....
• That’s not practical
• There is a massive investment in COM that
we still need to use
General Points
• COM ↔ .NET Interoperability is usually called
Com Interop
• COM/Win32 ↔ .NET requires marshaling of
parameters
• COM Interop requires some reconciliation of
COM reference counting and .NET GC
mechanisms
• Interoperability requires some proxy / thunk /
wrapper to be in place (automated)
.NET → COM (RCW)
• RCW – Runtime Callable Wrappers:
.NET wrapper around COM object
• Type library importer (TlbImp.exe) generates an
Interop Assembly
• Delphi 8 & “Diamondback” IDEs do it just as well
• Interop Assemblies have a common naming
convention: Interop.LibraryName.dll
(LibraryName is the type library name, not the
COM server name)
.NET → COM (RCW)
Let’s make an Interop Assembly
.NET → COM (RCW)
• Use Primary Interop Assembly if available
• Primary Interop Assemblies are provided
and signed by the COM component’s
creator
• E.g. adodb.dll for MDAC objects
• Microsoft Office XP Primary Interop
Assemblies available from MSDN web site
.NET → COM (RCW)
• RCW manages COM object reference
count
• COM object is released during RCW
garbage collection
• RCW turns HRESULTs into .NET
exceptions
.NET → COM (RCW)
• A coclass Foo becomes an RCW FooClass
• Interfaces keep the same name
• An additional interface Foo is generated,
combining the coclass’ default interface and a
helper interface for the default event interface
• An event method Bar from an event interface
IEvents gets turned into a delegate type
IEvents_BarEventHandler.
• These COM events can be hooked up like
normal .NET events
.NET → COM (RCW)
Early binding:
• Straightforward - get an interface reference
from the construction of the RCW
• Call methods or access properties
• Exposed events can be set up just like normal
.NET events
.NET → COM (RCW)
Let’s see some RCW early binding
.NET → COM (RCW)
• If the type library importer does not provide
appropriate parameter type marshaling you
can tweak it using creative round tripping
• Little other choice exists
.NET → COM (RCW)
Late binding:
• This is possible without the Interop Assembly
• Uses reflection to operate
• New instance (CreateOleObject) through:
– System.Type.GetTypeFromProgID
– Activator.CreateInstance
• Current instance (GetActiveOleObject) through:
– Marshal.GetActiveObject
• Note System.Reflection.Missing and
System.Type.Missing (for EmptyParam)
.NET → COM (RCW)
Late binding:
• Methods invoked through
– System.Type.InvokeMember
• Parameters passed in an object array
.NET → COM (RCW)
Let’s see some RCW late binding
.NET → COM (RCW)
Late binding:
• Reference parameters are fiddly
• Overloaded InvokeMember requires single
element array of ParameterModifier
• ParameterModifier is an array of
Boolean flags
• Flag is True for reference parameter
• Flag is False for value parameter
.NET → COM (RCW)
Let’s see some more RCW late binding
.NET → COM (RCW)
Let’s see RCW early binding with events
COM → .NET (CCW)
• CCW – COM Callable Wrappers:
COM wrapper around .NET object
• Assembly registration utility
(RegAsm.exe)
COM → .NET (CCW)
• CCW ensures it will be marked for
garbage collection when external
reference count reaches 0
• CCW turns .NET exceptions into
HRESULTs
• Assembly must be accessible to CLR:
– installed in GAC
– resident in application directory (or
available for probing)
COM → .NET (CCW)
• Late binding simply requires the assembly
to be registered
• Late binding uses a ProgID registered by
RegAsm.exe:
AssemblyName.ClassName
(e.g. MyAssembly.MyClass)
COM → .NET (CCW)
Let’s see some CCW late binding
COM → .NET (CCW)
• Early binding relies on an Interop Type Library:
– use the /tlb option with RegAsm
– use the import wizard in “Diamondback”
• .NET objects may choose to implement a defined
interface or not
• The Guid attribute can be used to give a .NET
interface an IID (traditional Delphi syntax should
also work*)
* And does in “Diamondback”, but not in Delphi 8
COM → .NET (CCW)
• The ClassInterface attribute controls whether and
how an interface will be manufactured to expose the
class:
– AutoDispatch - dispinterface for late binding (the
default)
– AutoDual – for early binding (versioning issues)
interface is class name with _ prefix
– None – IDispatch access only
• Use AutoDual if you have no interface
• Use None if you implement an interface (the suggested
approach to avoid interface versioning issues)
COM → .NET (CCW)
• Importing a Delphi assembly’s Interop Type
Library requires some forethought, due to the
symbols exposed by default
• Use [assembly: ComVisible(False)] and
[ComVisible(True)] to control default
visibility
• Early binding from Win32 uses the creator class
in the type library import unit, as usual, or any of
the other standard options
COM → .NET (CCW)
Let’s see some CCW early binding
.NET → Win32 (P/Invoke)
• Platform Invocation Service, usually referred to as
Platform Invoke, or simply P/Invoke (or even
PInvoke):
• DllImport attribute (from
System.Runtime.InteropServices) is needed for
routines with text parameters
• Standard Delphi DLL import syntax works
otherwise
• Uses DllImport behind the scenes
• Caveat is string parameters
.NET → Win32 (P/Invoke)
Let’s see a traditional import
.NET → Win32 (P/Invoke)
//Win32
procedure FooA(Msg: PChar); cdecl;
begin
MessageBox(0, Msg, 'Foo',
MB_OK or MB_ICONQUESTION);
end;
//.NET
procedure Foo(const Msg: String);
...
[DllImport('bar.dll',
EntryPoint = 'FooA',
CharSet = CharSet.Ansi,
CallingConvention =
CallingConvention.Cdecl)]
procedure Foo(const Msg: String); external;
.NET → Win32 (P/Invoke)
• The big issue with P/Invoke is ensuring the
parameters are marshaled across correctly.
• String parameters are generally catered for with
DllImport.CharSet:
– Ansi
– None
– Unicode
– Auto*
*
uses Ansi on Win9x and Unicode on NT platforms
.NET → Win32 (P/Invoke)
Let’s see some P/Invoke imports
.NET → Win32 (P/Invoke)
• Use Windows.pas and Delphi.Vcl.Windows.pas
as guidelines for parameter type translation
• MarshalAs parameter attribute from
System.Runtime.InteropServices
• Used to fix parameter marshaling when the
default marshaling is inappropriate
.NET → Win32 (P/Invoke)
Let’s see P/Invoke imports in use
.NET → Win32 (P/Invoke)
Other issues surround Win32 error codes:
– DllImport.SetLastError
– Marshal.GetLastWin32Error
– GetLastError
HResult values:
– safecall (Win32 COM)
– DllImport.PreserveSig (.NET)
.NET → Win32 (P/Invoke)
Let’s use P/Invoke attribute fields
.NET → Win32 (P/Invoke)
Performance:
• P/Invoke calls cost ~10 machine instructions
• Cost rises for each extra job (marshaling etc.)
• By default security is on
• UnmanagedCode permission
• SuppressUnmanagedCodeSecurity attribute
omits security check stack walk
.NET → Win32 (P/Invoke)
Let’s see more P/Invoke code
.NET → Win32 (P/Invoke)
• New in Delphi “Diamondback”
• Virtual Library Interfaces (VLI) aka Dynamic
PInvoke
• Makes a set of functions implemented in a
DLL look like an interface implemented by an
object
• Uses new overload of Supports
.NET → Win32 (P/Invoke)
Let’s see some VLI
Win32 → .NET methods
• Little known mechanism (Inverse P/Invoke),
primarily discussed in:
– Inside Microsoft .NET IL Assembler,
Serge Lidin, Microsoft Press
• Uses method transition thunks
• Only supported by Managed C++ and IL
• Oh, and Delphi for .NET
Win32 → .NET methods
• Very trivial mechanism in Delphi: managed exports
• Simply use an exports clause as you do in Win32
when exporting functions from DLLs
• Caveats:
– Must mark the project source as containing unsafe
code: {$UNSAFECODE ON}
– Can only export “global” routines
– Can not export static class methods this way
Win32 → .NET methods
• Can also be accomplished in other languages
• Much more involved (as indeed it is when
exposing Delphi static class methods)
• Involves creative round-tripping to expose
assembly methods
Win32 → .NET methods
Round-tripping:
•
Disassemble a compiled assembly to an IL
source file with the .NET disassembler:
ildasm.exe
•
Modify the IL code, or add additional IL files,
possibly to include features not supported by
the original compiler
•
Reassemble the IL code with the .NET
assembler: ilasm.exe
Win32 → .NET methods
Creative round-tripping:
•
Disassemble a compiled assembly to an IL
source file with the .NET disassembler:
ildasm.exe
•
Modify the IL code, or add additional IL files,
possibly to include features not supported by
the original compiler
•
Reassemble the IL code with the .NET
assembler: ilasm.exe
Win32 → .NET methods
Let’s see some round tripping
Win32 → .NET methods
IL modifications to export .NET methods:
• Modify IL manifest:
– Modify .corflags directive to cater for XP
issue
– Declare a v-table fixup table
– Declare data space for the v-table fixup table
• Modify implementations of methods to be exported:
– Mark each method with the .vtentry and
.export directives
Win32 → .NET methods
IL file assembly manifest (original):
.module dotNetAssembly.dll
.imagebase 0x00400000
.subsystem 0x00000002
.file alignment 512
.corflags 0x00000001
Win32 → .NET methods
IL file assembly manifest (modified):
.module dotNetAssembly.dll
.imagebase 0x00400000
.subsystem 0x00000002
.file alignment 512
.corflags 0x00000002
.data VT_01 = int32[2]
.vtfixup [2] int32
fromunmanaged at VT_01
Win32 → .NET methods
Two IL methods (original):
.method public static void
int32 I) cil managed
{
DoSomething(
.maxstack 1
IL_0000: ldarg.0
// rest of code omitted for brevity
} // end of method Unit::DoSomething
.method public static void DoSomethingElse(
[in] string Msg) cil managed
{
.maxstack 1
IL_0000: ldarg.0
// rest of code omitted for brevity
} // end of method Unit::DoSomethingElse
Win32 → .NET methods
Two IL methods (exported):
.method public static void DoSomething(
int32 I) cil managed
{
.vtentry 1:1
.export [1] as DoSomething
.maxstack 1
IL_0000: ldarg.0
// rest of code omitted for brevity
} // end of method Unit::DoSomething
.method public static void DoSomethingElse(
[in] string Msg) cil managed
{
.vtentry 1:2
.export [2] as DoSomethingElse
.maxstack 1
IL_0000: ldarg.0
// rest of code omitted for brevity
} // end of method Unit::DoSomethingElse
Win32 → .NET methods
Let’s see some creative round tripping
Win32 → .NET methods
• Potential maintenance issue: must modify IL
generated from disassembling every built
executable
• Workaround is a utility to automate the process
(perhaps a command-line utility)
• One such utility (almost) is mme.exe (Managed
Method Exporter)*
• mme.exe is actually a simple GUI app
* supplied with source in the files that accompany this session
References
Everything you ever wanted to know about COM Interop
(and lots you didn’t):
References
The book to have in order to learn about Delphi 8 and the
Microsoft .NET Framework:
References
Full coverage of CIL (or MSIL) by the author of ILASM,
ILDASM & the CLR Metadata validation engine
Questions?
Thank You
3222
Microsoft .NET Framework Interop
Please fill out the speaker evaluation
You can contact me further at
[email protected]