Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add preliminary support for Windows .manifest files #17448

Merged
merged 2 commits into from
Oct 16, 2023

Conversation

squeek502
Copy link
Collaborator

@squeek502 squeek502 commented Oct 9, 2023

This is one option for addressing #17406 (it is option 3 in #17406 (comment)). See that issue for some more details.

An embedded manifest file is really just XML data embedded as a RT_MANIFEST resource (ID = 24). Typically, the Windows-only 'Manifest Tool' (mt.exe) is used to embed manifest files, and mt.exe also seems to perform some transformation of the manifest data before embedding, but in testing it doesn't seem like the transformations are necessary to get the intended result.

So, to handle embedding manifest files, Zig now takes the following approach:

  • Generate a .rc file with the contents 1 24 "path-to-manifest.manifest"
  • Compile that generated .rc file into a .res file
  • Link the .res file into the final binary

This effectively achieves the same thing as mt.exe minus the validation/transformations of the XML data that it performs.

How this is used:

On the command line:

zig build-exe main.zig main.manifest

(on the command line, specifying a .manifest file when the target object format is not COFF is an error)

or in build.zig:

const exe = b.addExecutable(.{
    .name = "manifest-test",
    .root_source_file = .{ .path = "main.zig" },
    .target = target,
    .optimize = optimize,
    .win32_manifest = .{ .path = "main.manifest" },
});

(in build.zig, the manifest file is ignored if the target object format is not COFF)

Note: Currently, only one manifest file can be specified per compilation. This is because the ID of the manifest resource is currently always 1. Specifying multiple manifests could be supported if a way for the user to specify an ID for each manifest is added (manifest IDs must be a u16). I'm not familiar enough with manifests to know what the use case for multiple manifests is.

Closes #17406


remnants of the original OP

The above works as intended on Windows for the example manifest here at least: https://learn.microsoft.com/en-us/windows/apps/design/globalizing/use-utf8-code-page#set-a-process-code-page-to-utf-8

This is currently a draft because I need to do some more testing to better determine if it's a viable strategy more generally:

  • Need to test cross-compilation (should work, since it's just using .rc/.res files under the hood)
  • I'm planning on going through the .manifest files in Windows-classic-samples and seeing if this method of embedding the manifest has the same effective result as the MSVC compilers method (this is the real kicker, and might be somewhat tough to determine)

@squeek502
Copy link
Collaborator Author

Did some testing and I think this is the right way to go. The downside of this approach is that Zig won't do any validation of manifest files, so it's on the user to ensure that their manifest file is correct (mt.exe -validate_manifest).


Here's a real world example from Windows-classic-samples:

Compiled with Zig on Windows via:

zig cc TIPAutoCompleteSDKSample.cpp TIPAutoCompleteSDKSample.rc TIPAutoCompleteSDKSample.manifest -target x86_64-windows-msvc -Wl,/subsystem:windows -municode -lc -luser32 -lole32 -ladvapi32 -lgdi32 -o TIPAutoCompleteSDKSample.exe

Here's proof that the DPI awareness is in effect in the Zig-compiled version:
manifest-dpiaware

And here's proof that the control styles are affected in the intended way (see the checkbox and button style difference in the version without the manifest):
manifests-windows-control-styles


Note that this 'generate an .rc with a RT_MANIFEST resource' strategy happens to be the exact thing that MSVC does for auto-generated manifests. If the "Generate Manifest" option is set to Yes in "Linker > Manifest File" in Visual Studio, then, for example:

  • Gallery.exe.embed.manifest gets generated
  • Gallery_manifest.rc gets generated, with the contents:
    1 /* CREATEPROCESS_MANIFEST_RESOURCE_ID */ 24 /* RT_MANIFEST */ "x64\\Debug\\Gallery.exe.embed.manifest"
  • Gallery_manifest.rc gets compiled into Gallery.exe.embed.manifest.res which gets linked into the final .exe

@squeek502 squeek502 force-pushed the win32-manifest branch 2 times, most recently from 1fd102d to c21ee00 Compare October 10, 2023 08:05
@squeek502 squeek502 marked this pull request as ready for review October 10, 2023 08:05
Copy link
Member

@andrewrk andrewrk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work! I agree with your reasoning regarding option 3.

src/Compilation.zig Outdated Show resolved Hide resolved
lib/std/Build/Step/Compile.zig Outdated Show resolved Hide resolved
An embedded manifest file is really just XML data embedded as a RT_MANIFEST resource (ID = 24). Typically, the Windows-only 'Manifest Tool' (`mt.exe`) is used to embed manifest files, and `mt.exe` also seems to perform some transformation of the manifest data before embedding, but in testing it doesn't seem like the transformations are necessary to get the intended result.

So, to handle embedding manifest files, Zig now takes the following approach:

- Generate a .rc file with the contents `1 24 "path-to-manifest.manifest"`
- Compile that generated .rc file into a .res file
- Link the .res file into the final binary

This effectively achieves the same thing as `mt.exe` minus the validation/transformations of the XML data that it performs.

How this is used:

On the command line:
```
zig build-exe main.zig main.manifest
```
(on the command line, specifying a .manifest file when the target object format is not COFF is an error)

or in build.zig:

```
const exe = b.addExecutable(.{
    .name = "manifest-test",
    .root_source_file = .{ .path = "main.zig" },
    .target = target,
    .optimize = optimize,
    .win32_manifest = .{ .path = "main.manifest" },
});
```
(in build.zig, the manifest file is ignored if the target object format is not COFF)

Note: Currently, only one manifest file can be specified per compilation. This is because the ID of the manifest resource is currently always 1. Specifying multiple manifests could be supported if a way for the user to specify an ID for each manifest is added (manifest IDs must be a u16).

Closes ziglang#17406

options
…Windows manifest file

Example:

> zig build-exe test.zig test.xml
warning: embedded manifest files must have the extension '.manifest'
error: unrecognized file extension of parameter 'test.xml'
Copy link
Member

@andrewrk andrewrk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks perfect

@andrewrk andrewrk merged commit ca690ff into ziglang:master Oct 16, 2023
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support for embedding Windows Manifest XML to the compiled exe
2 participants