COM Interop & P/Invoke
.NET and the Old World
COM Interop & P/Invoke .NET and the Old World Objectives - - PowerPoint PPT Presentation
COM Interop & P/Invoke .NET and the Old World Objectives Introduction to interoperation between .NET and COM COM and .NET .NET and platform services Contents Section 1: Overview Section 2: Calling COM Services from
.NET and the Old World
Introduction to interoperation between
.NET and COM COM and .NET .NET and platform services
Section 1: Overview Section 2: Calling COM Services from .NET Section 3: Calling .NET Services from COM Section 4: Calling Platform Services from .NET
Transition to .NET will be a lasting process Portions of systems will remain as they are now
...and even maintained in their current form
Interoperability increases the momentum of .NET Goal is to achieve total transparency Mere presence of .NET must not interfere with COM Performance should not be hit
Managed Code vs. Unmanaged Code
Lifetime management Metadata
Common Type System Inheritance concept Threading model Error handling mechanisms Registration
Metadata for COM class must be available
Can be generated from Type Library Can declare Custom Wrapper
Use COM class like .NET class Early and late bound activation COM component must be registered locally
Instantiate like creating a .NET object Metadata must be available at compile time
Must be referenced in development environment
At runtime, Metadata and COM library must be available
COMLib.COMClass COMLib.COMClass aCOMObject; aCOMObject; aCOMObject aCOMObject = new = new COMLib.COMClass; COMLib.COMClass;
Preserving object identity Maintaining object lifetime Proxying interfaces Marshalling method calls Consuming selected interfaces .NET Object COM Object RCW
IUnknown IDispatch IIfc IIfc
ISupportErrorInfo
ISupportErrorInfo
Additional information with exceptions
IProvideClassInfo
IProvideClassInfo
Typed wrapper
ITypeInfo
ITypeInfo
TlbImp
TlbImp converts references to System.Type System.Type
COM lifetime control is wrapped by RCW COM object released when RCW garbage collected Non-deterministic finalization issue
COMLib.COMClass COMLib.COMClass aCOMObject; aCOMObject; aCOMObject aCOMObject = new = new COMLib.COMClass(); COMLib.COMClass(); aCOMObject.SomeFunc(); aCOMObject.SomeFunc(); Marshal.ReleaseComObject(aCOMObject); Marshal.ReleaseComObject(aCOMObject);
Use SDK tool TlbImp
Or just add reference to server in Visual Studio
Type library can be imported from executable Reference resulting metadata file in project Custom IDL attributes are preserved
IDL: IDL: [custom(„FE42746F-...-AC...84178”, “Value”)] [custom(„FE42746F-...-AC...84178”, “Value”)] .NET: .NET: [IdlAttribute(„FE42746F-...-AC...84178”, “Value”)] [IdlAttribute(„FE42746F-...-AC...84178”, “Value”)]
Library level
Library to namespace
Module is converted to class
Constants within module to constants within that class Module functions to static members of the class
library library COMLib { COMLib { interface Widget {}; interface Widget {}; coclass coclass Slingshot {}; Slingshot {}; } namespace namespace COMLib { COMLib { interface Widget {}; interface Widget {}; class Slingshot {}; class Slingshot {}; } custom(0F21F359-AB84-41e8-9A78-36D110E6D2F9, custom(0F21F359-AB84-41e8-9A78-36D110E6D2F9, „MyNamespace.Class") „MyNamespace.Class")
Interfaces
Base interfaces IUnknown and IDispatch are stripped
Type definitions are not imported
Imported as the underlying types
Properties
Generates corresponding get
get and set set
Events
namespace namespace COMLib { COMLib { interface Widget {}; interface Widget {}; class Slingshot {}; class Slingshot {}; }
Isomorphic Types
Integer, Float Values
Non-isomorphic Types
Booleans, Chars, Strings, Arrays, Objects
Copying vs. Pinning
Isomorphic Types are pinned Non-Isomorphic Types are copied Watch out with Unicode Strings!
Passing by value actually passes a reference! Passing by reference creates new String
Use attributed struct in .NET
StructLayout
StructLayout attribute for
Sequential
Sequential layout – is a regular structure
Default Union
Union layout
Explicit
Explicit layout [StructLayout(Layout.Explicit)] [StructLayout(Layout.Explicit)] public class SYSTEM_INFO public class SYSTEM_INFO { { [FieldOffset(0)] ulong [FieldOffset(0)] ulong OemId; OemId; [FieldOffset(4)] ulong [FieldOffset(4)] ulong PageSize; PageSize; [FieldOffset(16)] ulong [FieldOffset(16)] ulong ActiveProcessorMask; ActiveProcessorMask; [FieldOffset(20)] ulong [FieldOffset(20)] ulong NumberOfProcessors; NumberOfProcessors; [FieldOffset(24)] ulong [FieldOffset(24)] ulong ProcessorType; ProcessorType; } }
COM uses containment/aggregation .NET adds implementation inheritance Mixed-mode objects
Managed objects can derive from COM objects
Must have metadata available Must be instantiatable Must be aggregatable
Interfaces can be inherited
IUnknown, IDispatch derivation is removed
namespace CallingCOMServer namespace CallingCOMServer { using System; using System; using COMServerLib; using COMServerLib; public class DotNET_COMClient public class DotNET_COMClient {... {... public static int public static int Main(string[] args) Main(string[] args) { MyCOMServer myS MyCOMServer myS = new MyCOMServer(); = new MyCOMServer(); return return (myS.Add (myS.Add (17,4)); (17,4)); } } }; };
Accomplished by Reflection API
No difference whether COM or .NET object
Type can be obtained from ProgID or ClsID
InvokeMember
InvokeMember to access methods and properties
Metadata is not needed, but useful
COM object is wrapped by __ComObject
namespace LateBoundClient namespace LateBoundClient { using System.Reflection; using System.Reflection; ... ... Type typ; Type typ; Object Object obj;
Object[] prms Object[] prms = new = new Object[2]; Object[2]; int int r; r; typ typ = Type.GetTypeFromProgID(„MyLib.MyServer"); = Type.GetTypeFromProgID(„MyLib.MyServer");
= Activator.CreateInstance(typ); prms[0] = 10; prms[0] = 10; prms[1] = 150; prms[1] = 150; r = (int)typ.InvokeMember(„aMethod", r = (int)typ.InvokeMember(„aMethod", BindingFlags.InvokeMethod, null, obj, prms); BindingFlags.InvokeMethod, null, obj, prms); ... ... }
namespace AutomationClient namespace AutomationClient { using EmotionalTulip; using EmotionalTulip; using System.Reflection; using System.Reflection; ... ... TulipApplication tulipApp = new TulipApplication(); TulipApplication tulipApp = new TulipApplication(); Type t Type t = tulipApp.GetType(); tulipApp.GetType(); Object[] prms Object[] prms = new Object[1]; = new Object[1]; prms[0] = true; prms[0] = true; t.InvokeMember("Visible", t.InvokeMember("Visible", BindingFlags.SetProperty, BindingFlags.SetProperty, null, tulipApp, prms); null, tulipApp, prms); t.InvokeMember("Exit", t.InvokeMember("Exit", BindingFlags.InvokeMethod, BindingFlags.InvokeMethod, null, tulipApp, null); null, tulipApp, null); ... ... }
Most COM objects are single threaded .NET thread must initialize COM apartment
Deferred initialization Initialized either as MTA or STA
Runtime provides transparent use of COM threads
Access apartment threaded objects through proxy Can avoid proxy by setting apartment state of thread
Let the runtime initialize the default MTA Setting ApartmentState explicitly eliminates proxy
Do before the object is created!
using System.Threading; using System.Threading; using APTOBJLib; using APTOBJLib; AptSimple AptSimple obj
= new AptSimple (); ();
= 1; using System.Threading; using System.Threading; using APTOBJLib; using APTOBJLib; Thread.CurrentThread.ApartmentState Thread.CurrentThread.ApartmentState = = ApartmentState.STA; ApartmentState.STA; AptSimple AptSimple obj
= new AptSimple();
= 1;
COM reports errors via result code
.NET methods throws exceptions Runtime manages transition
HRESULT specifies exception class Extended information via IErrorInfo
COM object should implement interface
ErrorCode
ErrorCode, HelpLink HelpLink, Message Message
Source
Source, StackTrace StackTrace, TargetSite TargetSite
PreserveSigAttribute
PreserveSigAttribute in custom wrapper
Suppresses translation to exception
Provide and register Type Libraries Use Automation Compliant Data Types Use Chunky Calls
Marshalling may be costly
Explicitly Free External Resources Avoid Using Members of System.Object
Object, Equals, Finalize, GetHashCode, GetType MemberwiseClone and ToString
void*
void* parameters need marshalling
Success HRESULTS
HRESULTS cannot be distinguished
Failure HRESULT
HRESULT do raise exceptions
Success code don‘t
Typedef
Typedef do not get through
Variable length arrays cannot be imported
is imported as
HRESULT DoSomething(int HRESULT DoSomething(int cb, [in] byte buf[]); cb, [in] byte buf[]); public void DoSomething(int public void DoSomething(int cb, ref Byte buf); cb, ref Byte buf);
Use .NET class like COM class
Type and methods must be public
Early and late bound activation Convert Metadata to Type Library Wrapper of .NET component must be registered
Can use RegAsm
RegAsm tool
Responsibilities of the COM Callable Wrapper
Preserving Object Identity Maintaining object lifetime Proxying custom interfaces Marshalling method calls Synthezising selected interfaces
COM Object CCW .NET Object
IUnknown IDispatch IIfc IIfc
Single CCW for .NET class insures identity
No matter how many COM classes call to it No matter how object is created
Each interface of the CCW is referenced counted
No reference counting on the managed object
Managed objects lives at least while the CCW lives
But finalization is undeterministic Managed objects may occasionally leak
Should be explicitly freed using CoEEShutDown
CoEEShutDown
myClass
myClass implementing myItf myItf derived from myBase
myBase
_myClass
_myClass: all methods of myItf, myBase, myClass
_myBase
_myBase: all methods of myBase
myItf
myItf: all methods of myItf
_Object
_Object
Additional interfaces implemented on CCW
IUnknown
Standard implementation for lifetime management
IDispatch
Automation compatible or internal
ITypeInfo, IProvideClassInfo, IErrorInfo
Use SDK tool TlbExp
Type library exported from assembly
Use the TypeLibConverter
TypeLibConverter Class
Leave the conversion to RegAsm
RegAsm utility
Export process involves a lot of conversions
Name collisions are solved using name mangling
tlbexp tlbexp AssemblyName.dll AssemblyName.dll /out:TlbFile /out:TlbFile
public public class MyClass class MyClass { public public void Write(string void Write(string p, ref p, ref string string q) q) {...}; {...}; public public int int GetInt() {...}; GetInt() {...}; } [uuid(…), hidden, dual, odl, nonextensible, oleautomation] [uuid(…), hidden, dual, odl, nonextensible, oleautomation] interface _MyClass interface _MyClass : IDispatch { : IDispatch { [propget] HRESULT ToString([out, retval] BSTR* pRetVal); [propget] HRESULT ToString([out, retval] BSTR* pRetVal); HRESULT Equals([in] VARIANT obj, HRESULT Equals([in] VARIANT obj, [out, retval] [out, retval] VARIANT_BOOL* pRetVal); VARIANT_BOOL* pRetVal); HRESULT GetHashCode([out, retval] long* pRetVal HRESULT GetHashCode([out, retval] long* pRetVal);
);
HRESULT GetType([out, retval] _Type** pRetVal); HRESULT GetType([out, retval] _Type** pRetVal); HRESULT Write([in] BSTR p, [in, out] BSTR* q); HRESULT Write([in] BSTR p, [in, out] BSTR* q); HRESULT GetInt([out, retval] long* pRetVal); HRESULT GetInt([out, retval] long* pRetVal); } [ uuid(...) ] [ uuid(...) ] coclass coclass MyClass MyClass { [default] interface _MyClass; [default] interface _MyClass; interface interface _Object; _Object; }
Attributes control the conversion process
[In], [Out], [MarshalAs(...)]
[In], [Out], [MarshalAs(...)]
Parameters, Fields
[GuidAttribute("...")]
[GuidAttribute("...")]
Assemblies, classes, interfaces, structures, enumerations,
Uses Marshal.GenerateGuidForType
Marshal.GenerateGuidForType function
[ComVisibleAttribute(...)]
[ComVisibleAttribute(...)]
Individual type or assembly level
Adds assembly information to the registry
regasm
regasm assembly /tlb: /tlb:tlbfile /regfile /regfile:regfile [HKCR\ [HKCR\Namespace.Class Namespace.Class] @=„ ] @=„Namespace.Class Namespace.Class" [HKCR\ [HKCR\Namespace.Class Namespace.Class\CLSID] @="{...}" \CLSID] @="{...}" [HKCR\CLSID\{...}] @=" [HKCR\CLSID\{...}] @="Namespace.Class Namespace.Class" [HKCR\CLSID\{...}\InprocServer32] [HKCR\CLSID\{...}\InprocServer32] @="C:\WINNT\System32\MSCorEE.dll" @="C:\WINNT\System32\MSCorEE.dll" "ThreadingModel"="Both" "ThreadingModel"="Both" "Class"=" "Class"="Namespace.Class Namespace.Class" "Assembly"=„ "Assembly"=„AssemblyName AssemblyName, Ver=1.0.0.0, Loc=""" , Ver=1.0.0.0, Loc=""" [HKCR\CLSID\{...}\ProgId] @=" [HKCR\CLSID\{...}\ProgId] @="Namespace.Class Namespace.Class"
You know: COM uses HRESULTs, .NET exceptions Can‘t propagate exceptions to unmanaged code
User defined exception classes define HRESULT
Default is HRESULT of base class
Class NoAccessException Class NoAccessException : : public ApplicationException public ApplicationException { { NoAccessException() NoAccessException() { HResult HResult = E_ACCESSDENIED; = E_ACCESSDENIED; } } } } CMyClass::MethodThatThrows CMyClass::MethodThatThrows { { NoAccessException NoAccessException e = e = new NoAccessException(); new NoAccessException(); throw e; throw e; }
Simple requirements to be met
Class must be public Class must have default constructor
Containment:
Simply create instance and delegate calls to it
Aggregation
Just use CoCreateInstance
COM object‘s IUnknown as outer IUnknown
Queries for unsupported interfaces are delegated back Reference counting is done on outer IUnknown
Managed interfaces provide inheritance
Always derive from IUnknown or IDispatch Interface inheritance is flattened
Advantages with versioning
Users of interfaces not effected when base changes Interfaces may be separately attributed
Disadvantage
Interfaces cannot be used polymorphically
Original managed code
interface IBase interface IBase { { void m1(); void m1(); } } interface IDrvd interface IDrvd : IBase : IBase { { void m2(); void m2(); } } class CDrvdImpl class CDrvdImpl : IDrvd : IDrvd { void m1(); void m1(); void m2(); void m2(); } }
Converted type library
interface interface IBase IBase : IDispatch : IDispatch { { void m1(); void m1(); } } interface interface IDrvd IDrvd : IDispatch : IDispatch { { void m2(); void m2(); } } interface interface _CDrvdImpl _CDrvdImpl : IDispatch : IDispatch { { boolean boolean Equals(); Equals(); // and other methods of object // and other methods of object void m1(); void m1(); void m2(); void m2(); } coclass coclass CDrvdImpl CDrvdImpl { interface IBase; interface IBase; interface IDerived; interface IDerived; interface _CDerivedImpl; interface _CDerivedImpl; interface _Object; interface _Object; } }
Parameterized constructors are not exposed
Always uses default constructor
Static methods are not exposed
Wrapping with instance method required
Calling static functions in DLLs P/Invoke provides services
Locates implementing DLL Loads DLL Finds function address
Fuzzy name matching algorithm
Pushes arguments on stack Performs marshalling Enables pre-emptive garbage collection Transfers control to unmanaged code
namespace namespace HelloWorld HelloWorld { using using System; System; class class MyClass MyClass { [dllimport(„user32.dll“, CharSet=CharSet.Ansi)] [dllimport(„user32.dll“, CharSet=CharSet.Ansi)] static static extern int extern int MessageBox(int MessageBox(int h, string h, string m, m, string string c, int c, int t); t); public public static static int int Main() Main() { return return MessageBox(0, "Hello MessageBox(0, "Hello World!", "Caption", 0); World!", "Caption", 0); } } }
Strings are copied
Converted to platform format: ANSI or Unicode Strings are never copied back – use StringBuilder
StringBuilder
Interfaces supported
Cannot be used as out parameter
Arrays of primitive types can be used Custom marshalling using attributes
int int SomeFunc SomeFunc( int ( int sel, void* buf, int sel, void* buf, int size); size); [DllImport("some.dll")] [DllImport("some.dll")] static static extern int extern int SomeFunc SomeFunc(int (int sel, sel, [MarshalAs(UnmanagedType.LPArray)] byte[] buf, [MarshalAs(UnmanagedType.LPArray)] byte[] buf, int int size); size);
[DllImport("some.dll", EntryPoint="SomeFunc")]
DllImport("some.dll", EntryPoint="SomeFunc")] static static extern int extern int SomeFunc_String SomeFunc_String(int (int sel, sel, StringBuilder StringBuilder sb, int sb, int size); size); [DllImport("some.dll", EntryPoint="SomeFunc")] [DllImport("some.dll", EntryPoint="SomeFunc")] static static extern int extern int SomeFunc_Int SomeFunc_Int(int (int sel, sel, ref ref int int i, int i, int size); size);
Given a method in some DLL Standard declaration may need special decoding May use taylored versions
Unmanaged code can call back to managed code
Unmanaged parameter is function pointer Must supply parameter as delegate P/Invoke creates callback thunk
Passes address of thunk as callback parameter
Managed Code .NET Application Call passes pointer to callback function Implementation of callback function Unmanaged Code DLL DLL function
public public class class EnumReport EnumReport { public public bool bool Report(int Report(int hwnd, int hwnd, int lParam) lParam) { // report { // report the the window window handle handle Console.Write("Window Console.Write("Window handle is handle is "); "); Console.WriteLine(hwnd); Console.WriteLine(hwnd); return return true; true; } }; }; public public class class SampleClass SampleClass { delegate delegate bool bool CallBack(int CallBack(int hwnd, int hwnd, int lParam); Param); [DllImport("user32")] [DllImport("user32")] static static extern int extern int EnumWi EnumWindows(CallBack ndows(CallBack x, int x, int y); y); public public static static void void Main() Main() { EnumReport EnumReport er = new er = new EnumReport(); numReport(); CallBack CallBack myCallBack myCallBack = = new new CallBack(er.Report); CallBack(er.Report); EnumWindows(myCallBack, 0); EnumWindows(myCallBack, 0); } }
Suppressing demands for unmanaged code permission Calling class must be fully trusted
Unmanaged code runs with no runtime checks
Any code calling that class must be fully trusted
[SuppressUnmanagedCodeSecurityAttribute()] [SuppressUnmanagedCodeSecurityAttribute()] [dllimport("some.dll")] private static extern [dllimport("some.dll")] private static extern int int EntryPoint(args); EntryPoint(args);
Fundamental building blocks
Just-In-Time activation, synchronization, pooling Transaction processing, shared properties, etc.
Mostly restricted to Windows 2000 COM+ generates and services context object .NET Client Context Object A Context Object B .NET Framework COM+ Services Object A
(TX required)
Object B
(TX supported)
Derive class from System.ServicedComponent
System.ServicedComponent
Add attributes to parameterize services
Use attributes from Microsoft.ComServices
Microsoft.ComServices namespace
Helper classes: ContextUtil
ContextUtil, RegistrationHelper RegistrationHelper
Use regsvcs
regsvcs to register assembly as server
Or rely on lazy registration
using using System; System; using System.Runtime.CompilerServices; using System.Runtime.CompilerServices; using Microsoft.ComServices; using Microsoft.ComServices; [assembly:Application [assembly:ApplicationName(“TestApp”)] Name(“TestApp”)] [Transaction(Transact [Transaction(TransactionOption.Required)] ionOption.Required)] public class Account public class Account : ServicedComponent : ServicedComponent { { [AutoComplete] [AutoComplete] public public void Debit(int void Debit(int amount) { amount) { // Any exception thrown // Any exception thrown here aborts transaction here aborts transaction // otherwise transaction commits // otherwise transaction commits } } } class client { class client { static int static int Main() Main() { Account accountX Account accountX = new Account(); = new Account(); accountX.Debit(100); accountX.Debit(100); return 0; return 0; } } }
Preserving investments and moving on
Keep COM components and enhance using .NET Add managed components to COM projects Use Windows Component Services
Prepare COM components for use with .NET .NET offers maximum transparency