Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 92 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# dynwinrt

**Call any Windows Runtime (WinRT) API from JavaScript or TypeScript without writing a native addon.**
**Call Windows APIs — WinRT, classic COM, and flat Win32 (`[DllImport]`) — from JavaScript or TypeScript without writing a native addon.**

[![@microsoft/dynwinrt](https://img.shields.io/npm/v/@microsoft/dynwinrt.svg?label=%40microsoft%2Fdynwinrt)](https://www.npmjs.com/package/@microsoft/dynwinrt)
[![@microsoft/dynwinrt-codegen](https://img.shields.io/npm/v/@microsoft/dynwinrt-codegen.svg?label=%40microsoft%2Fdynwinrt-codegen)](https://www.npmjs.com/package/@microsoft/dynwinrt-codegen)
Expand All @@ -14,7 +14,7 @@ If you've ever tried to call a modern Windows API (WinAppSDK, Windows AI, notifi
- **Writing a C# addon via `node-api-dotnet`** — needs the .NET SDK, a `csproj` build step, and a hand-maintained wrapper for every API surface you want to expose.
- **Waiting for an official projection** — Windows ships `.winmd` metadata months before any JavaScript- or Python-friendly projection appears in a published package.

`dynwinrt` removes all of that. It reads the same `.winmd` metadata your Windows SDK / WinAppSDK NuGet packages already ship and calls the underlying COM vtables **dynamically at runtime via libffi**. The codegen emits typed `.js` + `.d.ts` wrappers; the runtime invokes them through `dynwinrt`'s native binary. No MSBuild step in your app, no `node-gyp`, no per-Windows-version recompile.
`dynwinrt` removes all of that. It reads the same `.winmd` metadata your Windows SDK / WinAppSDK NuGet packages already ship and calls the underlying COM vtables **dynamically at runtime via libffi**. The codegen emits typed `.js` + `.d.ts` wrappers; the runtime invokes them through `dynwinrt`'s native binary. No MSBuild step in your app, no `node-gyp`, no per-Windows-version recompile. The same runtime and generator now cover three metadata-driven surfaces — **WinRT**, **classic COM**, and **flat Win32 (`[DllImport]`)** — so you can reach Windows APIs whether or not they ship as WinRT runtime classes.

```ts
import { LanguageModel } from './bindings/winrt';
Expand Down Expand Up @@ -87,6 +87,96 @@ set `WINAPPSDK_BOOTSTRAP_DLL_PATH` to the architecture-matched
configures its UI thread for Per-Monitor V2 DPI awareness. Packaged processes
can omit the bootstrap call.

## New: Win32 and classic COM support (JavaScript/TypeScript)

`dynwinrt` now reaches beyond WinRT runtime classes. From the same metadata-driven pipeline it can also project **Windows APIs that are not WinRT**:

- **Classic COM** — `IUnknown`-rooted interfaces created via `CoCreateInstance` and dispatched through the COM vtable (e.g. taskbar, shell).
- **WinRT interop bridges** — the desktop `*Interop` shims a windowed app needs to reach WinRT features from an `HWND` (Share, media controls, file-picker parenting).
- **Flat Win32 `[DllImport]`** — plain DLL exports (e.g. Registry, credentials) called through `LoadLibrary` + `GetProcAddress` + libffi.

Generate Win32/COM wrappers by pointing the codegen at the Win32 metadata (`Windows.Win32.winmd` from the [win32metadata](https://github.com/microsoft/win32metadata) package):

### Getting `Windows.Win32.winmd`

`Windows.Win32.winmd` is not on a stock Windows machine — it ships in the MIT-licensed NuGet package [`Microsoft.Windows.SDK.Win32Metadata`](https://www.nuget.org/packages/Microsoft.Windows.SDK.Win32Metadata). A `.nupkg` is just a zip with the `.winmd` at its root, so **copy-paste this whole block into PowerShell as-is** — it fetches the latest winmd and generates the `ITaskbarList3` classic-COM wrapper (used in the first example below) into `./generated` (no NuGet client, no version number to pick):

```powershell
$pkg = 'microsoft.windows.sdk.win32metadata'
$ver = (Invoke-RestMethod "https://api.nuget.org/v3-flatcontainer/$pkg/index.json").versions[-1]
Invoke-WebRequest "https://api.nuget.org/v3-flatcontainer/$pkg/$ver/$pkg.$ver.nupkg" -OutFile "$env:TEMP\win32meta.zip"
Expand-Archive "$env:TEMP\win32meta.zip" -DestinationPath .\win32meta -Force
npx dynwinrt-codegen generate --winmd .\win32meta\Windows.Win32.winmd --namespace Windows.Win32.UI.Shell --class-name ITaskbarList3 --output ./generated
```

`--class-name` names what to generate — either a specific classic-COM/WinRT interface like `ITaskbarList3` (above), or `Apis`, the synthetic bundle of a whole namespace's flat `[DllImport]` exports (shown in the Flat Win32 example below). Dependencies are pulled in automatically. Swap in any other namespace — for something Electron doesn't give you, try `Windows.Win32.Security.Credentials` / `Apis` (Credential Manager — a replacement for the deprecated `keytar`). Reuse the same `.\win32meta\Windows.Win32.winmd` for every run.

> Win32 and classic-COM generation currently emits JavaScript/TypeScript (`.js` + `.d.ts`).

### Classic COM: `CoCreateInstance` + vtable interfaces

```js
// main.js (Electron main process) — mainWindow is your existing BrowserWindow
import { ITaskbarList3 } from './generated/ITaskbarList3.js';
import { TBPFLAG } from './generated/TBPFLAG.js';

// Your window's HWND — any window size works. getNativeWindowHandle() returns a
// Node Buffer; read it as a 64-bit little-endian bigint.
const hwnd = mainWindow.getNativeWindowHandle().readBigUInt64LE(0);

const taskbar = ITaskbarList3.create(); // CoCreateInstance under the hood
taskbar.hrInit();
taskbar.setProgressState(hwnd, TBPFLAG.TBPF_NORMAL);
taskbar.setProgressValue(hwnd, 40n, 100n); // paint 40% on the taskbar icon
```

The generated wrapper exposes natural typed methods while the runtime handles `CoCreateInstance`, interface registration, pointer arguments, `HRESULT` checks, and vtable slot dispatch — no hand-written IIDs, `REFIID`, `void**`, or vtable indices.

### WinRT interop bridges: from `HWND` to WinRT objects

```js
// main.js (Electron main process)
import { IDataTransferManagerInterop } from './generated/IDataTransferManagerInterop.js';

const hwnd = mainWindow.getNativeWindowHandle().readBigUInt64LE(0); // your existing window

const interop = IDataTransferManagerInterop.create();
const dtm = interop.getForWindow(hwnd); // → DynWinRtValue bridge to DataTransferManager
interop.showShareUIForWindow(hwnd); // pops the Windows Share sheet over your window
```

Where `ITaskbarList3` above is a *pure* classic-COM object — every call stays on its own vtable — an **interop** interface is a one-way bridge from Win32 windowing into WinRT: you hand it an `HWND` and `getForWindow(hwnd)` hands back a live **WinRT** object (here a `DataTransferManager`) that you then drive with the rest of your generated WinRT bindings. `create()` activates the WinRT factory and QIs to the interop interface; the returned COM pointer is adopted and surfaced as a `DynWinRtValue` bridge — with no `riid`/`void**` in the signature.

### Flat Win32 exports: call `[DllImport]` APIs

Flat exports live in a namespace's synthetic `Apis` bundle. Generate a whole namespace at once — here every `Reg*` function — with `--class-name Apis` (reusing the winmd fetched above):

```powershell
npx dynwinrt-codegen generate --winmd .\win32meta\Windows.Win32.winmd --namespace Windows.Win32.System.Registry --class-name Apis --output ./generated
```

```js
import { regOpenKeyExW, regCloseKey } from './generated/Apis.js';
import { REG_SAM_FLAGS } from './generated/REG_SAM_FLAGS.js';

const HKEY_LOCAL_MACHINE = 0x80000002n;

const open = regOpenKeyExW(
HKEY_LOCAL_MACHINE,
'SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion', // note: doubled backslashes in JS
0,
REG_SAM_FLAGS.KEY_READ,
);
if (open.status !== 0) throw new Error(`RegOpenKeyExW failed: LSTATUS=${open.status}`);
try {
console.log(`hKey = 0x${open.phkResult.toString(16)}`);
} finally {
regCloseKey(open.phkResult);
}
```

`[out]` parameters become return fields (`{ status, phkResult }`), caller-allocated buffers and in/out sizes are handled for you, `LPCWSTR` ↔ `string`, and the Win32 `LSTATUS`/error is surfaced. Handles (`HKEY`, `HANDLE`) project as `bigint | number`.

## Repository layout

```
Expand Down
Loading