[CoreMidi] Make P/Invokes have blittable signatures. (#19724)

This turned into a rather involved exercise, because the
MidiThruConnectionParamsStruct struct had several array fields that needed
marshalling to work.

The changes include:

* Unwrap the array fields with MarshalAs attributes to be individual fields
  instead.
* Write a number of tests to ensure the changes work.
* Fix an overflow issue found by the tests for the Controls array if trying to
  set to an array with more than 65535 elements.
* Fix an overflow issue found by the tests for the Maps array if trying to set
  to an array with more than 65535 elements.
* Fix an issue found by the tests where we wouldn't deserialize the Maps array
  correctly from a byte array / NSData if the Maps array had more than 1
  element.
* Fix a consistency issue found in the tests where deserializing a serialized
  structure doesn't yield the same result.
    * In particular this happens with the ChannelMap property: behavior has
      changed a little bit where setting the ChannelMap to an array with fewer
      than 16 elements (including a null array) will not return the same array
      anymore, but instead an array with 16 elements, where the extra elements
      are all 0. I've also made the ChannelMap property non-nullable, since
      the nullability state isn't serializable into the underlying struct.

Contributes towards #15684.
This commit is contained in:
Rolf Bjarne Kvinge 2024-01-15 17:27:11 +01:00 коммит произвёл GitHub
Родитель 62905c695c
Коммит 27248b1ddb
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
3 изменённых файлов: 1223 добавлений и 47 удалений

Просмотреть файл

@ -10,6 +10,7 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
@ -131,14 +132,148 @@ namespace CoreMidi {
struct MidiThruConnectionParamsStruct {
public uint Version;
public uint NumSources;
[MarshalAs (UnmanagedType.ByValArray, SizeConst = 8)]
public MidiThruConnectionEndpoint []? Sources;
public uint NumDestinations;
[MarshalAs (UnmanagedType.ByValArray, SizeConst = 8)]
public MidiThruConnectionEndpoint []? Destinations;
[MarshalAs (UnmanagedType.ByValArray, SizeConst = 16)]
public byte []? ChannelMap;
MidiThruConnectionEndpoint Sources_0;
MidiThruConnectionEndpoint Sources_1;
MidiThruConnectionEndpoint Sources_2;
MidiThruConnectionEndpoint Sources_3;
MidiThruConnectionEndpoint Sources_4;
MidiThruConnectionEndpoint Sources_5;
MidiThruConnectionEndpoint Sources_6;
MidiThruConnectionEndpoint Sources_7;
public MidiThruConnectionEndpoint []? Sources {
get {
if (NumSources == 0)
return null;
var rv = new List<MidiThruConnectionEndpoint> ();
if (NumSources > 0)
rv.Add (Sources_0);
if (NumSources > 1)
rv.Add (Sources_1);
if (NumSources > 2)
rv.Add (Sources_2);
if (NumSources > 3)
rv.Add (Sources_3);
if (NumSources > 4)
rv.Add (Sources_4);
if (NumSources > 5)
rv.Add (Sources_5);
if (NumSources > 6)
rv.Add (Sources_6);
if (NumSources > 7)
rv.Add (Sources_7);
return rv.ToArray ();
}
set {
if (value?.Length > 8)
throw new ArgumentOutOfRangeException (nameof (value), "A maximum of 8 endpoints are allowed");
Sources_0 = value?.Length > 0 ? value [0] : default (MidiThruConnectionEndpoint);
Sources_1 = value?.Length > 1 ? value [1] : default (MidiThruConnectionEndpoint);
Sources_2 = value?.Length > 2 ? value [2] : default (MidiThruConnectionEndpoint);
Sources_3 = value?.Length > 3 ? value [3] : default (MidiThruConnectionEndpoint);
Sources_4 = value?.Length > 4 ? value [4] : default (MidiThruConnectionEndpoint);
Sources_5 = value?.Length > 5 ? value [5] : default (MidiThruConnectionEndpoint);
Sources_6 = value?.Length > 6 ? value [6] : default (MidiThruConnectionEndpoint);
Sources_7 = value?.Length > 7 ? value [7] : default (MidiThruConnectionEndpoint);
NumSources = (uint) (value?.Length ?? 0);
}
}
public uint NumDestinations;
MidiThruConnectionEndpoint Destinations_0;
MidiThruConnectionEndpoint Destinations_1;
MidiThruConnectionEndpoint Destinations_2;
MidiThruConnectionEndpoint Destinations_3;
MidiThruConnectionEndpoint Destinations_4;
MidiThruConnectionEndpoint Destinations_5;
MidiThruConnectionEndpoint Destinations_6;
MidiThruConnectionEndpoint Destinations_7;
public MidiThruConnectionEndpoint []? Destinations {
get {
if (NumDestinations == 0)
return null;
var rv = new List<MidiThruConnectionEndpoint> ();
if (NumDestinations > 0)
rv.Add (Destinations_0);
if (NumDestinations > 1)
rv.Add (Destinations_1);
if (NumDestinations > 2)
rv.Add (Destinations_2);
if (NumDestinations > 3)
rv.Add (Destinations_3);
if (NumDestinations > 4)
rv.Add (Destinations_4);
if (NumDestinations > 5)
rv.Add (Destinations_5);
if (NumDestinations > 6)
rv.Add (Destinations_6);
if (NumDestinations > 7)
rv.Add (Destinations_7);
return rv.ToArray ();
}
set {
if (value?.Length > 8)
throw new ArgumentOutOfRangeException (nameof (value), "A maximum of 8 endpoints are allowed");
Destinations_0 = value?.Length > 0 ? value [0] : default (MidiThruConnectionEndpoint);
Destinations_1 = value?.Length > 1 ? value [1] : default (MidiThruConnectionEndpoint);
Destinations_2 = value?.Length > 2 ? value [2] : default (MidiThruConnectionEndpoint);
Destinations_3 = value?.Length > 3 ? value [3] : default (MidiThruConnectionEndpoint);
Destinations_4 = value?.Length > 4 ? value [4] : default (MidiThruConnectionEndpoint);
Destinations_5 = value?.Length > 5 ? value [5] : default (MidiThruConnectionEndpoint);
Destinations_6 = value?.Length > 6 ? value [6] : default (MidiThruConnectionEndpoint);
Destinations_7 = value?.Length > 7 ? value [7] : default (MidiThruConnectionEndpoint);
NumDestinations = (uint) (value?.Length ?? 0);
}
}
byte ChannelMap_00;
byte ChannelMap_01;
byte ChannelMap_02;
byte ChannelMap_03;
byte ChannelMap_04;
byte ChannelMap_05;
byte ChannelMap_06;
byte ChannelMap_07;
byte ChannelMap_08;
byte ChannelMap_09;
byte ChannelMap_10;
byte ChannelMap_11;
byte ChannelMap_12;
byte ChannelMap_13;
byte ChannelMap_14;
byte ChannelMap_15;
public byte [] ChannelMap {
get {
return new byte [] {
ChannelMap_00, ChannelMap_01, ChannelMap_02, ChannelMap_03, ChannelMap_04, ChannelMap_05, ChannelMap_06, ChannelMap_07,
ChannelMap_08, ChannelMap_09, ChannelMap_10, ChannelMap_11, ChannelMap_12, ChannelMap_13, ChannelMap_14, ChannelMap_15,
};
}
set {
if (value.Length > 16)
throw new ArgumentOutOfRangeException (nameof (value), "A maximum of 16 channels are allowed");
ChannelMap_00 = value.Length > 00 ? value [00] : (byte) 0;
ChannelMap_01 = value.Length > 01 ? value [01] : (byte) 0;
ChannelMap_02 = value.Length > 02 ? value [02] : (byte) 0;
ChannelMap_03 = value.Length > 03 ? value [03] : (byte) 0;
ChannelMap_04 = value.Length > 04 ? value [04] : (byte) 0;
ChannelMap_05 = value.Length > 05 ? value [05] : (byte) 0;
ChannelMap_06 = value.Length > 06 ? value [06] : (byte) 0;
ChannelMap_07 = value.Length > 07 ? value [07] : (byte) 0;
ChannelMap_08 = value.Length > 08 ? value [08] : (byte) 0;
ChannelMap_09 = value.Length > 09 ? value [09] : (byte) 0;
ChannelMap_10 = value.Length > 10 ? value [10] : (byte) 0;
ChannelMap_11 = value.Length > 11 ? value [11] : (byte) 0;
ChannelMap_12 = value.Length > 12 ? value [12] : (byte) 0;
ChannelMap_13 = value.Length > 13 ? value [13] : (byte) 0;
ChannelMap_14 = value.Length > 14 ? value [14] : (byte) 0;
ChannelMap_15 = value.Length > 15 ? value [15] : (byte) 0;
}
}
public byte LowVelocity;
public byte HighVelocity;
public byte LowNote;
@ -154,14 +289,19 @@ namespace CoreMidi {
public byte FilterOutMtc;
public byte FilterOutBeatClock;
public byte FilterOutTuneRequest;
[MarshalAs (UnmanagedType.ByValArray, SizeConst = 3)]
public byte [] Reserved2;
byte Reserved2_0;
byte Reserved2_1;
byte Reserved2_2;
public byte FilterOutAllControls;
public ushort NumControlTransforms;
public ushort NumMaps;
[MarshalAs (UnmanagedType.ByValArray, SizeConst = 4)]
public ushort [] Reserved3;
ushort Reserved3_0;
ushort Reserved3_1;
ushort Reserved3_2;
ushort Reserved3_3;
// FUN: structure is variably-sized. It contains numControlTransform instances of
// MidiControlTransform, followed by numMaps instances of MidiValueMap.
@ -176,39 +316,36 @@ namespace CoreMidi {
public class MidiThruConnectionParams {
MidiThruConnectionParamsStruct connectionParams;
MidiControlTransform []? controls;
MidiValueMap []? maps;
[DllImport (Constants.CoreMidiLibrary)]
extern static void MIDIThruConnectionParamsInitialize (out MidiThruConnectionParamsStruct inConnectionParams);
unsafe extern static void MIDIThruConnectionParamsInitialize (MidiThruConnectionParamsStruct* inConnectionParams);
public MidiThruConnectionParams ()
{
// Always create a valid init point
MIDIThruConnectionParamsInitialize (out connectionParams);
MidiThruConnectionParamsStruct tmpStruct;
unsafe {
MIDIThruConnectionParamsInitialize (&tmpStruct);
}
connectionParams = tmpStruct;
}
public MidiThruConnectionEndpoint []? Sources {
get { return connectionParams.Sources; }
set {
if (value?.Length > 8)
throw new ArgumentOutOfRangeException (nameof (value), "A maximum of 8 endpoints are allowed");
connectionParams.Sources = value;
}
set { connectionParams.Sources = value; }
}
public MidiThruConnectionEndpoint []? Destinations {
get { return connectionParams.Destinations; }
set {
if (value?.Length > 8)
throw new ArgumentOutOfRangeException (nameof (value), "A maximum of 8 endpoints are allowed");
connectionParams.Destinations = value;
}
set { connectionParams.Destinations = value; }
}
public byte []? ChannelMap {
public byte [] ChannelMap {
get { return connectionParams.ChannelMap; }
set {
if (value?.Length > 16)
throw new ArgumentOutOfRangeException (nameof (value), "A maximum of 16 channels are allowed");
connectionParams.ChannelMap = value;
connectionParams.ChannelMap = value ?? new byte [16];
}
}
@ -287,8 +424,23 @@ namespace CoreMidi {
set { connectionParams.FilterOutAllControls = value ? (byte) 1 : (byte) 0; }
}
public MidiControlTransform []? Controls { get; set; }
public MidiValueMap []? Maps { get; set; }
public MidiControlTransform []? Controls {
get => controls;
set {
if (value?.Length > ushort.MaxValue)
throw new ArgumentOutOfRangeException (nameof (value), "A maximum of 65535 controls are allowed");
controls = value;
}
}
public MidiValueMap []? Maps {
get => maps;
set {
if (value?.Length > ushort.MaxValue)
throw new ArgumentOutOfRangeException (nameof (value), "A maximum of 65535 maps are allowed");
maps = value;
}
}
internal void ReadStruct (NSData data)
{
@ -319,6 +471,7 @@ namespace CoreMidi {
Maps [i].Value = new byte [128];
fixed (void* arrAddr = Maps [i].Value)
Buffer.MemoryCopy ((void*) bufferEnd, arrAddr, 128, 128);
bufferEnd += 128;
}
}
}
@ -326,23 +479,6 @@ namespace CoreMidi {
internal NSData WriteStruct ()
{
if (Sources?.Length > 0 && connectionParams.Sources is not null) {
connectionParams.NumSources = (uint) Sources.Length;
for (int i = 0; i < Sources.Length; i++)
connectionParams.Sources [i] = Sources [i];
}
if (Destinations?.Length > 0 && connectionParams.Destinations is not null) {
connectionParams.NumDestinations = (uint) Destinations.Length;
for (int i = 0; i < Destinations.Length; i++)
connectionParams.Destinations [i] = Destinations [i];
}
if (ChannelMap?.Length > 0 && connectionParams.ChannelMap is not null) {
for (int i = 0; i < ChannelMap.Length; i++)
connectionParams.ChannelMap [i] = ChannelMap [i];
}
connectionParams.NumControlTransforms = Controls is not null ? (ushort) Controls.Length : (ushort) 0;
connectionParams.NumMaps = Maps is not null ? (ushort) Maps.Length : (ushort) 0;

Просмотреть файл

@ -724,7 +724,6 @@ namespace Cecil.Tests {
"System.Void CoreGraphics.CGPath::CGPathAddArc(System.IntPtr,CoreGraphics.CGAffineTransform*,System.Runtime.InteropServices.NFloat,System.Runtime.InteropServices.NFloat,System.Runtime.InteropServices.NFloat,System.Runtime.InteropServices.NFloat,System.Runtime.InteropServices.NFloat,System.Boolean)",
"System.Void CoreGraphics.CGPDFDocument::CGPDFDocumentGetVersion(System.IntPtr,System.Int32&,System.Int32&)",
"System.Void CoreGraphics.CGRectExtensions::CGRectDivide(CoreGraphics.CGRect,CoreGraphics.CGRect&,CoreGraphics.CGRect&,System.Runtime.InteropServices.NFloat,CoreGraphics.CGRectEdge)",
"System.Void CoreMidi.MidiThruConnectionParams::MIDIThruConnectionParamsInitialize(CoreMidi.MidiThruConnectionParamsStruct&)",
"System.Void CoreText.CTFontManager::CTFontManagerRegisterFontDescriptors(System.IntPtr,CoreText.CTFontManagerScope,System.Boolean,ObjCRuntime.BlockLiteral*)",
"System.Void CoreText.CTFontManager::CTFontManagerRegisterFontsWithAssetNames(System.IntPtr,System.IntPtr,CoreText.CTFontManagerScope,System.Boolean,ObjCRuntime.BlockLiteral*)",
"System.Void CoreText.CTFontManager::CTFontManagerRegisterFontURLs(System.IntPtr,CoreText.CTFontManagerScope,System.Boolean,ObjCRuntime.BlockLiteral*)",

Разница между файлами не показана из-за своего большого размера Загрузить разницу