
The SDK is a central installation with a bootstrapper COM component. The COM component handles all WinRT activation for Windows MIDI Services without requiring the MIDI types to be included in any type of manifest (for desktop apps). Applications do not deploy anything except their own compiled code.
Individual applications do not deploy the SDK with their binaries. The C# projection in the NuGet package contains everything a C# application needs. For C++, the NuGet package includes a .hpp file with the required implementation.
WinRT APIs have two components
The metadata is needed at compile-time for apps to build against. It contains information about all the types available. An API could be broken across multiple metadata or implementation files.
WinRT generally expects applications to use the APIs using one of the following models:
The majority of applications which use MIDI on Windows are unpackaged desktop applications. Therefore, with normal WinRT approaches, they would need to ship the implementation DLL and also include an application manifest which lists every type they want to activate.
The issue there comes about with subsequent MIDI feature updates and especially with plugins. If plugins want to use a new feature in the MIDI SDK, they are constrained by the types known to the host process and listed in its manifest. Additionally, updates to the implementation, including bug and security fixes, have to be shipped as application updates by the host.
And, of course, app developers told us clearly that they do not want to have to ship the SDK runtime and tools with their apps, and deal with maintaining that themselves.
During development, we were counseled by others in Microsoft to undock ourselves from the in-box SDK for a number of reasons
The approach we have taken is very similar to what the Windows App SDK team has done. The Windows App SDK has a much larger audience than MIDI, given that it is the primary native UI stack on Windows for desktop apps.
As a reminder, we needed to solve for the following:
And MIDI is relatively small compared to some other APIs/SDKs, so we’re also able to optimize some things for our use-cases.
At install-time, the SDK is placed in a known location in Windows. Typically C:\Program Files\Windows MIDI Services\Desktop App SDK Runtime
. We also register the single classic-COM component in the implementation DLL: MidiClientInitializer
.
That one classic COM component provides an entry point into the SDK runtime, without involving WinRT types, and as the classid is known to the provided bootstrapper code, it is easy to spin up.
The source-only MidiDesktopAppSdkInitializer
code is delivered in the NuGet package. This code, which you may modify if needed, is responsible for instantiating the MidiClientInitializer
COM object and making calls into it. If you are a .NET developer, the generated projection DLL also includes this code directly in it, without the need for incorporating source.
Also included in the NuGet package are C++ and C# version files which your app should included to be able to use to check for a compatible runtime. You should always pull in the latest version of this file from the NuGet package you are referencing.
WindowsMidiServicesVersion.h
// this file has been auto-generated by the Windows MIDI Services build process
#ifndef WINDOWS_MIDI_SERVICES_VERSION_INCLUDE
#define WINDOWS_MIDI_SERVICES_VERSION_INCLUDE
#define WINDOWS_MIDI_SERVICES_BUILD_SOURCE L"GitHub Preview"
#define WINDOWS_MIDI_SERVICES_BUILD_VERSION_NAME L"Customer Preview 3"
#define WINDOWS_MIDI_SERVICES_BUILD_VERSION_FULL L"1.0.3-preview-12.250426-251"
#define WINDOWS_MIDI_SERVICES_BUILD_VERSION_MAJOR L"1"
#define WINDOWS_MIDI_SERVICES_BUILD_VERSION_MINOR L"0"
#define WINDOWS_MIDI_SERVICES_BUILD_VERSION_REVISION L"3"
#define WINDOWS_MIDI_SERVICES_BUILD_VERSION_DATE_NUMBER L"250426"
#define WINDOWS_MIDI_SERVICES_BUILD_VERSION_TIME_NUMBER L"251"
#endif
WindowsMidiServicesVersion.cs
// this file has been auto-generated by the Windows MIDI Services build process
// this information is the version from the SDK projection, not the SDK runtime
namespace Microsoft.Windows.Devices.Midi2.Common
{
public static class MidiBuildInformation
{
public const string Source = "GitHub Preview";
public const string Name = "Customer Preview 3";
public const string BuildFullVersion = "1.0.3-preview-12.250426-251";
public const string VersionMajor = "1";
public const string VersionMinor = "0";
public const string VersionRevision = "3";
public const string VersionDateNumber = "250426";
public const string VersionTimeNumber = "251";
}
}
Those files make it easy for an application to know the version of the SDK it has compiled against.
Today, we do not have any major version differences in the implementation and the runtime will remain backwards compatible throughout its update and bug fix cycle. The internal runtime startup code will change a bit once we do in the future, to ensure backwards compatibility. If we have major differences, we may end up with side-by-side implementations with different DLLs, all of which would be handled internally by the COM component possibly with a new initialization function that takes major/minor/rev information before loading the implementation, or simply with a new “rev 2” class id. For those reasons, we recommend sticking with the COM approach here, and not trying to manually load any implementation DLLs into your process.
Now that the runtime DLL is loaded, and the MidiClientInitializer
COM singleton initialized and stored in the COM Static Store, we spin up the WinRT type redirection. This uses a library called Detours, from Microsoft, which makes it possible to hook API calls and redirect them. In this case, we redirect all the Ro* activation functions to our own versions. The code was adapted from our Undocked RegFree_WinRT code which were designed to provide down-level support for desktop apps to use WinRT on older versions of Windows 10, but still requires the use of a manifest.
Rather than rely on metadata and/or manifests for the types, we:
Microsoft.Windows.Devices.Midi2*
namespace, and forward all other activation calls to the original Ro functionsMidiAppSdkRuntimeComponentCatalog.cpp
The code for all this has been modified from the RegFreeWinRT examples we have published elsewhere on GitHub.
So for the process we’re operating under, all WinRT activation calls go through our redirected code, which then handles calling either the original functions, or our MIDI SDK-specific functions.
The primary tests to validate the scope of the activation approach may be found in the MidiAppSdkInitializationTests.cpp file in the repo.
This is not related to the SDK itself, but to the in-box Windows service.
To ship the service and its transport/transform plugins in Windows, we had to set it to demand-start. That is, it only starts when applications need to use its features. This helps Windows start up quickly – something particularly important given that the set of users who use MIDI is relatively small compared to the audience as a whole.
When the first call is made into the service, the service is started up. During startup, we enumerate devices, initialize the transports, create the Windows MIDI Services endpoints, perform discovery and protocol negotiation with MIDI 2 devices, and create compatible WinMM ports. If you have many MIDI devices attached, this can take a noticeable number of seconds to complete.
Customers are free to change the service to automatic start. In fact, we recommend that for anyone who uses MIDI regularly – especially musicians. That will avoid the delay when making that first call into the service.
When you call EnsureServiceAvailable
, we make an RPC call to the service, which causes it to start up and peform all the tasks mentioned above. We wait for those tasks to complete before returning control back to the call.
If you want to see exactly how it works, take a look at the MidiClientInitializer in the SDK, and specifically at this function:
HRESULT MidiClientInitializer::EnsureServiceAvailable() noexcept
{ ... }