NOW LOADING

What is SDK initialization doing?


An explanation of what the SDK initialization calls are doing under the covers

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.

Metadata vs implementation

WinRT APIs have two components

  • Metadata (the .winmd file)
  • Implementation (typically a .dll file)

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.

The problem

WinRT generally expects applications to use the APIs using one of the following models:

  • A packaged (Store / MSIX) application using in-box WinRT APIs that are in the Windows SDK
  • An application manifest which lists every WinRT type the process will use, and the DLL (in the same location as the exe) it is implemented in

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.

Why not just include in the Windows SDK?

During development, we were counseled by others in Microsoft to undock ourselves from the in-box SDK for a number of reasons

  • We can be more agile and deliver updates more often. This is especially important for us to be able to introduce SDK support for new features and transports. MIDI 2.0 is an evolving standard.
  • We can be selective, especially at first, about which platforms we support. For example, although we could consider them in the future, we don’t want to have to support 32-bit apps, HoloLens, Xbox, IoT Core on day 1. Doing so would have added significant time and complexity to the project.

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.

How SDK initialization works

As a reminder, we needed to solve for the following:

  • Central installation of the implementation, managed by Microsoft
  • Apps do not need to have a manifest for type activation
  • Plugins can use more MIDI features than their host is aware of

And MIDI is relatively small compared to some other APIs/SDKs, so we’re also able to optimize some things for our use-cases.

Install-time

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.

Runtime and version checks

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.

Type catalog and redirection

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:

  1. Scope the activation code to only types in the Microsoft.Windows.Devices.Midi2* namespace, and forward all other activation calls to the original Ro functions
  2. Use our own type catalog which is compiled directly into the SDK itself. You can see this in the MidiAppSdkRuntimeComponentCatalog.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.

What happens when you call EnsureServiceAvailable

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
{ ... }

Didn't find what you were looking for?

Windows MIDI Services is an open source project with all source available on GitHub. We have a great community on Discord as well. Between GitHub and Discord, you should find the information you are looking for.