Standardmäßiges Marshalling für Objekte
Parameter und Felder, die als System.Object typisiert sind, können für nicht verwalteten Code als einer der folgenden Typen verfügbar gemacht werden:
Als Variante, wenn das Objekt ein Parameter ist.
Als Schnittstelle, wenn das Objekt ein Strukturfeld ist.
Nur COM-Interop unterstützt das Marshalling für Objekttypen. Standardmäßig werden Objekte an COM-Varianten gemarshallt. Diese Regeln gelten nur für den Typ Objekt und gelten nicht für stark typisierte Objekte, die von der Objekt-Klasse abgeleitet werden.
Marshallingoptionen
Die folgende Tabelle zeigt die Marshallingoptionen für den Object-Datentyp. Das MarshalAsAttribute-Attribut stellt mehrere UnmanagedType-Enumerationswerte zum Marshallen von Objekten bereit.
Enumerationstyp | Beschreibung des nicht verwalteten Formats |
---|---|
UnmanagedType.Struct (Standard für Parameter) |
Eine Variante im COM-Stil. |
UnmanagedType.Interface | Eine IDispatch-Schnittstelle, wenn möglich, andernfalls eine IUnknown-Schnittstelle. |
UnmanagedType.IUnknown (Standard für Felder) |
Eine IUnknown-Schnittstelle. |
UnmanagedType.IDispatch | Eine IDispatch-Schnittstelle. |
Das folgende Beispiel zeigt die verwaltete Schnittstellendefinition für MarshalObject
.
Interface MarshalObject
Sub SetVariant(o As Object)
Sub SetVariantRef(ByRef o As Object)
Function GetVariant() As Object
Sub SetIDispatch( <MarshalAs(UnmanagedType.IDispatch)> o As Object)
Sub SetIDispatchRef(ByRef <MarshalAs(UnmanagedType.IDispatch)> o _
As Object)
Function GetIDispatch() As <MarshalAs(UnmanagedType.IDispatch)> Object
Sub SetIUnknown( <MarshalAs(UnmanagedType.IUnknown)> o As Object)
Sub SetIUnknownRef(ByRef <MarshalAs(UnmanagedType.IUnknown)> o _
As Object)
Function GetIUnknown() As <MarshalAs(UnmanagedType.IUnknown)> Object
End Interface
interface MarshalObject {
void SetVariant(Object o);
void SetVariantRef(ref Object o);
Object GetVariant();
void SetIDispatch ([MarshalAs(UnmanagedType.IDispatch)]Object o);
void SetIDispatchRef([MarshalAs(UnmanagedType.IDispatch)]ref Object o);
[MarshalAs(UnmanagedType.IDispatch)] Object GetIDispatch();
void SetIUnknown ([MarshalAs(UnmanagedType.IUnknown)]Object o);
void SetIUnknownRef([MarshalAs(UnmanagedType.IUnknown)]ref Object o);
[MarshalAs(UnmanagedType.IUnknown)] Object GetIUnknown();
}
Der folgende Code exportiert die MarshalObject
Schnittstelle in eine Typbibliothek.
interface MarshalObject {
HRESULT SetVariant([in] VARIANT o);
HRESULT SetVariantRef([in,out] VARIANT *o);
HRESULT GetVariant([out,retval] VARIANT *o)
HRESULT SetIDispatch([in] IDispatch *o);
HRESULT SetIDispatchRef([in,out] IDispatch **o);
HRESULT GetIDispatch([out,retval] IDispatch **o)
HRESULT SetIUnknown([in] IUnknown *o);
HRESULT SetIUnknownRef([in,out] IUnknown **o);
HRESULT GetIUnknown([out,retval] IUnknown **o)
}
Hinweis
Der Interop-Marshaller gibt nach dem Aufruf automatisch alle zugeordneten Objekte innerhalb der Variante frei.
Das folgende Beispiel zeigt einen formatierten Werttypen.
Public Structure ObjectHolder
Dim o1 As Object
<MarshalAs(UnmanagedType.IDispatch)> Public o2 As Object
End Structure
public struct ObjectHolder {
Object o1;
[MarshalAs(UnmanagedType.IDispatch)]public Object o2;
}
Der folgende Code exportiert den formatierten Typ in eine Typbibliothek.
struct ObjectHolder {
VARIANT o1;
IDispatch *o2;
}
Marshalling eines Objekts an eine Schnittstelle
Wenn ein Objekt in COM als Schnittstelle verfügbar gemacht wird, ist diese Schnittstelle die Klassenschnittstelle für den verwalteten Typ Object (die _Object-Schnittstelle). Diese Schnittstelle wird als IDispatch (UnmanagedType) oder als IUnknown (UnmanagedType.IUnknown) in der resultierenden Typbibliothek typisiert. COM-Clients können die Mitglieder der verwalteten Klasse, oder irgendwelche Mitglieder, die durch die abgeleiteten Klassen über die _Object-Schnittstelle implementiert wurden dynamisch abrufen. Der Client kann auch QueryInterface aufrufen, um irgendeine andere Schnittstelle zu erhalten, die vom verwalteten Typ explizit implementiert wurde.
Marshalling eines Objekts an eine Variante
Wenn ein Objekt an eine Variante gemarshallt wird, wird der interne Varianttyp zur Runtime anhand der folgenden Regeln bestimmt:
Wenn der Objektverweis NULL ist (Nothing (nichts) in Visual Basic), wird das Objekt an eine Variante des Typs VT_EMPTY gemarshallt.
Wenn das Objekt eine Instanz eines beliebigen Typs ist, die in der folgenden Tabelle aufgeführt ist, wird der resultierende Varianttyp durch die im Marshaller integrierten und in der Tabelle aufgeführten Regeln bestimmt.
Andere Objekte, die das Marshallingverhalten explizit steuern müssen, können die IConvertible-Schnittstelle implementieren. In diesem Fall wird der Varianttyp durch den Typcode bestimmt, der von der IConvertible.GetTypeCode-Methode zurückgegeben wird. Andernfalls wird das Objekt als eine Variante des Typs VT_UNKNOWN gemarshallt.
Marshalling von Systemtypen an eine Variante
Die folgende Tabelle zeigt verwaltete Objekttypen und ihre entsprechenden COM-Varianttypen. Diese Typen werden nur konvertiert, wenn die Signatur der aufgerufenen Methode vom Typ System.Object ist.
Objekttyp | COM-Varianttyp |
---|---|
NULL-Objektverweis (Nothing (nichts) in Visual Basic). | VT_EMPTY |
System.DBNull | VT_NULL |
System.Runtime.InteropServices.ErrorWrapper | VT_ERROR |
System.Reflection.Missing | VT_ERROR mit E_PARAMNOTFOUND |
System.Runtime.InteropServices.DispatchWrapper | VT_DISPATCH |
System.Runtime.InteropServices.UnknownWrapper | VT_UNKNOWN |
System.Runtime.InteropServices.CurrencyWrapper | VT_CY |
System.Boolean | VT_BOOL |
System.SByte | VT_I1 |
System.Byte | VT_UI1 |
System.Int16 | VT_I2 |
System.UInt16 | VT_UI2 |
System.Int32 | VT_I4 |
System.UInt32 | VT_UI4 |
System.Int64 | VT_I8 |
System.UInt64 | VT_UI8 |
System.Single | VT_R4 |
System.Double | VT_R8 |
System.Decimal | VT_DECIMAL |
System.DateTime | VT_DATE |
System.String | VT_BSTR |
System.IntPtr | VT_INT |
System.UIntPtr | VT_UINT |
System.Array | VT_ARRAY |
Mithilfe der im vorherigen Beispiel definierten MarshalObject
-Schnittstelle, veranschaulicht das folgende Codebeispiel, wie unterschiedliche Varianttypen an einen COM-Server übergeben werden.
Dim mo As New MarshalObject()
mo.SetVariant(Nothing) ' Marshal as variant of type VT_EMPTY.
mo.SetVariant(System.DBNull.Value) ' Marshal as variant of type VT_NULL.
mo.SetVariant(CInt(27)) ' Marshal as variant of type VT_I2.
mo.SetVariant(CLng(27)) ' Marshal as variant of type VT_I4.
mo.SetVariant(CSng(27.0)) ' Marshal as variant of type VT_R4.
mo.SetVariant(CDbl(27.0)) ' Marshal as variant of type VT_R8.
MarshalObject mo = new MarshalObject();
mo.SetVariant(null); // Marshal as variant of type VT_EMPTY.
mo.SetVariant(System.DBNull.Value); // Marshal as variant of type VT_NULL.
mo.SetVariant((int)27); // Marshal as variant of type VT_I2.
mo.SetVariant((long)27); // Marshal as variant of type VT_I4.
mo.SetVariant((single)27.0); // Marshal as variant of type VT_R4.
mo.SetVariant((double)27.0); // Marshal as variant of type VT_R8.
COM-Typen, die keine entsprechenden verwalteten Typen aufweisen, können mithilfe von Wrapperklassen wie ErrorWrapper, DispatchWrapper, UnknownWrapper und CurrencyWrapper gemarshallt werden. Das folgende Codebeispiel veranschaulicht, wie diese unterschiedlichen Wrapper verwendet werden, um verschiedene Varianttypen an einen COM-Server zu übergeben.
Imports System.Runtime.InteropServices
' Pass inew as a variant of type VT_UNKNOWN interface.
mo.SetVariant(New UnknownWrapper(inew))
' Pass inew as a variant of type VT_DISPATCH interface.
mo.SetVariant(New DispatchWrapper(inew))
' Pass a value as a variant of type VT_ERROR interface.
mo.SetVariant(New ErrorWrapper(&H80054002))
' Pass a value as a variant of type VT_CURRENCY interface.
mo.SetVariant(New CurrencyWrapper(New Decimal(5.25)))
using System.Runtime.InteropServices;
// Pass inew as a variant of type VT_UNKNOWN interface.
mo.SetVariant(new UnknownWrapper(inew));
// Pass inew as a variant of type VT_DISPATCH interface.
mo.SetVariant(new DispatchWrapper(inew));
// Pass a value as a variant of type VT_ERROR interface.
mo.SetVariant(new ErrorWrapper(0x80054002));
// Pass a value as a variant of type VT_CURRENCY interface.
mo.SetVariant(new CurrencyWrapper(new Decimal(5.25)));
Die Wrapperklassen werden im System.Runtime.InteropServices-Namespace definiert.
Marshalling der IConvertible-Schnittstelle an eine Variante
Typen, die im vorherigen Abschnitt nicht aufgeführt werden, können durch Implementierung der IConvertible-Schnittstelle steuern, wie sie gemarshallt werden. Wenn das Objekt die IConvertible-Schnittstelle implementiert, wird der COM-Varianttyp zur Laufzeit durch den Wert der durch die IConvertible.GetTypeCode-Methode zurückgegeben TypeCode-Enumeration bestimmt.
Die folgende Tabelle zeigt die möglichen Werte für die TypeCode-Enumeration und die entsprechenden COM-Varianttypen für jeden Wert.
TypeCode | COM-Varianttyp |
---|---|
TypeCode.Empty | VT_EMPTY |
TypeCode.Object | VT_UNKNOWN |
TypeCode.DBNULL | VT_NULL |
TypeCode.Boolean | VT_BOOL |
TypeCode.Char | VT_UI2 |
TypeCode.Sbyte | VT_I1 |
TypeCode.Byte | VT_UI1 |
TypeCode.Int16 | VT_I2 |
TypeCode.UInt16 | VT_UI2 |
TypeCode.Int32 | VT_I4 |
TypeCode.UInt32 | VT_UI4 |
TypeCode.Int64 | VT_I8 |
TypeCode.UInt64 | VT_UI8 |
TypeCode.Single | VT_R4 |
TypeCode.Double | VT_R8 |
TypeCode.Decimal | VT_DECIMAL |
TypeCode.DateTime | VT_DATE |
TypeCode.String | VT_BSTR |
Wird nicht unterstützt. | VT_INT |
Wird nicht unterstützt. | VT_UINT |
Wird nicht unterstützt. | VT_ARRAY |
Wird nicht unterstützt. | VT_RECORD |
Wird nicht unterstützt. | VT_CY |
Wird nicht unterstützt. | VT_VARIANT |
Der Wert der COM-Variante wird durch Aufruf der IConvertible.To Type-Schnittstelle bestimmt, wobei To Type die Konvertierungsroutine ist, die dem Typ entspricht, der von IConvertible.GetTypeCode zurückgegeben wurde. Beispielsweise wird ein Objekt, das TypeCode.Double aus IConvertible.GetTypeCode zurückgibt, als COM-Variante des Typs VT_R8 gemarshallt. Sie erhalten den Wert der Variante (gespeichert im DblVal-Feld der COM-Variante), indem Sie eine Umwandlung in die IConvertible-Schnittstelle durchführen und die ToDouble-Methode aufrufen.
Marshalling einer Variante an ein Objekt
Beim Marshalling einer Variante an ein Objekt bestimmt der Typ und in einigen Fällen der Wert der gemarshallten Variante den Typ des erstellten Objekts. In der folgenden Tabelle sind die einzelnen Varianttypen und die entsprechenden Objekttypen aufgelistet, die der Marshaller erstellt, wenn eine Variante aus COM an .NET Framework übergeben wird.
COM-Varianttyp | Objekttyp |
---|---|
VT_EMPTY | NULL-Objektverweis (Nothing (nichts) in Visual Basic). |
VT_NULL | System.DBNull |
VT_DISPATCH | System.__ComObject oder NULL wenn (pdispVal == NULL) |
VT_UNKNOWN | System.__ComObject oder NULL wenn (punkVal == NULL) |
VT_ERROR | System.UInt32 |
VT_BOOL | System.Boolean |
VT_I1 | System.SByte |
VT_UI1 | System.Byte |
VT_I2 | System.Int16 |
VT_UI2 | System.UInt16 |
VT_I4 | System.Int32 |
VT_UI4 | System.UInt32 |
VT_I8 | System.Int64 |
VT_UI8 | System.UInt64 |
VT_R4 | System.Single |
VT_R8 | System.Double |
VT_DECIMAL | System.Decimal |
VT_DATE | System.DateTime |
VT_BSTR | System.String |
VT_INT | System.Int32 |
VT_UINT | System.UInt32 |
VT_ARRAY | VT_* | System.Array |
VT_CY | System.Decimal |
VT_RECORD | Entsprechender geschachtelter Werttyp. |
VT_VARIANT | Wird nicht unterstützt. |
Varianttypen aus COM, die an verwalteten Code übergeben und dann an COM zurückgegeben werden, behalten für die Dauer des Aufrufs möglicherweise nicht denselben Varianttypen bei. Beachten Sie, was geschieht, wenn eine Variante des Typs VT_DISPATCH von COM an .NET Framework übergeben wird. Während des Marshallings wird die Variante in ein System.Object konvertiert. Wenn das Objekt dann an COM übergeben wird, wird es wieder auf eine Variante des Typs VT_UNKNOWN gemarshallt. Es gibt keine Garantie dafür, dass die erzeugte Variante, wenn ein Objekt an COM aus verwaltetem Code gemarshallt wird, vom selben Typ ist wie die ursprünglich zur Erstellung des Objekts verwendete Variante.
Marshalling von ByRef-Varianten
Obwohl Varianten selbst durch einen Wert oder durch einen Verweis übergeben werden können, kann das VT_BYREF-Flag auch mit einem beliebigen Varianttyp verwendet werden, um anzugeben, dass die Inhalte der Variante durch Verweis und nicht durch einen Wert übergeben werden. Der Unterschied zwischen dem Marshalling von Varianten durch einen Verweis und dem Marshalling von Varianten mit eingerichtetem VT_BYREF-Flag kann verwirrend sein. Die folgende Abbildung verdeutlicht die Unterschiede:
Nach Wert und Verweis übergebene Varianten
Standardmäßiges Verhalten beim Marshalling von Objekten und Varianten durch einen Wert
Wenn Objekte aus verwaltetem Code an COM übergeben werden, wird der Inhalt des Objekts unter Verwendung der unter Marshalling eines Objekts an eine Variante definierten Regeln in eine neue, vom Marshaller erstellte Variante kopiert. Die auf der nicht verwalteten Seite der Variante vorgenommenen Änderungen, werden bei Rückgabe aus dem Aufruf nicht auf das ursprüngliche Objekt zurückübertragen.
Wenn Varianten von COM an verwalteten Code übergeben werden, wird der Inhalt der Variante unter Verwendung der unter Marshalling einer Variante an ein Objekt definierten Regeln in ein neu erstelltes Objekt kopiert. Die auf der verwalteten Seite des Objekts vorgenommenen Änderungen, werden bei Rückgabe aus dem Aufruf nicht auf die ursprüngliche Variante zurückübertragen.
Standardverhalten für das Marshalling von Objekten und Varianten durch einen Verweis
Um Änderungen an den Aufrufer weiterzugeben, müssen die Parameter durch einen Verweis übergeben werden. Beispielsweise können Sie das Ref-Schlüsselwort in C# (oder ByRef in Visual Basic verwaltetem Code) zum Übergeben von Parametern durch einen Verweis verwenden. In COM werden Verweisparameter mithilfe eines Zeigers, z. B. als variant * übergeben.
Wenn ein Objekt durch einen Verweis an COM übergeben wird, erstellt der Marshaller eine neue Variante und kopiert den Inhalt des Objektverweises in die Variante, bevor der Aufruf ausgeführt wird. Die Variante wird an die nicht verwaltete Funktion übergeben, in der der Benutzer den Inhalt der Variante nach Belieben ändern kann. Die auf der nicht verwalteten Seite der Variante vorgenommenen Änderungen, werden bei Rückgabe aus dem Aufruf nicht auf das ursprüngliche Objekt zurückübertragen. Wenn der Typ der Variante vom Typ der an den Aufruf übergebenen Variante abweicht, werden die Änderungen auf ein Objekt eines anderen Typs zurückübertragen. Das bedeutet, dass sich der Typ des an den Aufruf übergeben Objekts vom Typ des Objekts unterscheiden kann, der aus dem Aufruf zurückgegeben wurde.
Wenn eine Variante durch einen Verweis an verwalteten Code übergeben wird, erstellt der Marshaller ein neues Objekt, und kopiert den Inhalt der Variante in das Objekt, bevor der Aufruf ausgeführt wird. Ein Verweis auf das Objekt wird an die verwaltete Funktion übergeben, in der der Benutzer das Objekt nach Belieben ändern kann. Die am referenzierten Objekt vorgenommenen Änderungen werden bei Rückgabe aus dem Aufruf auf die ursprüngliche Variante zurückübertragen. Wenn sich der Typ des Objekts vom Typ des an den Aufruf übergebenen Objekts unterscheidet, wird der Typ der ursprünglichen Variante geändert, und der Wert wird wieder an die Variante zurückübertragen. Das bedeutet, dass sich der Typ der an den Aufruf übergebenen Variante vom Typ der durch den Aufruf zurückgegebenen Variante unterscheiden kann.
Standardverhalten für das Marshalling von Varianten mit eingerichtetem VT_BYREF-Flag
Für eine durch einen Wert an verwalteten Code übergebene Variante, kann das VT_BYREF-Flag so festgelegt sein, dass angegeben wird, dass die Variante einen Verweis anstelle eines Werts enthält. In diesem Fall wird die Variante noch auf ein Objekt gemarshallt, da die Variante als Wert übergeben wird. Der Marshaller dereferenziert den Inhalt der Variante automatisch und kopiert sie vor der Ausführung des Aufrufs in ein neu erstelltes Objekt. Das Objekt wird dann in die verwaltete Funktion übergeben. Bei der Rückgabe aus dem Aufruf wird das Objekt jedoch nicht wieder in der ursprünglichen Variante zurückverteilt. Am verwalteten Objekt vorgenommene Änderungen gehen verloren.
Achtung
Es gibt keine Möglichkeit, den Wert einer Variante, die als Wert übergeben wurde zu ändern, selbst wenn für die Variante das VT_BYREF-Flag festgelegt wurde.
Für eine durch einen Verweis an verwalteten Code übergebene Variante, kann das VT_BYREF-Flag so festgelegt sein, dass angegeben wird, dass die Variante einen weiteren Verweis enthält. In diesem Fall wird die Variante noch auf ein ref-Objekt gemarshallt, da die Variante durch Verweis übergeben wird. Der Marshaller dereferenziert den Inhalt der Variante automatisch und kopiert sie vor der Ausführung des Aufrufs in ein neu erstelltes Objekt. Bei der Rückgabe des Aufrufs wird der Wert des Objekts zurück an den Verweis in der ursprünglichen Variante nur weitergegeben, wenn das Objekt demselben Typ angehört wie das übergebene Objekt. Durch die Weitergabe wird also der Typ einer Variante mit dem eingerichteten VT_BYREF-Flag ändert. Wenn der Objekttyp während des Aufrufs geändert wird, wird bei Rückgabe aus dem Aufruf eine InvalidCastException ausgelöst.
In der folgenden Tabelle werden die Regeln zur Weitergabe für Varianten und Objekte zusammengefasst.
Von | Beschreibung | Zurückübertragene Änderungen |
---|---|---|
Variant v | Object o | Nie |
Object o | Variant v | Nie |
Variant * pv | Ref Object o | Always |
Ref object o | Variant * pv | Always |
Variant v (VT_BYREF | VT_*) | Object o | Nie |
Variant v (VT_BYREF | VT_) | Ref Object o | Nur, wenn sich der Typ nicht geändert hat. |