Domyślne zachowanie marshallingu

Interop marshalling działa na regułach, które określają, w jaki sposób dane skojarzone z parametrami metody zachowują się w miarę ich działania między zarządzaną i niezarządzaną pamięcią. Te wbudowane reguły kontrolują takie działania marshalling jak przekształcenia typów danych, czy obiekt wywoływany może zmienić przekazane do niego dane i zwrócić te zmiany do obiektu wywołującego, a w jakich okolicznościach marshaller zapewnia optymalizacje wydajności.

W tej sekcji przedstawiono domyślne cechy behawioralne międzyoperacyjnej usługi marshallingowej. Przedstawia szczegółowe informacje na temat tablic marshalling, typów logicznych, typów char, delegatów, klas, obiektów, ciągów i struktur.

Uwaga

Marshalling typów ogólnych nie jest obsługiwany. Aby uzyskać więcej informacji, zobacz Interoperating Using Generic Types (Współdziałanie przy użyciu typów ogólnych).

Zarządzanie pamięcią za pomocą programu marshaller międzyoperacyjnej

Marshaller międzyoperacyjny zawsze próbuje zwolnić pamięć przydzieloną przez niezarządzany kod. To zachowanie jest zgodne z regułami zarządzania pamięcią COM, ale różni się od reguł, które zarządzają natywnym językiem C++.

Może wystąpić zamieszanie, jeśli przewidujesz natywne zachowanie języka C++ (bez zwalniania pamięci) podczas korzystania z wywołania platformy, co automatycznie zwalnia pamięć dla wskaźników. Na przykład wywołanie następującej niezarządzanej metody z biblioteki DLL języka C++ nie powoduje automatycznego zwolnienia żadnej pamięci.

Podpis niezarządzany

BSTR MethodOne (BSTR b) {  
     return b;  
}  

Jeśli jednak zdefiniujesz metodę jako platformę wywołaj prototyp, zastąp każdy typ BSTR typem typem String i wywołaj MethodOnemetodę , środowisko uruchomieniowe języka wspólnego próbuje zwolnić b dwa razy. Zachowanie marshalingu można zmienić przy użyciu IntPtr typów, a nie typów ciągów .

Środowisko uruchomieniowe zawsze używa metody CoTaskMemFree w systemie Windows i bezpłatnej metody na innych platformach do zwolnienia pamięci. Jeśli pamięć, z którą pracujesz, nie została przydzielona z metodą CoTaskMemAlloc w systemie Windows lub malloc na innych platformach, musisz użyć intPtr i zwolnić pamięć ręcznie przy użyciu odpowiedniej metody. Podobnie można uniknąć automatycznego zwalniania pamięci w sytuacjach, w których pamięć nigdy nie powinna być zwalniana, na przykład w przypadku korzystania z funkcji GetCommandLine z Kernel32.dll, która zwraca wskaźnik do pamięci jądra. Aby uzyskać szczegółowe informacje na temat ręcznego zwalniania pamięci, zobacz Przykład buforów.

Domyślne marshalling dla klas

Klasy mogą być marshalled tylko przez międzyoperacyjności COM i są zawsze marshalled jako interfejsy. W niektórych przypadkach interfejs używany do marshalingu klasy jest znany jako interfejs klasy. Aby uzyskać informacje o zastępowaniu interfejsu klasy wybranym interfejsem, zobacz Wprowadzenie do interfejsu klasy.

Przekazywanie klas do modelu COM

Gdy klasa zarządzana jest przekazywana do modelu COM, międzyoperator automatycznie opakowuje klasę za pomocą serwera proxy COM i przekazuje interfejs klasy utworzony przez serwer proxy do wywołania metody COM. Następnie serwer proxy deleguje wszystkie wywołania interfejsu klasy z powrotem do zarządzanego obiektu. Serwer proxy udostępnia również inne interfejsy, które nie są jawnie implementowane przez klasę. Serwer proxy automatycznie implementuje interfejsy, takie jak IUnknown i IDispatch w imieniu klasy.

Przekazywanie klas do kodu platformy .NET

Coclasses nie są zwykle używane jako argumenty metody w modelu COM. Zamiast tego interfejs domyślny jest zwykle przekazywany zamiast coclass.

Gdy interfejs jest przekazywany do kodu zarządzanego, międzyoperator jest odpowiedzialny za zawijanie interfejsu przy użyciu odpowiedniej otoki i przekazanie otoki do metody zarządzanej. Określenie, która otoka do użycia może być trudna. Każde wystąpienie obiektu COM ma jedną, unikatową otokę, niezależnie od tego, ile interfejsów implementuje obiekt. Na przykład pojedynczy obiekt COM, który implementuje pięć odrębnych interfejsów, ma tylko jedną otokę. Ta sama otoka uwidacznia wszystkie pięć interfejsów. Jeśli zostaną utworzone dwa wystąpienia obiektu COM, zostaną utworzone dwa wystąpienia otoki.

Aby otoka utrzymywała ten sam typ przez cały okres istnienia, międzyoperator musi zidentyfikować poprawną otokę przy pierwszym przekazaniu interfejsu uwidocznionego przez obiekt marshaller. Marshaller identyfikuje obiekt, patrząc na jeden z interfejsów, które implementuje obiekt.

Na przykład marshaller określa, że otoka klasy powinna być używana do opakowania interfejsu, który został przekazany do kodu zarządzanego. Gdy interfejs jest najpierw przekazywany przez marshaller, marshaller sprawdza, czy interfejs pochodzi ze znanego obiektu. Ta kontrola występuje w dwóch sytuacjach:

  • Interfejs jest implementowany przez inny zarządzany obiekt, który został przekazany do modelu COM gdzie indziej. Marshaller może łatwo identyfikować interfejsy uwidocznione przez obiekty zarządzane i jest w stanie dopasować interfejs z obiektem zarządzanym, który zapewnia implementację. Zarządzany obiekt jest następnie przekazywany do metody i nie jest wymagana żadna otoka.

  • Obiekt, który został już opakowany, implementuje interfejs. Aby ustalić, czy tak jest, marshaller wysyła zapytanie do obiektu dla jego interfejsu IUnknown i porównuje zwrócony interfejs z interfejsami innych obiektów, które są już opakowane. Jeśli interfejs jest taki sam jak w przypadku innej otoki, obiekty mają tę samą tożsamość, a istniejąca otoka jest przekazywana do metody .

Jeśli interfejs nie pochodzi ze znanego obiektu, marshaller wykonuje następujące czynności:

  1. Marshaller odpytuje obiekt dla interfejsu IProvideClassInfo2 . Jeśli tak, marshaller używa identyfikatora CLSID zwróconego z IProvideClassInfo2.GetGUID , aby zidentyfikować klasę współklasy dostarczającą interfejs. Za pomocą identyfikatora CLSID marshaller może zlokalizować otokę z rejestru, jeśli zestaw został wcześniej zarejestrowany.

  2. Marshaller wysyła zapytanie do interfejsu interfejsu IProvideClassInfo . Jeśli jest to podane, marshaller używa ITypeInfo zwróconego z IProvideClassInfo.GetClassinfo , aby określić CLSID klasy uwidaczniającej interfejs. Marshaller może użyć CLSID, aby zlokalizować metadane dla otoki.

  3. Jeśli marshaller nadal nie może zidentyfikować klasy, opakowuje interfejs z ogólną klasą otoki o nazwie System.__ComObject.

Domyślne marshaling dla delegatów

Delegat zarządzany jest marshalled jako interfejs COM lub jako wskaźnik funkcji, na podstawie mechanizmu wywołującego:

  • W przypadku wywołania platformy delegat jest domyślnie rozdzielany jako wskaźnik funkcji niezarządzanej.

  • W przypadku międzyoperatorów COM delegat jest domyślnie rozdzielany jako interfejs COM typu _Delegate . Interfejs _Delegate jest zdefiniowany w bibliotece typów Mscorlib.tlb i zawiera Delegate.DynamicInvoke metodę, która umożliwia wywołanie metody, do której odwołuje się delegat.

W poniższej tabeli przedstawiono opcje marshalingu dla zarządzanego typu danych delegata. Atrybut MarshalAsAttribute zawiera kilka UnmanagedType wartości wyliczenia do marshalingu delegatów.

Typ wyliczenia Opis formatu niezarządzanego
UnmanagedType.FunctionPtr Wskaźnik funkcji niezarządzanej.
UnmanagedType.Interface Interfejs typu _Delegate zdefiniowany w pliku Mscorlib.tlb.

Rozważmy następujący przykładowy kod, w którym metody DelegateTestInterface są eksportowane do biblioteki typów MODELU COM. Zwróć uwagę, że tylko delegaty oznaczone słowem kluczowym ref (lub ByRef) są przekazywane jako parametry in/out.

using System;  
using System.Runtime.InteropServices;  
  
public interface DelegateTest {  
void m1(Delegate d);  
void m2([MarshalAs(UnmanagedType.Interface)] Delegate d);
void m3([MarshalAs(UnmanagedType.Interface)] ref Delegate d);
void m4([MarshalAs(UnmanagedType.FunctionPtr)] Delegate d);
void m5([MarshalAs(UnmanagedType.FunctionPtr)] ref Delegate d);
}  

Reprezentacja biblioteki typów

importlib("mscorlib.tlb");  
interface DelegateTest : IDispatch {  
[id(…)] HRESULT m1([in] _Delegate* d);  
[id(…)] HRESULT m2([in] _Delegate* d);  
[id(…)] HRESULT m3([in, out] _Delegate** d);  
[id()] HRESULT m4([in] int d);  
[id()] HRESULT m5([in, out] int *d);  
   };  

Wskaźnik funkcji może zostać wyłuszony, podobnie jak każdy inny niezarządzany wskaźnik funkcji może zostać wyłuszony.

W tym przykładzie, gdy dwa delegaty są marshalled jako UnmanagedType.FunctionPtr, wynik jest int i wskaźnikiem intdo . Ponieważ typy delegatów są marshalled, int tutaj reprezentuje wskaźnik do void (void*), który jest adresem delegata w pamięci. Innymi słowy, ten wynik jest specyficzny dla 32-bitowych systemów Windows, ponieważ int tutaj reprezentuje rozmiar wskaźnika funkcji.

Uwaga

Odwołanie do wskaźnika funkcji do zarządzanego delegata przechowywanego przez niezarządzany kod nie uniemożliwia środowisku uruchomieniowemu języka wspólnego wykonywania odzyskiwania pamięci na zarządzanym obiekcie.

Na przykład poniższy kod jest niepoprawny, ponieważ odwołanie do cb obiektu, przekazane do SetChangeHandler metody, nie utrzymuje cb aktywności poza życiem Test metody. Po zebraniu cb pamięci obiektu wskaźnik funkcji przekazany do SetChangeHandler elementu nie jest już prawidłowy.

public class ExternalAPI {  
   [DllImport("External.dll")]  
   public static extern void SetChangeHandler(  
      [MarshalAs(UnmanagedType.FunctionPtr)]ChangeDelegate d);  
}  
public delegate bool ChangeDelegate([MarshalAs(UnmanagedType.LPWStr) string S);  
public class CallBackClass {  
   public bool OnChange(string S){ return true;}  
}  
internal class DelegateTest {  
   public static void Test() {  
      CallBackClass cb = new CallBackClass();  
      // Caution: The following reference on the cb object does not keep the
      // object from being garbage collected after the Main method
      // executes.  
      ExternalAPI.SetChangeHandler(new ChangeDelegate(cb.OnChange));
   }  
}  

Aby zrekompensować nieoczekiwane odzyskiwanie pamięci, obiekt wywołujący musi upewnić się, że cb obiekt jest nadal aktywny, o ile wskaźnik funkcji niezarządzanej jest używany. Opcjonalnie możesz mieć niezarządzany kod powiadamiający zarządzany kod, gdy wskaźnik funkcji nie jest już potrzebny, jak pokazano w poniższym przykładzie.

internal class DelegateTest {  
   CallBackClass cb;  
   // Called before ever using the callback function.  
   public static void SetChangeHandler() {  
      cb = new CallBackClass();  
      ExternalAPI.SetChangeHandler(new ChangeDelegate(cb.OnChange));  
   }  
   // Called after using the callback function for the last time.  
   public static void RemoveChangeHandler() {  
      // The cb object can be collected now. The unmanaged code is
      // finished with the callback function.  
      cb = null;  
   }  
}  

Domyślne marshalling dla typów wartości

Większość typów wartości, takich jak liczby całkowite i liczby zmiennoprzecinkowe, jest blittable i nie wymaga marshalingu. Inne typy nienależące do blittable mają różne reprezentacje w zarządzanej i niezarządzanej pamięci i wymagają marshallingu. Nadal inne typy wymagają jawnego formatowania w granicach międzyoperacyjnych.

Ta sekcja zawiera informacje na temat następujących sformatowanych typów wartości:

Oprócz opisywania sformatowanych typów w tym temacie przedstawiono typy wartości systemowych, które mają nietypowe zachowanie marshallingu.

Sformatowany typ jest typem złożonym zawierającym informacje, które jawnie steruje układem jego elementów członkowskich w pamięci. Informacje o układzie elementu członkowskiego są udostępniane przy użyciu atrybutu StructLayoutAttribute . Układ może być jedną z następujących LayoutKind wartości wyliczenia:

  • LayoutKind.Auto

    Wskazuje, że środowisko uruchomieniowe języka wspólnego może zmienić kolejność elementów członkowskich typu pod kątem wydajności. Jednak po przekazaniu typu wartości do kodu niezarządzanego układ elementów członkowskich jest przewidywalny. Próba automatycznego marshalingu takiej struktury powoduje wyjątek.

  • Layoutkind.sequential

    Wskazuje, że składowe typu mają być określone w niezarządzanej pamięci w tej samej kolejności, w jakiej są wyświetlane w definicji typu zarządzanego.

  • Layoutkind.explicit

    Wskazuje, że składowe są określone zgodnie z podanym FieldOffsetAttribute polem.

Typy wartości używane w wywołaniu platformy

W poniższym przykładzie typy Point i Rect zawierają informacje o układzie składowym przy użyciu atrybutu StructLayoutAttribute.

Imports System.Runtime.InteropServices  
<StructLayout(LayoutKind.Sequential)> Public Structure Point  
   Public x As Integer  
   Public y As Integer  
End Structure  
<StructLayout(LayoutKind.Explicit)> Public Structure Rect  
   <FieldOffset(0)> Public left As Integer  
   <FieldOffset(4)> Public top As Integer  
   <FieldOffset(8)> Public right As Integer  
   <FieldOffset(12)> Public bottom As Integer  
End Structure  
using System.Runtime.InteropServices;  
[StructLayout(LayoutKind.Sequential)]  
public struct Point {  
   public int x;  
   public int y;  
}
  
[StructLayout(LayoutKind.Explicit)]  
public struct Rect {  
   [FieldOffset(0)] public int left;  
   [FieldOffset(4)] public int top;  
   [FieldOffset(8)] public int right;  
   [FieldOffset(12)] public int bottom;  
}  

Po przesłaniu do niezarządzanego kodu te sformatowane typy są rozdzielane jako struktury stylu C. Zapewnia to łatwy sposób wywoływania niezarządzanego interfejsu API, który ma argumenty struktury. Na przykład POINT struktury i RECT można przekazać do funkcji PtInRect interfejsu API systemu Microsoft Windows w następujący sposób:

BOOL PtInRect(const RECT *lprc, POINT pt);  

Struktury można przekazywać przy użyciu następującej definicji wywołania platformy:

Friend Class NativeMethods
    Friend Declare Auto Function PtInRect Lib "User32.dll" (
        ByRef r As Rect, p As Point) As Boolean
End Class
internal static class NativeMethods
{
   [DllImport("User32.dll")]
   internal static extern bool PtInRect(ref Rect r, Point p);
}

Rect Typ wartości musi zostać przekazany przez odwołanie, ponieważ niezarządzany interfejs API oczekuje przekazania wskaźnika do RECT funkcji. Typ Point wartości jest przekazywany przez wartość, ponieważ niezarządzany interfejs API oczekuje POINT przekazania elementu na stosie. Ta subtelna różnica jest bardzo ważna. Odwołania są przekazywane do niezarządzanego kodu jako wskaźników. Wartości są przekazywane do niezarządzanego kodu na stosie.

Uwaga

Gdy sformatowany typ jest rozdzielany jako struktura, dostępne są tylko pola w danym typie. Jeśli typ zawiera metody, właściwości lub zdarzenia, są one niedostępne z niezarządzanego kodu.

Klasy mogą być również rozdzielane do niezarządzanego kodu jako struktur stylu C, pod warunkiem, że mają stały układ składowy. Informacje o układzie składowym dla klasy są również dostarczane z atrybutem StructLayoutAttribute . Główną różnicą między typami wartości ze stałym układem i klasami ze stałym układem jest sposób, w jaki są one rozdzielane do niezarządzanego kodu. Typy wartości są przekazywane przez wartość (na stosie), a w związku z tym wszelkie zmiany wprowadzone w elementach członkowskich typu przez obiekt wywołujący nie są widoczne dla obiektu wywołującego. Typy referencyjne są przekazywane przez odwołanie (odwołanie do typu jest przekazywane na stosie); w związku z tym wszystkie zmiany wprowadzone w elementach członkowskich typu blittable typu przez obiekt wywołujący są widoczne przez obiekt wywołujący.

Uwaga

Jeśli typ odwołania zawiera elementy członkowskie typów niezwiązanych z blittable, konwersja jest wymagana dwa razy: po raz pierwszy po przekazaniu argumentu do niezarządzanej strony i po raz drugi po powrocie z wywołania. Ze względu na to dodane obciążenie parametry we/wy muszą być jawnie stosowane do argumentu, jeśli obiekt wywołujący chce zobaczyć zmiany wprowadzone przez obiekt wywoływany.

W poniższym przykładzie SystemTime klasa ma układ składowych sekwencyjnych i może zostać przekazana do funkcji GetSystemTime interfejsu API systemu Windows.

<StructLayout(LayoutKind.Sequential)> Public Class SystemTime  
   Public wYear As System.UInt16  
   Public wMonth As System.UInt16  
   Public wDayOfWeek As System.UInt16  
   Public wDay As System.UInt16  
   Public wHour As System.UInt16  
   Public wMinute As System.UInt16  
   Public wSecond As System.UInt16  
   Public wMilliseconds As System.UInt16  
End Class  
[StructLayout(LayoutKind.Sequential)]  
   public class SystemTime {  
   public ushort wYear;
   public ushort wMonth;  
   public ushort wDayOfWeek;
   public ushort wDay;
   public ushort wHour;
   public ushort wMinute;
   public ushort wSecond;
   public ushort wMilliseconds;
}  

Funkcja GetSystemTime jest zdefiniowana w następujący sposób:

void GetSystemTime(SYSTEMTIME* SystemTime);  

Równoważna definicja wywołania platformy dla polecenia GetSystemTime jest następująca:

Friend Class NativeMethods
    Friend Declare Auto Sub GetSystemTime Lib "Kernel32.dll" (
        ByVal sysTime As SystemTime)
End Class
internal static class NativeMethods
{
   [DllImport("Kernel32.dll", CharSet = CharSet.Auto)]
   internal static extern void GetSystemTime(SystemTime st);
}

Zwróć uwagę, że SystemTime argument nie jest wpisany jako argument odwołania, ponieważ SystemTime jest klasą, a nie typem wartości. W przeciwieństwie do typów wartości klasy są zawsze przekazywane przez odwołanie.

Poniższy przykład kodu przedstawia inną Point klasę, która ma metodę o nazwie SetXY. Ponieważ typ ma układ sekwencyjny, można go przekazać do niezarządzanego kodu i uruchomić jako strukturę. Jednak element SetXY członkowski nie jest wywoływany z niezarządzanego kodu, mimo że obiekt jest przekazywany przez odwołanie.

<StructLayout(LayoutKind.Sequential)> Public Class Point  
   Private x, y As Integer  
   Public Sub SetXY(x As Integer, y As Integer)  
      Me.x = x  
      Me.y = y  
   End Sub  
End Class  
[StructLayout(LayoutKind.Sequential)]  
public class Point {  
   int x, y;  
   public void SetXY(int x, int y){
      this.x = x;  
      this.y = y;  
   }  
}  

Typy wartości używane w międzyoperacyjności modelu COM

Sformatowane typy można również przekazać do wywołań metod międzyoperacyjnej modelu COM. W rzeczywistości podczas eksportowania do biblioteki typów typy są automatycznie konwertowane na struktury. Jak pokazano w poniższym przykładzie, Point typ wartości staje się definicją typu (typedef) o nazwie Point. Wszystkie odwołania do Point typu wartości w innej części biblioteki typów są zastępowane definicją Point typedef.

Reprezentacja biblioteki typów

typedef struct tagPoint {  
   int x;  
   int y;  
} Point;  
interface _Graphics {  
   …  
   HRESULT SetPoint ([in] Point p)  
   HRESULT SetPointRef ([in,out] Point *p)  
   HRESULT GetPoint ([out,retval] Point *p)  
}  

Te same reguły używane do marshalingu wartości i odwołań do wywołań wywołań platformy są używane podczas marshalingu za pośrednictwem interfejsów COM. Na przykład gdy wystąpienie Point typu wartości jest przekazywane z programu .NET Framework do modelu COM, Point wartość jest przekazywana przez wartość. Point Jeśli typ wartości jest przekazywany przez odwołanie, wskaźnik do elementu Point jest przekazywany na stosie. Marshaller międzyoperacji nie obsługuje wyższego poziomu pośredniego (punkt **) w obu kierunkach.

Uwaga

Struktury o wartości wyliczenia ustawionej LayoutKind na Jawne nie mogą być używane w międzyoperacyjności MODELU COM, ponieważ wyeksportowana biblioteka typów nie może wyrazić jawnego układu.

Typy wartości systemowych

System Przestrzeń nazw ma kilka typów wartości reprezentujących pole typu pierwotnego środowiska uruchomieniowego. Na przykład struktura typu System.Int32 wartości reprezentuje postać pola ELEMENT_TYPE_I4. Zamiast marshalling tych typów jako struktur, jak inne sformatowane typy są, można je marshaling w taki sam sposób jak typy pierwotne, które są w polu. System.Int32 jest zatem marshalled jako ELEMENT_TYPE_I4 zamiast jako struktura zawierająca jeden element członkowski typu długi. Poniższa tabela zawiera listę typów wartości w przestrzeni nazw systemu , które są reprezentacjami typów pierwotnych.

Typ wartości systemowej Typ elementu
System.Boolean ELEMENT_TYPE_BOOLEAN
System.SByte ELEMENT_TYPE_I1
System.Byte ELEMENT_TYPE_UI1
System.Char ELEMENT_TYPE_CHAR
System.Int16 ELEMENT_TYPE_I2
System.UInt16 ELEMENT_TYPE_U2
System.Int32 ELEMENT_TYPE_I4
System.UInt32 ELEMENT_TYPE_U4
System.Int64 ELEMENT_TYPE_I8
System.UInt64 ELEMENT_TYPE_U8
System.Single ELEMENT_TYPE_R4
System.Double ELEMENT_TYPE_R8
System.String ELEMENT_TYPE_STRING
System.IntPtr ELEMENT_TYPE_I
System.UIntPtr ELEMENT_TYPE_U

Niektóre inne typy wartości w przestrzeni nazw systemu są obsługiwane inaczej. Ponieważ niezarządzany kod ma już dobrze ugruntowane formaty dla tych typów, marshaller ma specjalne zasady dotyczące ich marshalingu. W poniższej tabeli wymieniono specjalne typy wartości w przestrzeni nazw systemowej , a także typ niezarządzany, do którego są one rozdzielane.

Typ wartości systemowej Typ IDL
System.DateTime DATA
System.Decimal DZIESIĘTNYCH
System.Guid Identyfikator GUID
System.Drawing.Color OLE_COLOR

Poniższy kod przedstawia definicję typów niezarządzanych DATE, GUID, DECIMAL i OLE_COLOR w bibliotece typów Stdole2.

Reprezentacja biblioteki typów

typedef double DATE;  
typedef DWORD OLE_COLOR;  
  
typedef struct tagDEC {  
    USHORT    wReserved;  
    BYTE      scale;  
    BYTE      sign;  
    ULONG     Hi32;  
    ULONGLONG Lo64;  
} DECIMAL;  
  
typedef struct tagGUID {  
    DWORD Data1;  
    WORD  Data2;  
    WORD  Data3;  
    BYTE  Data4[ 8 ];  
} GUID;  

Poniższy kod przedstawia odpowiednie definicje w interfejsie zarządzanym IValueTypes .

Public Interface IValueTypes  
   Sub M1(d As System.DateTime)  
   Sub M2(d As System.Guid)  
   Sub M3(d As System.Decimal)  
   Sub M4(d As System.Drawing.Color)  
End Interface  
public interface IValueTypes {  
   void M1(System.DateTime d);  
   void M2(System.Guid d);  
   void M3(System.Decimal d);  
   void M4(System.Drawing.Color d);  
}  

Reprezentacja biblioteki typów

[…]  
interface IValueTypes : IDispatch {  
   HRESULT M1([in] DATE d);  
   HRESULT M2([in] GUID d);  
   HRESULT M3([in] DECIMAL d);  
   HRESULT M4([in] OLE_COLOR d);  
};  

Zobacz też