사용자 지정 마샬링을 위한 원본 생성
.NET 7에서는 원본 생성 interop을 사용할 때 형식이 마샬링되는 방식을 사용자 지정하기 위한 새로운 메커니즘이 도입되었습니다. P/Invokes의 원본 생성기는 MarshalUsingAttribute 및 NativeMarshallingAttribute를 형식의 사용자 지정 마샬링에 대한 지표로 인식합니다.
NativeMarshallingAttribute는 하나의 형식에 적용하여 그 형식에 대한 기본 사용자 지정 마샬링을 나타낼 수 있습니다. MarshalUsingAttribute를 하나의 매개 변수 또는 반환 값에 적용하여 형식 자체에 존재할 수 있는 NativeMarshallingAttribute 항목보다 우선하여 해당 형식의 특정 사용에 대한 사용자 지정 마샬링을 나타낼 수 있습니다. 이러한 두 특성은 모두 하나 이상의 CustomMarshallerAttribute 특성으로 표시된 하나의 Type 즉, 진입점 마샬러 형식을 예상합니다. 각 CustomMarshallerAttribute는 지정된 MarshalMode에 대해 지정된 관리되는 형식을 마샬링하는 데 사용해야 하는 마샬러 구현을 나타냅니다.
마샬러 구현
마샬러 구현은 상태 비저장 또는 상태 저장일 수 있습니다. 마샬러 형식이 static
클래스인 경우 상태 비저장으로 간주됩니다. 값 형식인 경우 상태 저장으로 간주되며 해당 마샬러의 한 인스턴스를 사용하여 특정 매개 변수 또는 반환 값을 마샬링합니다. 마샬러 구현을 위한 도형은 마샬러가 상태 비저장인지 아니면 상태 저장인지 여부와 관리되는 값에서 관리되지 않는 값으로 마샬링을 지원하는지, 관리되지 않는 값에서 관리되는 값으로 마샬링을 지원하는지 아니면 둘 다 지원하는지 여부에 따라 달라집니다. .NET SDK에는 필요한 도형에 적합한 마샬러를 구현하는 데 도움이 되는 분석기 및 코드 수정 도구가 포함되어 있습니다.
MarshalMode
CustomMarshallerAttribute에 지정된 MarshalMode는 마샬러 구현에 대해 예상되는 마샬링 지원 및 도형을 결정합니다. 모든 모드는 상태 비저장 마샬러 구현을 지원합니다. 요소 마샬링 모드는 상태 저장 마샬러 구현을 지원하지 않습니다.
MarshalMode |
예상 지원 | 상태 저장 가능 |
---|---|---|
ManagedToUnmanagedIn | 관리되는 값에서 관리되지 않는 값으로 | Yes |
ManagedToUnmanagedRef | 관리되는 값에서 관리되지 않는 값으로, 관리되지 않는 값에서 관리되는 값으로 | Yes |
ManagedToUnmanagedOut | 관리되지 않는 값에서 관리되는 값으로 | Yes |
UnmanagedToManagedIn | 관리되지 않는 값에서 관리되는 값으로 | Yes |
UnmanagedToManagedRef | 관리되는 값에서 관리되지 않는 값으로, 관리되지 않는 값에서 관리되는 값으로 | Yes |
UnmanagedToManagedOut | 관리되는 값에서 관리되지 않는 값으로 | Yes |
ElementIn | 관리되는 값에서 관리되지 않는 값으로 | No |
ElementRef | 관리되는 값에서 관리되지 않는 값으로, 관리되지 않는 값에서 관리되는 값으로 | No |
ElementOut | 관리되지 않는 값에서 관리되는 값으로 | No |
MarshalMode.Default는 마샬러 구현이 지원하는 모든 모드에서 이 구현을 사용해야 함을 나타냅니다. 보다 구체적인 MarshalMode
를 위한 마샬러 구현도 지정되었다면 이 구현이 MarshalMode.Default
보다 우선적으로 적용됩니다.
기본적인 사용 방법
static
클래스 또는 struct
인 진입점 마샬러 형식을 가리키는 형식에서 NativeMarshallingAttribute를 지정할 수 있습니다.
[NativeMarshalling(typeof(ExampleMarshaller))]
public struct Example
{
public string Message;
public int Flags;
}
진입점 마샬러 형식인 ExampleMarshaller
는 마샬러 구현 형식을 가리키는 CustomMarshallerAttribute로 표시됩니다. 이 예제에서 ExampleMarshaller
는 진입점과 구현을 모두 나타냅니다. 이는 어떤 값의 사용자 지정 마샬링에 예상되는 마샬러 셰이프를 따릅니다.
[CustomMarshaller(typeof(Example), MarshalMode.Default, typeof(ExampleMarshaller))]
internal static class ExampleMarshaller
{
public static ExampleUnmanaged ConvertToUnmanaged(Example managed)
=> throw new NotImplementedException();
public static Example ConvertToManaged(ExampleUnmanaged unmanaged)
=> throw new NotImplementedException();
public static void Free(ExampleUnmanaged unmanaged)
=> throw new NotImplementedException();
internal struct ExampleUnmanaged
{
public IntPtr Message;
public int Flags;
}
}
예제의 ExampleMarshaller
는 관리되는 값에서 관리되지 않는 값으로 마샬링 및 관리되지 않는 값에서 관리되는 값으로 마샬링에 대한 지원을 구현하는 상태 비저장 마샬러입니다. 마샬링 논리는 마샬러 구현에 의해 완전히 제어됩니다. MarshalAsAttribute를 사용하여 하나의 구조체에 필드를 표시해도 생성된 코드에는 영향을 미치지 않습니다.
그런 다음, P/Invoke 원본 생성에서 Example
형식을 사용할 수 있습니다. 다음 P/Invoke 예제에서 ExampleMarshaller
는 매개 변수를 관리되는 값에서 관리되지 않는 값으로 마샬링하는 데 사용됩니다. 또한 반환 값을 관리되지 않는 값에서 관리되는 값으로 마샬링하는 데에도 사용됩니다.
[LibraryImport("nativelib")]
internal static partial Example ConvertExample(Example example);
Example
형식의 특정 사용 시 다른 마샬러를 사용하려면 사용 사이트에서 MarshalUsingAttribute를 지정합니다. 다음 P/Invoke 예제에서 ExampleMarshaller
는 매개 변수를 관리되는 값에서 관리되지 않는 값으로 마샬링하는 데 사용됩니다. OtherExampleMarshaller
는 반환 값을 관리되지 않는 값에서 관리되는 값으로 마샬링하는 데에도 사용됩니다.
[LibraryImport("nativelib")]
[return: MarshalUsing(typeof(OtherExampleMarshaller))]
internal static partial Example ConvertExample(Example example);
컬렉션
ContiguousCollectionMarshallerAttribute를 마샬러 진입점 형식에 적용하여 연속 컬렉션에 대한 것임을 나타냅니다. 해당 형식에는 그와 연결된 관리되는 형식보다 하나 이상의 형식 매개 변수가 있어야 합니다. 마지막 형식 매개 변수는 자리 표시자이며 컬렉션의 요소 형식에 대해 관리되지 않는 형식으로 원본 생성기에 의해 채워집니다.
예를 들어 List<T>에 대한 사용자 지정 마샬링을 지정할 수 있습니다. 다음 코드에서 ListMarshaller
는 진입점과 구현을 모두 나타냅니다. 이는 어떤 컬렉션의 사용자 지정 마샬링에 예상되는 마샬러 셰이프를 따릅니다.
[ContiguousCollectionMarshaller]
[CustomMarshaller(typeof(List<>), MarshalMode.Default, typeof(ListMarshaller<,>))]
public unsafe static class ListMarshaller<T, TUnmanagedElement> where TUnmanagedElement : unmanaged
{
public static byte* AllocateContainerForUnmanagedElements(List<T> managed, out int numElements)
=> throw new NotImplementedException();
public static ReadOnlySpan<T> GetManagedValuesSource(List<T> managed)
=> throw new NotImplementedException();
public static Span<TUnmanagedElement> GetUnmanagedValuesDestination(byte* unmanaged, int numElements)
=> throw new NotImplementedException();
public static List<T> AllocateContainerForManagedElements(byte* unmanaged, int length)
=> throw new NotImplementedException();
public static Span<T> GetManagedValuesDestination(List<T> managed)
=> throw new NotImplementedException();
public static ReadOnlySpan<TUnmanagedElement> GetUnmanagedValuesSource(byte* nativeValue, int numElements)
=> throw new NotImplementedException();
public static void Free(byte* unmanaged)
=> throw new NotImplementedException();
}
예제의 ListMarshaller
는 List<T>에 대해 관리되는 값에서 관리되지 않는 값으로 마샬링하고 관리되지 않는 값에서 관리되는 값으로 마샬링할 수 있도록 지원을 구현하는 상태 비저장 마샬러입니다. 다음 P/Invoke 예제에서 ListMarshaller
는 매개 변수를 관리되는 값에서 관리되지 않는 값으로 마샬링하고 반환 값을 관리되지 않는 값에서 관리되는 값으로 마샬링하는 데 사용됩니다. CountElementName은 반환 값을 관리되지 않는 값에서 관리되는 값으로 마샬링할 때 numValues
매개 변수를 요소 수로 사용해야 함을 나타냅니다.
[LibraryImport("nativelib")]
[return: MarshalUsing(typeof(ListMarshaller<,>), CountElementName = "numValues")]
internal static partial void ConvertList(
[MarshalUsing(typeof(ListMarshaller<,>))] List<int> list,
out int numValues);
추가 정보
.NET