Skip to content
Open
Show file tree
Hide file tree
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
21 changes: 18 additions & 3 deletions bindings/js/src/com_completion_test_hooks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -652,15 +652,30 @@ unsafe extern "system" fn result_u32(this: *mut c_void, output: *mut u32) -> HRE
}

macro_rules! stub {
($name:ident($($arg:ident: $typ:ty),*)) => {
($name:ident($($arg:ident: $typ:ty),* $(,)?)) => {
unsafe extern "system" fn $name(_this: *mut c_void, $($arg: $typ),*) -> HRESULT {
E_NOTIMPL
}
};
}
stub!(audio_initialize(_mode: AUDCLNT_SHAREMODE, _flags: u32, _duration: i64, _period: i64, _format: *const WAVEFORMATEX, _session: *const GUID));
stub!(
audio_initialize(
_mode: AUDCLNT_SHAREMODE,
_flags: u32,
_duration: i64,
_period: i64,
_format: *const WAVEFORMATEX,
_session: *const GUID,
)
);
stub!(audio_i64(_value: *mut i64));
stub!(audio_format_supported(_mode: AUDCLNT_SHAREMODE, _format: *const WAVEFORMATEX, _closest: *mut *mut WAVEFORMATEX));
stub!(
audio_format_supported(
_mode: AUDCLNT_SHAREMODE,
_format: *const WAVEFORMATEX,
_closest: *mut *mut WAVEFORMATEX,
)
);
stub!(audio_mix_format(_format: *mut *mut WAVEFORMATEX));
stub!(audio_period(_default: *mut i64, _minimum: *mut i64));
stub!(audio_noargs());
Expand Down
23 changes: 22 additions & 1 deletion tools/dynwinrt-codegen/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ package.
| `--winmd-list FILE` | Newline-separated metadata paths to emit; blank lines and `#` comments are ignored. |
| `--folder DIR` | Load every `.winmd` file directly inside a directory. |
| `--namespace NS` | Generate one namespace. Without it, generate all non-`Windows.*` namespaces in the input. |
| `--class-name NAME[,NAME...]` | Generate specific classes, public interfaces, or native `Apis` containers. Use fully qualified names, or unqualified names together with `--namespace`. |
| `--class-name NAME[,NAME...]` | Generate specific classes, public interfaces, non-generic WinRT delegates, or native `Apis` containers. Use fully qualified names, or unqualified names together with `--namespace`. |
| `--ref PATH[;PATH...]` | Metadata used only for type resolution. Sibling discovery is disabled for references. |
| `--ref-list FILE` | Newline-separated reference metadata paths; blank lines and `#` comments are ignored. |
| `--lang js\|py` | `js` emits CommonJS `.js`, an ESM facade, and `.d.ts` files (default); `py` emits `.py`, `.pyi`, and `py.typed`. |
Expand Down Expand Up @@ -82,6 +82,20 @@ dynwinrt-codegen generate `
--output .\generated-python
```

Explicitly select a WinRT class and its delegate (also supported with `--lang py`):

```powershell
dynwinrt-codegen generate `
--namespace Windows.System.Threading `
--class-name ThreadPool,WorkItemHandler `
--output .\generated
```

Delegates can also be selected alone or emitted through their namespace. Their
IID and callback parameter types come from the same metadata pipeline used for
automatic dependencies. Open generic delegate definitions are not supported as
explicit roots; closed instantiations continue to be resolved as dependencies.

Load emitted metadata and reference metadata from list files:

```powershell
Expand Down Expand Up @@ -188,6 +202,13 @@ cargo build -p dynwinrt-codegen --release
cargo test -p dynwinrt-codegen
```

`delegate_root_selection_test` covers explicit, incremental, namespace, and
automatic delegate selection. Its native callback cases use a built JavaScript
binding and `DYNWINRT_TEST_PYTHON` pointing to a Python environment with the
matching wheel installed. `DYNWINRT_TEST_JS_RUNTIME` (binding package directory)
and `DYNWINRT_TEST_NODE` can select a matching JavaScript runtime/architecture;
`DYNWINRT_PYRIGHT` enables the additional strict Python consumer checks.

Official npm and PyPI packages are built and published by the repository release
pipelines.

Expand Down
9 changes: 8 additions & 1 deletion tools/dynwinrt-codegen/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,8 @@ enum Commands {
#[arg(long, value_name = "NS")]
namespace: Option<String>,

/// Generate bindings for specific class(es), comma-separated.
/// Generate specific classes, public interfaces, or non-generic WinRT delegates,
/// comma-separated.
/// Names may be qualified, or unqualified when --namespace is supplied.
/// E.g. --class-name Uri or --class-name Windows.Foundation.Uri
#[arg(long, name = "class", value_name = "NAME")]
Expand Down Expand Up @@ -595,6 +596,10 @@ fn run() -> Result<(), String> {
com_coclasses.push(coclass);
continue;
}
if let Some(delegate) = meta::parse_delegate(&winmd, ns, cls)? {
requested_winrt_interfaces.push(delegate);
continue;
}
if let Some(interface) = meta::parse_public_interface(&winmd, ns, cls) {
requested_winrt_interfaces.push(interface);
continue;
Expand Down Expand Up @@ -1108,6 +1113,7 @@ fn run() -> Result<(), String> {
}
selected_classes.extend(meta::parse_namespace(&winmd, ns));
selected_interfaces.extend(meta::parse_interfaces(&winmd, ns));
selected_interfaces.extend(meta::parse_delegates(&winmd, ns)?);
selected_enums.extend(meta::parse_enums(&winmd, ns));
}
winui::add_implicit_classes(&winmd, &mut selected_classes);
Expand Down Expand Up @@ -1138,6 +1144,7 @@ fn run() -> Result<(), String> {
for ns in &namespaces {
all_classes.extend(meta::parse_namespace(&winmd, ns));
all_interfaces.extend(meta::parse_interfaces(&winmd, ns));
all_interfaces.extend(meta::parse_delegates(&winmd, ns)?);
all_enums.extend(meta::parse_enums(&winmd, ns));
}
winui::add_implicit_classes(&winmd, &mut all_classes);
Expand Down
120 changes: 120 additions & 0 deletions tools/dynwinrt-codegen/src/meta.rs
Original file line number Diff line number Diff line change
Expand Up @@ -626,6 +626,126 @@ fn parse_public_interface_from_def(
parse_interface(index, def.namespace(), def.name())
}

/// Parse a non-generic WinRT delegate root through the interface dependency pipeline.
/// Invalid delegate definitions must not fall back to runtime-class projection.
pub fn parse_delegate(
winmd_paths: &str,
namespace: &str,
name: &str,
) -> Result<Option<InterfaceMeta>, String> {
let index = load_index(winmd_paths)
.ok_or_else(|| "Failed to load WinRT delegate metadata".to_string())?;
let Some(def) = index.get(namespace, name).next() else {
return Ok(None);
};
if !is_delegate_definition(&def) {
return Ok(None);
}
parse_delegate_from_def(&index, &def).map(Some)
}

/// Parse public, non-generic WinRT delegates selected by a namespace root.
pub fn parse_delegates(winmd_paths: &str, namespace: &str) -> Result<Vec<InterfaceMeta>, String> {
let index = load_index(winmd_paths)
.ok_or_else(|| "Failed to load WinRT delegate metadata".to_string())?;
index
.all()
.filter(|def| {
def.namespace() == namespace
&& is_delegate_definition(def)
&& def
.flags()
.contains(windows_metadata::TypeAttributes::WindowsRuntime)
&& def
.flags()
.contains(windows_metadata::TypeAttributes::Public)
&& !def.flags().is_nested()
&& !def.name().starts_with('<')
&& def.generic_params().next().is_none()
})
.map(|def| parse_delegate_from_def(&index, &def))
.collect()
}

fn is_delegate_definition(def: &reader::TypeDef) -> bool {
def.extends().is_some_and(|base| {
base.namespace() == "System" && matches!(base.name(), "Delegate" | "MulticastDelegate")
})
}

fn parse_delegate_from_def(
index: &reader::Index,
def: &reader::TypeDef,
) -> Result<InterfaceMeta, String> {
use windows_metadata::{Type, TypeAttributes, Value};

let invalid = |reason: &str| {
format!(
"Cannot generate WinRT delegate {}.{}: {reason}",
def.namespace(),
def.name()
)
};
if !def.flags().contains(TypeAttributes::WindowsRuntime)
|| !def.flags().contains(TypeAttributes::Public)
|| def.flags().is_nested()
|| def.flags().contains(TypeAttributes::Interface)
|| def.name().starts_with('<')
|| def.has_attribute("ExclusiveToAttribute")
{
return Err(invalid("not a public Windows Runtime delegate"));
}
if def.generic_params().next().is_some() {
return Err(invalid(
"open generic delegate roots require closed type arguments and are not supported",
));
}
let Some(attribute) = def.find_attribute("GuidAttribute") else {
return Err(invalid("missing GuidAttribute"));
};
if !matches!(
attribute.value().as_slice(),
[
(_, Value::U32(_)),
(_, Value::U16(_)),
(_, Value::U16(_)),
(_, Value::U8(_)),
(_, Value::U8(_)),
(_, Value::U8(_)),
(_, Value::U8(_)),
(_, Value::U8(_)),
(_, Value::U8(_)),
(_, Value::U8(_)),
(_, Value::U8(_))
]
) {
return Err(invalid("malformed GuidAttribute"));
}
if !def.methods().any(|method| method.name() == ".ctor")
|| def
.methods()
.filter(|method| method.name() == "Invoke")
.count()
!= 1
{
return Err(invalid("requires .ctor and exactly one Invoke contract"));
}

let mut contract = InterfaceImplementationMetadata::default();
collect_implementation_delegates(
&Type::named(def.namespace(), def.name()),
index,
&[],
&mut contract,
);
if !contract.diagnostics.is_empty() {
return Err(invalid(&contract.diagnostics.join("; ")));
}
parse_interface(index, def.namespace(), def.name())
.filter(InterfaceMeta::is_delegate)
.ok_or_else(|| invalid("could not parse delegate metadata"))
}

/// Parse enums in a namespace.
pub fn parse_enums(winmd_paths: &str, namespace: &str) -> Vec<TypeMeta> {
let index = match load_index(winmd_paths) {
Expand Down
Loading
Loading