Interop 마샬링은 관리되는 메모리와 관리되지 않는 메모리 간에 전달될 때 메서드 매개 변수와 연결된 데이터가 동작하는 방식을 지시하는 규칙에서 작동합니다. 이러한 기본 제공 규칙은 데이터 형식 변환과 같은 마샬링 작업, 호출 수신자가 전달된 데이터를 변경하고 해당 변경 내용을 호출자에게 반환할 수 있는지 여부 및 마샬러가 성능 최적화를 제공하는 상황을 제어합니다.
이 섹션에서는 interop 마샬링 서비스의 기본 동작 특성을 식별합니다. 배열, 부울 형식, 문자 형식, 대리자, 클래스, 개체, 문자열 및 구조체 마샬링에 대한 자세한 정보를 제공합니다.
비고
제네릭 형식의 마샬링이 지원되지 않습니다. 자세한 내용은 제네릭 형식을 사용하여 상호 운용하는 것을 참조하세요.
interop 마샬러를 사용하여 메모리 관리
interop 마샬러는 항상 관리되지 않는 코드에 의해 할당된 메모리를 해제하려고 시도합니다. 이 동작은 COM 메모리 관리 규칙을 준수하지만 네이티브 C++를 제어하는 규칙과 다릅니다.
플랫폼 호출을 사용할 때 네이티브 C++ 동작(메모리 해제 없음)을 예상하면 혼동이 발생할 수 있으며 포인터에 대한 메모리를 자동으로 해제합니다. 예를 들어 C++ DLL에서 다음 관리되지 않는 메서드를 호출해도 메모리가 자동으로 해제되지는 않습니다.
관리되지 않는 서명
BSTR MethodOne (BSTR b) {
return b;
}
그러나 메서드를 플랫폼 호출 프로토타입으로 정의하고, 각 BSTR 타입을 String 타입으로 교체하고 MethodOne
를 호출하면, 공용 언어 런타임이 b
을 두 번 해제하려고 시도합니다.
IntPtr 형식이 아닌 형식을 사용하여 마샬링 동작을 변경할 수 있습니다.
런타임은 항상 Windows의 CoTaskMemFree 메서드와 다른 플랫폼의 무료 메서드를 사용하여 메모리를 해제합니다. 작업 중인 메모리가 Windows의 CoTaskMemAlloc 메서드 또는 다른 플랫폼의 malloc 메서드로 할당되지 않은 경우 IntPtr 을 사용하고 적절한 메서드를 사용하여 수동으로 메모리를 해제해야 합니다. 마찬가지로 커널 메모리에 대한 포인터를 반환하는 Kernel32.dllGetCommandLine 함수를 사용하는 경우와 같이 메모리를 해제하지 않아야 하는 경우 자동 메모리 해제를 방지할 수 있습니다. 메모리를 수동으로 해제하는 방법은 버퍼 샘플을 참조하세요.
클래스에 대한 기본 마샬링
클래스는 COM interop에서만 마샬링할 수 있으며 항상 인터페이스로 마샬링됩니다. 경우에 따라 클래스를 마샬링하는 데 사용되는 인터페이스를 클래스 인터페이스라고 합니다. 선택한 인터페이스를 사용하여 클래스 인터페이스를 재정의하는 방법에 대한 자세한 내용은 클래스 인터페이스 소개를 참조하세요.
COM에 클래스 전달
관리되는 클래스가 COM에 전달되면 interop 마샬러는 자동으로 COM 프록시를 사용하여 클래스를 래핑하고 프록시에서 생성된 클래스 인터페이스를 COM 메서드 호출에 전달합니다. 그런 다음 프록시는 클래스 인터페이스의 모든 호출을 관리되는 개체에 다시 위임합니다. 또한 프록시는 클래스에서 명시적으로 구현되지 않은 다른 인터페이스를 노출합니다. 프록시는 클래스를 대신하여 IUnknown 및 IDispatch 와 같은 인터페이스를 자동으로 구현합니다.
.NET 코드에 클래스 전달
Coclass는 일반적으로 COM에서 메서드 인수로 사용되지 않습니다. 대신 기본 인터페이스는 일반적으로 coclass 대신 전달됩니다.
인터페이스가 관리 코드로 전달되면 interop 마샬러는 인터페이스를 적절한 래퍼로 래핑하고 래퍼를 관리되는 메서드에 전달하는 작업을 담당합니다. 사용할 포장지를 결정하는 것은 어려울 수 있습니다. COM 개체의 모든 인스턴스에는 개체가 구현하는 인터페이스 수에 관계없이 고유한 단일 래퍼가 있습니다. 예를 들어 5개의 고유한 인터페이스를 구현하는 단일 COM 개체에는 하나의 래퍼만 있습니다. 동일한 래퍼는 5개의 인터페이스를 모두 노출합니다. COM 개체의 인스턴스가 두 개 만들어지면 래퍼의 두 인스턴스가 만들어집니다.
래퍼가 수명 내내 동일한 형식을 유지하려면 개체에 의해 노출된 인터페이스가 마샬러를 통해 처음 전달될 때 interop 마샬러가 올바른 래퍼를 식별해야 합니다. 마샬러는 개체가 구현하는 인터페이스 중 하나를 확인하여 개체를 식별합니다.
예를 들어 마샬러는 클래스 래퍼를 사용하여 관리 코드로 전달된 인터페이스를 래핑해야 한다고 결정합니다. 인터페이스가 마샬러를 통해 처음 전달되면 마샬러는 인터페이스가 알려진 개체에서 오는지 여부를 확인합니다. 이 검사는 다음 두 가지 상황에서 발생합니다.
인터페이스는 다른 곳에서 COM에 전달된 다른 관리되는 개체에 의해 구현되고 있습니다. 마샬러는 관리되는 개체에 의해 노출되는 인터페이스를 쉽게 식별할 수 있으며 구현을 제공하는 관리되는 개체와 인터페이스를 일치시킬 수 있습니다. 그러면 관리되는 개체가 메서드에 전달되고 래퍼가 필요하지 않습니다.
이미 래핑된 개체가 인터페이스를 구현하고 있습니다. 이 경우인지 확인하기 위해 마샬러는 IUnknown 인터페이스에 대해 개체를 쿼리하고 반환된 인터페이스를 이미 래핑된 다른 개체의 인터페이스와 비교합니다. 인터페이스가 다른 래퍼의 인터페이스와 같으면 개체의 ID가 같고 기존 래퍼가 메서드에 전달됩니다.
인터페이스가 알려진 개체에서 온 것이 아니면 마샬러는 다음을 수행합니다.
마샬러는 IProvideClassInfo2 인터페이스에 대한 개체를 쿼리합니다. 제공된 경우 마샬러는 IProvideClassInfo2.GetGUID 에서 반환된 CLSID를 사용하여 인터페이스를 제공하는 coclass를 식별합니다. CLSID를 사용하면 어셈블리가 이전에 등록된 경우 마샬러가 레지스트리에서 래퍼를 찾을 수 있습니다.
마샬러는 IProvideClassInfo 인터페이스에 대한 인터페이스를 쿼리합니다. 제공된 경우 마샬러는 IProvideClassInfo.GetClassinfo에서 반환된 ITypeInfo를 사용하여 인터페이스를 노출하는 클래스의 CLSID를 확인합니다. 마샬러는 CLSID를 사용하여 래퍼에 대한 메타데이터를 찾을 수 있습니다.
마샬러가 여전히 클래스를 식별할 수 없는 경우 System.__ComObject이라는 제네릭 래퍼 클래스를 사용하여 인터페이스를 래핑합니다.
대리자 기본 마샬링
관리되는 대리자는 호출 메커니즘에 따라 COM 인터페이스 또는 함수 포인터로 마샬링됩니다.
플랫폼 호출의 경우 대리자는 기본적으로 관리되지 않는 함수 포인터로 마샬링됩니다.
COM interop의 경우 대리자는 기본적으로 _Delegate 형식의 COM 인터페이스로 마샬링됩니다. _Delegate 인터페이스는 Mscorlib.tlb 형식 라이브러리에 정의되며 대리자가 참조하는 메서드를 호출할 수 있는 메서드를 포함합니다Delegate.DynamicInvoke.
다음 표에서는 관리되는 대리자 데이터 형식에 대한 마샬링 옵션을 보여줍니다. MarshalAsAttribute 속성은 대리자를 마샬링하기 위해 여러 UnmanagedType 열거형 값을 제공합니다.
열거형 형식 | 관리되지 않는 형식에 대한 설명 |
---|---|
UnmanagedType.FunctionPtr | 관리되지 않는 함수 포인터입니다. |
UnmanagedType.Interface | Mscorlib.tlb에 정의된 _Delegate 형식의 인터페이스. |
다음 예제 코드에서는 메서드 DelegateTestInterface
를 COM 형식 라이브러리로 내보냅니다.
ref(또는 ByRef) 키워드로 표시된 대리자만 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);
}
타입 라이브러리 표현
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);
};
관리되지 않는 다른 함수 포인터를 역참조할 수 있는 것처럼 함수 포인터를 역참조할 수 있습니다.
이 예제에서 두 대리자를 UnmanagedType.FunctionPtr로 마샬링하면, 결과는 int
및 int
에 대한 포인터가 됩니다. 대리자 형식이 마샬링되고 있으므로 int
는 여기서 메모리에 있는 대리자의 주소를 가리키는 void(void*
) 타입의 포인터를 나타냅니다. 즉, 이 결과는 함수 포인터의 크기를 나타내므로 32비트 Windows 시스템과 int
관련이 있습니다.
비고
관리되지 않는 코드에서 보유하는 관리되는 대리자에 대한 함수 포인터를 참조해도 공용 언어 런타임이 관리되는 개체에서 가비지 수집을 수행할 수 없습니다.
예를 들어, 다음 코드는 cb
객체에 대한 참조가 SetChangeHandler
메서드에 전달되었지만 cb
메서드의 수명 이후에도 Test
를 활성 상태로 유지하지 않기 때문에 잘못되었습니다.
cb
개체가 가비지 수집되면, 함수 포인터가 SetChangeHandler
에 전달되어 더 이상 유효하지 않습니다.
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));
}
}
예기치 않은 가비지 수집을 보정하려면 호출자는 관리되지 않는 함수 포인터를 cb
사용하는 한 개체가 활성 상태로 유지되도록 해야 합니다. 필요에 따라 다음 예제와 같이 함수 포인터가 더 이상 필요하지 않은 경우 관리되지 않는 코드가 관리 코드에 알릴 수 있습니다.
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;
}
}
값 형식에 대한 기본 마샬링
정수 및 부동 소수점 숫자와 같은 대부분의 값 형식은 blittable 이며 마샬링이 필요하지 않습니다. Blittable이 아닌 다른 형식은 관리되는 메모리와 관리되지 않는 메모리에서 서로 다른 표현을 가지며 마샬링이 필요합니다. 다른 형식은 상호 운용 경계를 넘어 명시적 서식을 지정해야 합니다.
이 섹션에서는 다음과 같은 형식의 값 형식에 대한 정보를 제공합니다.
이 항목에서는 형식이 지정된 형식을 설명하는 것 외에도 비정상적인 마샬링 동작이 있는 시스템 값 형식 을 식별합니다.
형식이 지정된 형식은 메모리에 있는 멤버의 레이아웃을 명시적으로 제어하는 정보를 포함하는 복합 형식입니다. 멤버 레이아웃 정보는 StructLayoutAttribute 속성을 사용하여 제공됩니다. 레이아웃은 다음 LayoutKind 열거형 값 중 하나일 수 있습니다.
LayoutKind.Auto
공용 언어 런타임이 효율성을 위해 형식의 멤버를 자유롭게 다시 정렬할 수 있음을 나타냅니다. 그러나 값 형식이 관리되지 않는 코드에 전달되면 멤버의 레이아웃을 예측할 수 있습니다. 이러한 구조를 마샬링하려고 하면 자동으로 예외가 발생합니다.
LayoutKind.Sequential
형식의 멤버가 관리되는 형식 정의에 표시되는 순서와 동일한 순서로 관리되지 않는 메모리에 배치되도록 지정합니다.
LayoutKind.Explicit
각 필드에 제공된 항목에 따라 멤버가 배치됨을 FieldOffsetAttribute 나타냅니다.
플랫폼 호출에 사용되는 값 형식
다음 예제에서 Point
및 Rect
형식은 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;
}
관리되지 않는 코드로 마샬링된 경우 이러한 형식 형식은 C 스타일 구조체로 마샬링됩니다. 이렇게 하면 구조 인수가 있는 관리되지 않는 API를 쉽게 호출할 수 있습니다. 예를 들어 POINT
다음과 같이 구조체와 RECT
구조를 Microsoft Windows API PtInRect 함수에 전달할 수 있습니다.
BOOL PtInRect(const RECT *lprc, POINT pt);
다음 플랫폼 호출 정의를 사용하여 구조를 전달할 수 있습니다.
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);
}
관리되지 않는 API가 Rect
에 대한 포인터를 함수에 전달받기를 기대하기 때문에, RECT
값 형식을 참조로 전달해야 합니다. 값 형식인 Point
는 관리되지 않는 API가 POINT
을 스택에 전달하기를 기대하기 때문에 값으로 전달됩니다. 이 미묘한 차이는 매우 중요합니다. 참조는 비관리 코드에 포인터로 전달됩니다. 값은 스택의 관리되지 않는 코드에 전달됩니다.
비고
형식이 지정된 형식을 구조체로 마샬링하면 형식 내의 필드만 액세스할 수 있습니다. 형식에 메서드, 속성 또는 이벤트가 있는 경우 관리되지 않는 코드에서 액세스할 수 없습니다.
멤버 레이아웃이 고정된 경우 클래스를 C 스타일 구조체로 관리되지 않는 코드로 마샬링할 수도 있습니다. 클래스의 멤버 레이아웃 정보는 또한 StructLayoutAttribute 속성과 함께 제공됩니다. 고정 레이아웃이 있는 값 형식과 고정 레이아웃이 있는 클래스 간의 주요 차이점은 관리되지 않는 코드로 마샬링되는 방식입니다. 값 형식은 스택의 값으로 전달되며, 따라서 호출자가 형식의 멤버를 변경한 내용은 호출자가 볼 수 없습니다. 참조 형식은 참조에 의해 전달됩니다(형식의 참조가 스택을 통해 전달됨). 따라서 호출된 측이 형식의 blittable 형식 멤버에 대해 수행한 모든 변경은 호출자가 볼 수 있습니다.
비고
참조 형식에 Blittable이 아닌 형식의 멤버가 있는 경우 변환이 두 번 필요합니다. 인수가 관리되지 않는 쪽에 전달된 첫 번째 및 호출에서 반환될 때 두 번째 변환이 필요합니다. 이 추가된 오버헤드로 인해 호출자가 호출자가 변경한 내용을 확인하려는 경우 In/Out 매개 변수를 인수에 명시적으로 적용해야 합니다.
다음 예제에서는 클래스에 SystemTime
순차 멤버 레이아웃이 있으며 Windows API GetSystemTime 함수에 전달할 수 있습니다.
<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;
}
GetSystemTime 함수는 다음과 같이 정의됩니다.
void GetSystemTime(SYSTEMTIME* SystemTime);
GetSystemTime에 해당하는 플랫폼 호출 정의는 다음과 같습니다.
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);
}
인수가 SystemTime
참고 인수로 입력되지 않은 이유는 SystemTime
이(가) 값 형식이 아니라 클래스이기 때문입니다. 값 형식과 달리 클래스는 항상 참조로 전달됩니다.
다음 코드 예제에서는 Point
라는 메서드를 가진 다른 SetXY
클래스를 보여줍니다. 형식에는 순차적 레이아웃이 있으므로 관리되지 않는 코드에 전달하고 구조체로 마샬링할 수 있습니다. 그러나 개체가 SetXY
참조로 전달되더라도 멤버는 관리되지 않는 코드에서 호출할 수 없습니다.
<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;
}
}
COM Interop에 사용되는 값 형식
형식이 지정된 형식을 COM interop 메서드 호출에 전달할 수도 있습니다. 실제로 형식 라이브러리로 내보낼 때 값 형식은 구조체로 자동으로 변환됩니다. 다음 예제와 같이 값 형식은 Point
이름이 Point
있는 형식 정의(typedef)가 됩니다. 형식 라이브러리의 Point
다른 위치에 있는 값 형식에 대한 모든 참조는 typedef로 Point
바뀝니다.
타입 라이브러리 표현
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)
}
COM 인터페이스를 통해 마샬링할 때 플랫폼 호출에 대한 값 및 참조를 마샬링하는 데 사용되는 것과 동일한 규칙이 사용됩니다. 예를 들어 값 형식의 인스턴스가 Point
.NET Framework에서 COM Point
으로 전달되면 값으로 전달됩니다. 값 형식이 Point
참조로 전달되면, Point
에 대한 포인터가 스택에 전달됩니다. interop 마샬러는 어느 방향으로든 더 높은 수준의 간접 참조(점 **)를 지원하지 않습니다.
비고
열거형 값이 LayoutKindExplicit 로 설정된 구조체는 내보낸 형식 라이브러리가 명시적 레이아웃을 표현할 수 없으므로 COM interop에서 사용할 수 없습니다.
시스템 값 형식
System 네임스페이스에는 런타임 기본 형식의 상자 형식을 나타내는 여러 값 형식이 있습니다. 예를 들어 값 형식 System.Int32 구조체는 ELEMENT_TYPE_I4 상자 형식을 나타냅니다. 다른 형식 형식과 마찬가지로 이러한 형식을 구조체로 마샬링하는 대신 기본 형식과 동일한 방식으로 마샬링합니다. 따라서 System.Int32는 긴 형식의 단일 멤버를 포함하는 구조체 대신 ELEMENT_TYPE_I4 마샬링됩니다. 다음 표에는 기본 형식의 박싱된 표현인 System 네임스페이스의 값 형식 목록이 포함되어 있습니다.
시스템 값 형식 | 요소 타입 |
---|---|
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 |
시스템 네임스페이스의 일부 다른 값 형식은 다르게 처리됩니다. 관리되지 않는 코드에는 이러한 형식에 대해 잘 설정된 형식이 이미 있으므로 마샬러에는 마샬링에 대한 특수 규칙이 있습니다. 다음 표에서는 System 네임스페이스의 특수 값 유형과 마샬링되는 비관리 형식을 나열합니다.
시스템 값 형식 | IDL 형식 |
---|---|
System.DateTime | 날짜 |
System.Decimal | 십진법 |
System.Guid | GUID |
System.Drawing.Color | OLE_COLOR |
다음 코드는 Stdole2 형식 라이브러리의 관리되지 않는 형식 DATE, GUID, DECIMAL 및 OLE_COLOR 정의를 보여줍니다.
타입 라이브러리 표현
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;
다음 코드는 관리 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);
}
타입 라이브러리 표현
[…]
interface IValueTypes : IDispatch {
HRESULT M1([in] DATE d);
HRESULT M2([in] GUID d);
HRESULT M3([in] DECIMAL d);
HRESULT M4([in] OLE_COLOR d);
};
참고하십시오
.NET