Windows MIDI Services Transport plugins are COM components loaded by the MIDI service (midisrv). They can often replace custom kernel drivers or legacy WinMM .drv style integration for many scenarios, while still allowing a transport to discover, create, and manage endpoints.
This page is a practical starting point for third-party developers who want to build their own transport plugin.
Important
Transport plugins run in-process inside
midisrv. Your code executes inside the Windows service process, so reliability, security, and performance requirements are much stricter than for a normal desktop app component. If you have been writing kernel drivers, then you are well-prepared for writing secure and stable user-mode code.
At a high level, a transport plugin is:
midisrv using COMWindows MIDI Services also has a similar plugin mechanism for message transforms. However, discover and runtime activation of transform plugins are not yet enabled for external developers.
Use the same GUID for your transport Id and COM CLSID.
In the current codebase, transports follow this same pattern. For example, TRANSPORT_LAYER_GUID is set to the COM CLSID in transport definitions and metadata plumbing.
Examples:
This one-GUID approach keeps activation, metadata reporting, and configuration routing consistent.
The Windows MIDI Service routes all messages as UMP. However, your transport can expose either UMP or byte format data as appropriate. If you expose MIDI 1.0 byte format data, we will insert a translator in between the rest of the service and your transport.
Transport-facing message format is represented by MidiDataFormats in the service interfaces:
MidiDataFormats_ByteStream for MIDI 1.0 byte stream dataMidiDataFormats_UMP for Universal MIDI Packet data (recommended)Reference:
Recommendation:
All service transport timestamps are 64-bit ticks based on QueryPerformanceCounter (QPC), relative to PC startup time.
The position/timestamp fields passed through service transport interfaces are not wall-clock time and not MIDI beat time. They are high-resolution performance counter ticks.
References:
Guidance:
0 is allowed and indicates “timestamp not supplied”; the service pipeline can stamp later, but this is less accurate than a transport-origin timestampFor UMP endpoints, IMidiBidirectional is the main data path for both directions. In this, you want to quickly process incoming messages from the service, and quickly send messages going into the service. Typically, this class is adding to or pulling from an internal queue of message data in the transport.
Relevant interfaces and methods:
IMidiBidirectional::SendMidiMessage(...) for transport-to-service message flowIMidiCallback::Callback(...) for service-to-transport callback message flowReference:
Both directions can carry more than one UMP message in a single call.
Guidance for transport developers:
You can use either approach:
When multiple messages are sent from the transport to the service in a single call, the service attempts to preserve that grouping together to clients as much as possible.
Likewise, when multiple messages are delivered to the transport in one callback call, that call represents client intent that those messages be sent together.
This means your implementation should avoid splitting or re-chunking within a single call unless required by a strict transport/protocol constraint.
Your transport exposes functionality through COM interfaces activated from the root transport object.
Primary interfaces include:
IMidiTransport (root activation interface)IMidiEndpointManager (endpoint discovery/publication lifecycle)IMidiTransportConfigurationManager (service/app-driven transport config updates)IMidiServiceTransportPluginMetadataProvider (name, author, version, flags, etc.)IMidiIn, IMidiOut, IMidiBidirectional (data path instances)References:
IMidiTransport::Activate is your transport’s factory for all the additional types. Inside this, COM class factory activation is used (CoCreateInstance on your CLSID), then interface activation is handled through IMidiTransport::Activate.
By default, design for UMP endpoint creation first.
For endpoint publication, your IMidiEndpointManager implementation creates and manages Software Device endpoints and their associated metadata/properties.
References:
If your transport needs to expose legacy MIDI 1.0 ports in addition to UMP endpoints, provide an explicit configuration switch and keep the behavior deterministic.
The Network MIDI 2.0 transport shows this pattern:
createMidi1Ports controls whether MIDI 1.0 ports are also createdReferences:
createMidi1PortsUpdateConfiguration)For service transport plugin interfaces, midisrv is your only caller.
The service:
References:
Plan for mixed-version environments where your transport, the service, and client-facing tooling may not all update at the same time.
Recommendations:
References:
At minimum, you need:
InprocServer32, threading model, etc.)Windows MIDI Services constants define these values:
HKLM\Software\Microsoft\Windows MIDI Services\Transport PluginsCLSID (string GUID, required)Enabled (DWORD, optional; missing means enabled, as does a value of 1)References:
Illustrative example:
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows MIDI Services\Transport Plugins\MyCompany.MyTransport]
"CLSID"="{12345678-1234-1234-1234-1234567890AB}"
"Enabled"=dword:00000001
Service plugins are subject to a signing check before loading.
For development and local testing, enabling Developer Mode in Windows Settings bypasses the plugin signing requirement. This is useful for unsigned test builds, but it should not be used as a production deployment model.
Recommendations:
Reference:
Install transport binaries in Program Files and register from that stable location. 32-bit transports are not supported.
Recommendations:
Treat deployment and servicing as part of your transport design, not an afterthought.
Recommendations:
Your plugin must be safe to run under the service account context (Local Service) and must treat all external data as untrusted.
Recommendations:
For signing policy and development-mode bypass guidance, see the Code signing and Developer Mode section above.
Reference:
Assume callbacks and control operations can occur on different threads, and design for reentrancy.
Recommendations:
Operational expectation:
Use a clear split between COM boundary failures and transport-domain failures.
Recommendations:
HRESULT for interface activation and contract-level failures (invalid args, unavailable interfaces, initialization failure)Reference examples:
Define and test an explicit lifecycle model for each major transport component.
Typical service flow for transport components:
IMidiTransport::ActivateInitialize for endpoint/configuration managers and data-path objectsShutdown and resource teardownRecommendations:
Initialize/Shutdown calls safe and predictableReference:
Transport plugins run in-proc with midisrv, so failures can impact the entire MIDI stack on the machine.
Minimum stability bar:
Recommended patterns:
HRESULT or structured responseInitialize/Shutdown ordering should be robust)The in-box transports use ATL COM, which is consistent with other similar Windows components.
You are not required to use ATL specifically, but your implementation should produce high-quality native COM components with predictable lifetime and threading behavior.
Recommendations:
midisrv memory footprint or introduces non-deterministic pausesFor scenarios where user approval is required (for example, Network MIDI 2.0 remote client approval), use a split-control pattern:
This keeps policy decisions outside the service UI surface while preserving a service-controlled source of truth.
References:
Plan diagnostics early so production issues can be triaged without code changes.
Recommendations:
Important data-handling guidance:
The WinRT API provides a straightforward way to send custom transport JSON, invoke command verbs, and query capabilities.
Key API surface:
Example (C++/WinRT):
using namespace winrt;
using namespace Windows::Data::Json;
using namespace Windows::Devices::Midi2::ServiceConfig;
void ConfigureTransport(guid transportId)
{
JsonObject update;
update.Insert(L"action", JsonValue::CreateStringValue(L"refresh"));
update.Insert(L"reason", JsonValue::CreateStringValue(L"user-request"));
auto response = MidiServiceTransportPluginConfigManager::SendUpdate(transportId, update);
if (response.Status() == MidiServiceConfigResponseStatus::Success)
{
// Transport-defined JSON payload
auto result = response.ResponseJson();
}
else
{
auto serviceCode = response.ServiceErrorCode();
auto serviceMessage = response.ServiceErrorMessage();
// Log/report serviceCode + serviceMessage
}
// Query a single capability key
bool supportsFeatureX = MidiServiceTransportPluginConfigManager::QueryCapability(
transportId,
L"supportsFeatureX");
// Query all capability flags
auto allCaps = MidiServiceTransportPluginConfigManager::QueryAllCapabilities(transportId);
// Send a command verb
MidiServiceTransportCommand cmd(transportId);
cmd.Verb(MidiServiceTransportCommonCommands::QueryCapabilities());
auto cmdResponse = MidiServiceTransportPluginConfigManager::SendCommand(cmd);
}
For real usage examples in this repo, see:
These built-in transports are good reference points when creating your own COM transport plugin:
These examples show how to structure COM activation, endpoint/config managers, metadata reporting, and transport lifecycle behavior.
The Network MIDI 2.0 transport shows how to interact with an external data source/destination and protocol from user mode. The KS Transport interacts with the kernel mode UMP driver. Basic Loopback and Loopback show implementing a transport which simply routes messages internally, without any external hooks or protocol involved.
When you look at the example code, you may find
ifstatements that include a flag check with a name likeFeatureServicing_XYZ. Those are for internal Windows CFR rollouts. In the public repo, those all resolve totrue. Internally, there are more checks for feature enablement. Over time, theelsebranch of those checks gets pruned from the source.
Before shipping a third-party transport plugin, verify:
CLSID