Module 15 (Optional): Interoperating Between Managed and Unmanaged Code |
25 |
|
|
|
Demonstration: Using the Type Library Importer
Topic Objective
To demonstrate how to use the Type Library Importer to convert the type definitions found within a COM type library into equivalent definitions in a .NET assembly.
Lead-in
In this demonstration you will learn how to use the Type Library Importer to convert the type definitions found within a COM type library into equivalent definitions in a .NET assembly.
*****************************ILLEGAL FOR NON-TRAINER USE******************************
The Type Library Importer (Tlbimp.exe) can be used to convert the coclasses and interfaces found within a COM type library into equivalent definitions in a
.NET assembly. The output of the Type Library Importer is an assembly that contains metadata for the types defined within the original type library. The Type Library Importer performs the following tasks:
!Converts unmanaged COM coclasses to C# classes with a constructor (without parameters) and no other methods.
!Converts unmanaged COM vtable interfaces to C# interfaces.
!Converts unmanaged COM structures to C# structures with public fields.
The syntax for using the Type Library Importer is as follows:
tlbimp inputFile [options]
In the previous example, inputFile is the name of the file that contains the COM type library. This file can be either a stand-alone type library (.TLB) file or a DLL. The following command imports a type library file and generates a .NET assembly named myTest.dll:
tlbimp myTest.tlb
26 |
Module 15 (Optional): Interoperating Between Managed and Unmanaged Code |
The /out: option specifies the name of the assembly that is created as a result of running the Type Library Importer. The following command creates myNewTest as a DLL:
tlbimp myTest.tlb /out:myNewTest.dll
If the output assembly’s file name is equivalent to the input type library’s file name, the Type Library Importer will generate an error, because the input file cannot be overwritten.
The /delaysign option delays signing of the assembly. The /delaysign option must be used with the /keycontainer:, /keyfile:, or /publickey: option. For a complete list of all the options that can be used with the Type Library Importer, see “Type Library Importer (Tlbimp.exe)” in the .NET Framework SDK documentation.
Module 15 (Optional): Interoperating Between Managed and Unmanaged Code |
27 |
|
|
|
Threading Models
Topic Objective
To describe the threading models in managed and unmanaged code.
Lead-in
The threading models that are supported by COM components and the .NET Framework are different. While managed objects use synchronized regions…
!COM Components Use Apartments to Synchronize Access to Managed Resources
!Runtime Creates/Initializes Apartment When Calling a COM Object
using System.Threading; using System.Threading; using APTOBJLib;
using APTOBJLib;
……
Thread.CurrentThread.ApartmentState =
Thread.CurrentThread.ApartmentState =
ApartmentState.STA;
ApartmentState.STA;
AptSimple obj = new AptSimple (); AptSimple obj = new AptSimple (); obj.Counter = 1;
obj.Counter = 1;
! To ensure that the main thread of an application is STA
……
[STAThread]
[STAThread] static void Main()
static void Main()
……
*****************************ILLEGAL FOR NON-TRAINER USE******************************
COM components use apartments to synchronize access to managed resources. In contrast, managed objects use synchronized regions; synchronization primitives such as mutexes, locks, and completion ports; and synchronized contexts to ensure that all shared resources are used in a thread-safe manner.
For interoperability, the common language runtime creates and initializes an apartment when calling a COM object. A managed thread can create and enter a single-threaded apartment (STA) that contains only one thread, or a multithreaded apartment (MTA) that contains one or more threads. When a COM apartment and a thread-generated apartment are compatible, COM permits the calling thread to make calls directly to the COM object. If the apartments are incompatible, COM creates a compatible apartment and marshals all calls through a proxy in the new apartment.
On the first call to unmanaged code, the runtime calls CoInitializeEx to initialize the COM apartment as either an MTA or an STA. You can control the type of apartment created by setting the System.Threading.ApartmentState property on the thread to MTA, STA, or Unknown. As long as the proxy and stub are registered or the type library is registered, you do not have to set this property. If neither the proxy and stub nor the type library is registered, an InvalidCastException can occur when calling a COM object from managed code.
28 |
Module 15 (Optional): Interoperating Between Managed and Unmanaged Code |
The following table lists the ApartmentState enumeration values and shows the comparable COM apartment initialization call.
ApartmentState |
|
enumeration value |
COM apartment initialization |
|
|
MTA |
CoInitializeEx(NULL, COINIT_MULTITHREADED) |
STA |
CoIntializeEx(NULL, |
|
COINIT_APARTMENTTHREADED) |
Unknown |
CoInitializeEx(NULL, COINIT_MULTITHREADED) |
Whenever the COM object and the managed thread are in incompatible apartments, all calls on the object are made through a COM created proxy. For example, calls between Windows Forms controls whose COM objects must reside in an STA and managed code whose thread’s ApartmentState is MTA.
The following example shows how to create an STA apartment-threaded COM object, AptSimple, from managed code:
using System.Threading; using APTOBJLib;
…
AptSimple obj = new AptSimple (); obj.Counter = 1;
To eliminate the use of proxies and stubs and significantly enhance performance, set the ApartmentState on the thread before creating the object, as shown in the following example:
using System.Threading; using APTOBJLib;
…
Thread.CurrentThread.ApartmentState = ApartmentState.STA; AptSimple obj = new AptSimple ();
obj.Counter = 1;
Module 15 (Optional): Interoperating Between Managed and Unmanaged Code |
29 |
|
|
|
A thread can only initialize a COM apartment once. Since the runtime initializes the apartment before making the very first call to unmanaged code on that thread, you should set the ApartmentState as early as possible. Changing the ApartmentState after the apartment has been initialized has no effect. You can neither un-initialize nor re-initialize an apartment. In some situations, the thread may already have called into unmanaged code before the ApartmentState could be set. In such cases, you cannot change the apartment type after the thread is initialized. Your only option is to create a new thread.
As an alternative to setting the ApartmentState enumeration, you can apply the System.STAThreadAttribute or System.MTAThreadAttribute to the main entry point of the application. By applying these attributes you ensure that the main thread of an application is in the proper state. For example:
…
[STAThread]
static void Main()
…
After setting the apartment state, you can check the state programmatically, as in the following example:
Thread.CurrentThread.ApartmentState = ApartmentState.STA;
if (Thread.CurrentThread.ApartmentState == ApartmentState.STA) //STA apartment state
else
//incompatible apartment state
30 |
Module 15 (Optional): Interoperating Between Managed and Unmanaged Code |
Signature Translation and Error Handling
Topic Objective
To describe how signature translation works.
Lead-in
Signature translation is the mechanism by which managed return values are converted into COM HRESULTs.
*****************************ILLEGAL FOR NON-TRAINER USE******************************
COM components accept COM data types and return HRESULTs. However, classes in the .NET Framework do not use HRESULTs. In order to satisfy both models, every method of a managed type has a .NET Framework signature and an implied COM signature.
When COM interoperability services imports a COM type, it produces a
.NET Framework method signature equivalent to the original COM method signature. This is referred to as signature translation. During signature translation, COM parameters, return values, and HRESULTs are mapped to corresponding entities in the .NET Framework method signature, as shown in the illustration on the slide.
When the COM object returns an error HRESULT, the runtime converts the HRESULT to an exception, which is thrown to the caller. The type of exception that is generated depends on the error returned from the COM object. Note that any non-error HRESULTs will be lost in this conversion. For example, if a COM object returns any success HRESULT other than S_OK, such as S_FALSE, no exception is thrown, but the managed caller does not receive the HRESULT. You must manually generate the runtime callable wrappers to avoid this issue.
A managed signature is converted to an unmanaged signature by changing the managed return value to an [out, retval] parameter and changing the type of the unmanaged return value to HRESULT. The COM signature returns an HRESULT and has an additional output parameter for the return value. The return value from the managed implementation always returns as an [out, retval] parameter added to the end of the unmanaged signature, whereas the unmanaged signature always returns an HRESULT. If the managed method has a void return, the runtime omits the [out, retval] parameter.
Module 15 (Optional): Interoperating Between Managed and Unmanaged Code |
31 |
|
|
|
Under some circumstances, it is preferable to leave the managed signature unchanged. You can use the PreserveSig attribute to do this. The following examples show the translation from a managed signature to an unmanaged signature.
Managed signature:
[PreserveSig] short DoSomething(short i);
Unmanaged signature:
short DoSomething ([in] short i);
32 |
Module 15 (Optional): Interoperating Between Managed and Unmanaged Code |
Marshaling
Topic Objective
To describe how COM types relate to .NET Framework types and classes.
Lead-in
Types used in COM have corresponding .NET Framework built-in value types or classes.
Direct students to the Student Notes and explain that the table that is referred to in the slide may be found in the Student Notes.
!Wrappers perform data marshaling
#Support for marshaling data to and from COM almost always provides the correct marshaling behavior
!Table shows corresponding COM and C# types
!MarshalAsAttribute to change the marshaling behavior
public void M1 public void M1
([MarshalAs(UnmanagedType.LPWStr)]String msg); ([MarshalAs(UnmanagedType.LPWStr)]String msg);
!Two ways to customize the RCW to handle types
#Edit the interop assembly
#Create a wrapper manually
*****************************ILLEGAL FOR NON-TRAINER USE******************************
When marshaling data between managed and unmanaged code, the interop marshaler must recognize the representations of the data being passed.
!For blittable types, managed and unmanaged representations are always the same: a 4-byte integer is always marshaled to a 4-byte integer. The interop marshaler uses the managed signature to determine the data representation.
!For non-blittable types, the interop marshaler recognizes the managed representation from its method signature, but is unable to do the same for the unmanaged representation. To marshal non-blittable types, you can use one of the following techniques:
•Allow the marshaler to infer the representation from the managed representation.
•Supply the unmanaged data representation explicitly.
Module 15 (Optional): Interoperating Between Managed and Unmanaged Code |
33 |
|
|
|
The following table shows data types used in COM and their corresponding
.NET Framework built-in value types or classes. Any type not explicitly identified in this table is converted to an Int32 system type.
COM value type |
COM reference type |
C# Data Type |
bool |
bool * |
int |
char, small |
char *, small * |
System.SByte |
short |
short * |
short |
long, int |
long *, int * |
int |
Hyper |
hyper * |
long |
unsigned char, byte |
unsigned char *, byte * |
byte |
wchar_t, unsigned short |
wchar_t *, unsigned short * |
ushort |
unsigned long, unsigned int |
unsigned long *, unsigned int * |
uint |
unsigned hyper |
unsigned hyper * |
ulong |
float |
float * |
float |
double |
double * |
double |
VARIANT_BOOL |
VARIANT_BOOL * |
bool |
void * |
void ** |
System.IntPtr |
HRESULT |
HRESULT * |
System.IntPtr |
SCODE |
SCODE * |
int |
BSTR |
BSTR * |
string |
LPSTR or [string, …] |
LPSTR * |
string |
char * |
|
|
LPWSTR or [string, …] |
LPWSTR * |
string |
wchar_t * |
|
|
VARIANT |
VARIANT * |
object |
DECIMAL |
DECIMAL * |
System.Decimal |
DATE |
DATE * |
System.DateTime |
GUID |
GUID * |
System.Guid |
CURRENCY |
CURRENCY * |
System.Decimal |
IUnknown * |
IUnknown ** |
object |
IDispatch * |
IDispatch ** |
object |
SAFEARRAY(type) |
SAFEARRAY(type) * |
type[ ] |
34 |
Module 15 (Optional): Interoperating Between Managed and Unmanaged Code |
The following table lists COM value and reference types that convert to corresponding element types. For example, a COM coclass automatically maps to a managed class with the same name.
COM value type |
COM Reference type |
Element type |
typedef BaseType |
ByRef BaseType |
BaseType |
MyType |
|
|
MyStruct |
ByRef VALUETYPE<MyStruct> |
valuetype<MyStruct> |
MyEnum |
ByRef VALUETYPE<MyEnum> |
valuetype<MyEnum> |
MyInterface * |
ByRef CLASS <MyInterface> |
Class<MyInterface> |
MyCoClass |
ByRef CLASS <_Class> |
class <_Class> |
You can apply the System.Runtime.InteropServices.MarshalAsAttribute to a method parameter, class field, or return value to change the marshaling behavior. For example, a string is converted to a BSTR type when marshaled from managed to unmanaged code, unless you explicitly apply the MarshalAsAttribute to marshal the string to another type, such as LPWSTR. You can apply this attribute to a parameter, field, or return value within the source of the type definition, as shown in the following examples where it is specified that msg is to be marshaled as a null-terminated buffer of Unicode characters (LPWStr).
Apply the MarshalAsAttribute to a parameter:
public void M1 ([MarshalAs(UnmanagedType.LPWStr)]String msg);
Apply the MarshalAsAttribute to a field within a class:
class MsgText {
[MarshalAs(UnmanagedType.LPWStr)] Public String msg;
}
Apply the MarshalAsAttribute to a return value:
[return: MarshalAs(UnmanagedType.LPWStr)] public String GetMessage();