540 likes | 820 Views
.NET and the Old World. COM Interop & P/Invoke. 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 Section 3: Calling .NET Services from COM
E N D
.NET and the Old World COM Interop & P/Invoke
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 • Section 3: Calling .NET Services from COM • Section 4: Calling Platform Services from .NET
Existing Stuff Must Coexist • 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
What is different in the two Worlds? • Managed Code vs. Unmanaged Code • Lifetime management • Metadata • Common Type System • Inheritance concept • Threading model • Error handling mechanisms • Registration
Calling COM Services from .NET • 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
Early Bound Activation • 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 aCOMObject; aCOMObject = new COMLib.COMClass;
IUnknown IDispatch .NET Object COM Object IIfc RCW IIfc Runtime Callable Wrappers • Preserving object identity • Maintaining object lifetime • Proxying interfaces • Marshalling method calls • Consuming selected interfaces
Consuming Selected Interfaces • ISupportErrorInfo • Additional information with exceptions • IProvideClassInfo • Typed wrapper • ITypeInfo • TlbImpconverts references toSystem.Type
Identity and Lifetime Control • COM lifetime control is wrapped by RCW • COM object released when RCW garbage collected • Non-deterministic finalization issue COMLib.COMClass aCOMObject; aCOMObject = new COMLib.COMClass(); aCOMObject.SomeFunc(); Marshal.ReleaseComObject(aCOMObject);
Converting Type Library to Metadata • 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: [custom(„FE42746F-...-AC...84178”, “Value”)] .NET: [IdlAttribute(„FE42746F-...-AC...84178”, “Value”)]
Conversions by TlbImp 1/2 • 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 COMLib { interface Widget {}; coclass Slingshot {}; } namespace COMLib { interface Widget {}; class Slingshot {}; } custom(0F21F359-AB84-41e8-9A78-36D110E6D2F9, „MyNamespace.Class")
Conversions by TlbImp 2/2 • Interfaces • Base interfaces IUnknown and IDispatch are stripped • Type definitions are not imported • Imported as the underlying types • Properties • Generates corresponding get and set • Events • namespace COMLib { interface Widget {}; class Slingshot {}; }
Marshalling • 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
Marshalling a Union • Use attributed struct in .NET • StructLayout attribute for • Sequential layout – is a regular structure • Default Union layout • Explicit layout [StructLayout(Layout.Explicit)] public class SYSTEM_INFO { [FieldOffset(0)] ulong OemId; [FieldOffset(4)] ulong PageSize; [FieldOffset(16)] ulong ActiveProcessorMask; [FieldOffset(20)] ulong NumberOfProcessors; [FieldOffset(24)] ulong ProcessorType; }
Inheritance • 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
Calling an COM Server namespace CallingCOMServer { using System; using COMServerLib; public class DotNET_COMClient {... public static int Main(string[] args) { MyCOMServer myS = new MyCOMServer(); return (myS.Add (17,4)); } } };
Late Bound Activation • Accomplished by Reflection API • No difference whether COM or .NET object • Type can be obtained from ProgID or ClsID • InvokeMember to access methods and properties • Metadata is not needed, but useful • COM object is wrapped by __ComObject
Late Bound Call to COM Server namespace LateBoundClient { using System.Reflection;...Type typ;Object obj;Object[] prms = new Object[2];int r; typ = Type.GetTypeFromProgID(„MyLib.MyServer"); obj = Activator.CreateInstance(typ);prms[0] = 10; prms[1] = 150; r = (int)typ.InvokeMember(„aMethod", BindingFlags.InvokeMethod, null, obj, prms); ... }
Calling an Automation Server namespace AutomationClient{using EmotionalTulip;using System.Reflection; ... TulipApplication tulipApp = new TulipApplication(); Type t = tulipApp.GetType();Object[] prms = new Object[1];prms[0] = true; t.InvokeMember("Visible",BindingFlags.SetProperty,null, tulipApp, prms); t.InvokeMember("Exit",BindingFlags.InvokeMethod, null, tulipApp, null); ...}
Threading • 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
Using ApartmentState • Let the runtime initialize the default MTA • Setting ApartmentState explicitly eliminates proxy • Do before the object is created! using System.Threading;using APTOBJLib; AptSimple obj = new AptSimple (); obj.Counter = 1; using System.Threading; using APTOBJLib; Thread.CurrentThread.ApartmentState = ApartmentState.STA; AptSimple obj = new AptSimple(); obj.Counter = 1;
Results and Exceptions • 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, HelpLink, Message • Source, StackTrace, TargetSite • PreserveSigAttributein custom wrapper • Suppresses translation to exception
Design for Interoperability • 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
Restrictions and Limitations • void* parameters need marshalling • Success HRESULTS cannot be distinguished • Failure HRESULT do raise exceptions • Success code don‘t • Typedef do not get through • Variable length arrays cannot be imported is imported as HRESULT DoSomething(int cb, [in] byte buf[]); public void DoSomething(int cb, ref Byte buf);
Calling .NET Services from COM • 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 tool
.NET Object CCW COM Callable Wrappers • Responsibilities of the COM Callable Wrapper • Preserving Object Identity • Maintaining object lifetime • Proxying custom interfaces • Marshalling method calls • Synthezising selected interfaces IUnknown IIfc COM Object IDispatch IIfc
Identity and Lifetime Control • 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
Synthesizing Selected Interfaces • myClass implementing myItf derived from myBase • _myClass: all methods of myItf, myBase, myClass • _myBase: all methods of myBase • myItf: all methods of myItf • _Object • Additional interfaces implemented on CCW • IUnknown • Standard implementation for lifetime management • IDispatch • Automation compatible or internal • ITypeInfo, IProvideClassInfo, IErrorInfo
Exporting a Type Library • Use SDK tool TlbExp • Type library exported from assembly • Use the TypeLibConverter Class • Leave the conversion to RegAsm utility • Export process involves a lot of conversions • Name collisions are solved using name mangling tlbexp AssemblyName.dll /out:TlbFile
Sample Conversion public class MyClass {public void Write(string p, ref string q) {...};public int GetInt() {...};} [uuid(…), hidden, dual, odl, nonextensible, oleautomation]interface _MyClass : IDispatch {[propget] HRESULT ToString([out, retval] BSTR* pRetVal);HRESULT Equals([in] VARIANT obj,[out, retval]VARIANT_BOOL* pRetVal);HRESULT GetHashCode([out, retval] long* pRetVal); HRESULT GetType([out, retval] _Type** pRetVal);HRESULT Write([in] BSTR p, [in, out] BSTR* q);HRESULT GetInt([out, retval] long* pRetVal);} [ uuid(...) ]coclass MyClass{[default] interface _MyClass;interface _Object;}
Special Attributes • Attributes control the conversion process • [In], [Out], [MarshalAs(...)] • Parameters, Fields • [GuidAttribute("...")] • Assemblies, classes, interfaces, structures, enumerations, or delegates • Uses Marshal.GenerateGuidForType function • [ComVisibleAttribute(...)] • Individual type or assembly level
Registration (RegAsm) • Adds assembly information to the registry • regasmassembly /tlb:tlbfile /regfile:regfile [HKCR\Namespace.Class] @=„Namespace.Class" [HKCR\Namespace.Class\CLSID] @="{...}" [HKCR\CLSID\{...}] @="Namespace.Class" [HKCR\CLSID\{...}\InprocServer32] @="C:\WINNT\System32\MSCorEE.dll" "ThreadingModel"="Both" "Class"="Namespace.Class" "Assembly"=„AssemblyName, Ver=1.0.0.0, Loc=""" [HKCR\CLSID\{...}\ProgId] @="Namespace.Class"
Results and Exceptions • 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 : public ApplicationException{ NoAccessException() {HResult = E_ACCESSDENIED; } } CMyClass::MethodThatThrows{ NoAccessException e = new NoAccessException(); throw e; }
Aggregation and Containment • 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
Exposing Inheritance • 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
Converted Inheritance Tree Converted type library interface IBase : IDispatch { void m1(); } interface IDrvd : IDispatch { void m2(); } interface _CDrvdImpl : IDispatch{ boolean Equals(); // and other methods of object void m1(); void m2(); } coclass CDrvdImpl {interface IBase; interface IDerived; interface _CDerivedImpl;interface _Object; } Original managed code interface IBase { void m1(); } interface IDrvd : IBase { void m2(); } class CDrvdImpl : IDrvd { void m1(); void m2(); }
Restrictions and Limitations • Parameterized constructors are not exposed • Always uses default constructor • Static methods are not exposed • Wrapping with instance method required
Calling Platform Services from .NET • 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
Code Sample namespace HelloWorld{using System;class MyClass {[dllimport(„user32.dll“, CharSet=CharSet.Ansi)] static extern int MessageBox(int h, string m, string c, int t);public static int Main() {return MessageBox(0, "Hello World!", "Caption", 0); } }}
Marshalling • Strings are copied • Converted to platform format: ANSI or Unicode • Strings are never copied back – use StringBuilder • Interfaces supported • Cannot be used as out parameter • Arrays of primitive types can be used • Custom marshalling using attributes
Showcase for the Standard Marshaller • Given a method in some DLL • Standard declaration may need special decoding • May use taylored versions int SomeFunc( int sel, void* buf, int size); [DllImport("some.dll")]static extern int SomeFunc(int sel, [MarshalAs(UnmanagedType.LPArray)] byte[] buf, int size); [DllImport("some.dll", EntryPoint="SomeFunc")]static extern int SomeFunc_String(int sel, StringBuilder sb, int size); [DllImport("some.dll", EntryPoint="SomeFunc")]static extern int SomeFunc_Int(int sel, ref int i, int size);
Unmanaged Code Managed Code .NET Application DLL Call passes pointer to callback function DLL function Implementation of callback function Callbacks • 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
Code Sample public class EnumReport { public bool Report(int hwnd, int lParam) { // report the window handle Console.Write("Window handle is "); Console.WriteLine(hwnd); return true; }}; public class SampleClass{ delegate bool CallBack(int hwnd, int lParam); [DllImport("user32")] static extern int EnumWindows(CallBack x, int y); public static void Main() { EnumReport er = new EnumReport(); CallBack myCallBack = new CallBack(er.Report); EnumWindows(myCallBack, 0); }}
Security • 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()] [dllimport("some.dll")] private static extern int EntryPoint(args);
Object A (TX required) .NET Client Object B (TX supported) .NET Framework COM+ Services Context Object A Context Object B Component Services • 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
Using COM+ Services • Derive class from System.ServicedComponent • Add attributes to parameterize services • Use attributes from Microsoft.ComServices namespace • Helper classes: ContextUtil, RegistrationHelper • Use regsvcs to register assembly as server • Or rely on lazy registration
Transactions Sample using System;using System.Runtime.CompilerServices;using Microsoft.ComServices; [assembly:ApplicationName(“TestApp”)] [Transaction(TransactionOption.Required)]public class Account : ServicedComponent { [AutoComplete] public void Debit(int amount) {// Any exception thrown here aborts transaction // otherwise transaction commits }} class client {static int Main() {Account accountX = new Account(); accountX.Debit(100); return 0;}}
Summary • 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