Skip to content

Package ijwhost.dll as native asset for self-contained publish#11664

Open
mmanolova-msft wants to merge 1 commit into
dotnet:mainfrom
mmanolova-msft:fix/include-ijwhost-in-publish
Open

Package ijwhost.dll as native asset for self-contained publish#11664
mmanolova-msft wants to merge 1 commit into
dotnet:mainfrom
mmanolova-msft:fix/include-ijwhost-in-publish

Conversation

@mmanolova-msft
Copy link
Copy Markdown

@mmanolova-msft mmanolova-msft commented May 26, 2026

Fixes #11651

Main PR: N/A (this is the main branch fix)

Description

After PR #11575 migrated C++/CLI projects from /clr:pure to /clr:NetCore, DirectWriteForwarder.dll and System.Printing.dll became mixed-mode assemblies requiring ijwhost.dll at runtime. The build was placing ijwhost.dll in the managed lib/net11.0/ folder (via the $(OutDir)*.dll glob), where it is not recognized by RuntimeList.xml. This caused it to be missing from self-contained publish output.

The fix excludes ijwhost.dll from lib/ folder packaging and adds a target to place it in runtimes/win-{arch}/native/ where it is properly listed in the runtime pack manifest.

Customer Impact

Any WPF app published as self-contained (dotnet publish -r win-x64 --self-contained) crashes on launch with:

System.IO.FileNotFoundException: Could not load file or assembly '...\DirectWriteForwarder.dll'.
The specified module could not be found.

Framework-dependent apps are unaffected (they load from the shared framework folder).

Regression

Yes. Introduced by #11575 (Remove /clr:pure from WPF C++/CLI projects), which shipped in SDK 11.0.100-preview.5.26264.105. Not present in 11.0.100-preview.5.26263.112.

Testing

  • Installed both SDK versions and confirmed the regression
  • Built WPF with the fix (build.cmd -c Release -platform x64 -pack)
  • Verified ijwhost.dll lands in runtimes/win-x64/native/ (not lib/net11.0/)
  • Patched the runtime pack in NuGet cache and confirmed dotnet publish --self-contained includes Ijwhost.dll in output
  • Ran the published WPF app successfully

Risk

Low. The change only affects packaging of ijwhost.dll — moving it from an incorrect location (lib/) to the correct one (native/). This matches how all other native DLLs (vcruntime140_cor3.dll, wpfgfx_cor3.dll, etc.) are already packaged.

Microsoft Reviewers: Open in CodeFlow

After the /clr:pure to /clr:NetCore migration (dotnet#11575), DirectWriteForwarder
and System.Printing became mixed-mode C++/CLI assemblies that require
ijwhost.dll at runtime. The DLL was being placed in the managed lib/ folder
where it is not recognized by RuntimeList.xml, causing it to be missing from
self-contained publish output.

Exclude ijwhost.dll from the lib/ packaging and add it as a native asset
under runtimes/win-{arch}/native/ so it is properly included in the runtime
pack manifest.

Fixes dotnet#11651

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@mmanolova-msft mmanolova-msft requested review from a team and Copilot May 26, 2026 09:49
@dotnet-policy-service dotnet-policy-service Bot added the PR metadata: Label to tag PRs, to facilitate with triage label May 26, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes a regression in WPF self-contained publish by ensuring ijwhost.dll (required to load IJW mixed-mode C++/CLI assemblies) is packaged as a native runtime asset rather than ending up under the managed lib/ folder, which prevents it from being picked up for self-contained publish.

Changes:

  • Excludes ijwhost.dll from the default $(OutDir)*.dll managed (lib/) packaging glob.
  • Adds a packaging target in both mixed-mode projects to include ijwhost.dll under runtimes/win-{arch}/native/.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
src/Microsoft.DotNet.Wpf/src/System.Printing/System.Printing.vcxproj Excludes ijwhost.dll from lib/ packaging and adds it as a native runtime asset under runtimes/win-{arch}/native/.
src/Microsoft.DotNet.Wpf/src/DirectWriteForwarder/DirectWriteForwarder.vcxproj Same packaging adjustment as System.Printing to ensure ijwhost.dll is treated as a native asset for self-contained publish.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@mmanolova-msft
Copy link
Copy Markdown
Author

@dotnet-policy-service agree company="Microsoft"

nagilson added a commit to dotnet/sdk that referenced this pull request May 28, 2026
Self-contained WPF apps crash due to missing ijwhost.dll.
The fix is tracked in dotnet/wpf#11664.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@vinnarayana-msft
Copy link
Copy Markdown
Contributor

⚠️ Additional Regression: Single-File Publish (PublishSingleFile=true)

This PR correctly fixes the self-contained publish regression by routing ijwhost.dll to runtimes/win-{arch}/native/. However, there's a separate regression with single-file publish that isn't covered here.

Problem

After the /clr:pure/clr:netcore migration, DirectWriteForwarder.dll and System.Printing.dll are now IJW (mixed-mode) assemblies. In single-file publish, they are bundled inside the exe and the runtime attempts to load them from memory. IJW assemblies cannot be loaded from memory — they require the OS LoadLibrary call (because they have a native DllMain entry point, VTableFixups, and PE imports like ijwhost.dll).

Evidence

  1. Verified locally: Single-file publish of a WPF app on preview.4 SDK bundles DirectWriteForwarder.dll inside the exe (not extracted alongside it). Only native DLLs (wpfgfx_cor3.dll, PenImc_cor3.dll, etc.) are extracted.

  2. RuntimeList.xml (from microsoft.windowsdesktop.app.runtime.win-x64 NuGet pack):

    <File Type="Managed" Path="runtimes/win-x64/lib/net11.0/DirectWriteForwarder.dll" ... />
    <File Type="Managed" Path="runtimes/win-x64/lib/net11.0/System.Printing.dll" ... />

    Type="Managed" = bundled inside the exe in single-file mode.

  3. Bundler logic (Bundler.cs): IsAssembly() checks CorHeader != null → IJW assemblies have a CorHeader → classified as FileType.Assembly → always bundled (never excluded).

  4. Microsoft docs (Single-file deployment overview): "Managed C++ components aren't well suited for single file deployment."

Why This Will Crash

  • Single-file host loads Type="Managed" assemblies from memory (no physical file on disk)
  • IJW assemblies need LoadLibrary → requires a physical DLL file
  • IJW initialization requires ijwhost.dll to intercept the load
  • Result: FileNotFoundException or BadImageFormatException at runtime

Fix Required

This needs a change in the dotnet/windowsdesktop repo (not WPF), since that's where RuntimeList.xml is generated. In Microsoft.WindowsDesktop.App.Runtime.sfxproj:

<FrameworkListFileClass Include="DirectWriteForwarder" Profile="WPF" DropFromSingleFile="true" />
<FrameworkListFileClass Include="System.Printing" Profile="WPF" DropFromSingleFile="true" />

DropFromSingleFile="true" forces these DLLs to be extracted alongside the exe (not bundled) — the same behavior as wpfgfx_cor3.dll, PenImc_cor3.dll, and other native WPF dependencies.

Note: We can't use the same native/ folder trick used for ijwhost.dll here, because these DLLs contain managed types (e.g., System.Printing.PrintQueue) that the assembly resolver needs to find in lib/.

Summary

Scenario This PR fixes? Additional fix needed?
Self-contained publish ✅ Yes
Framework-dependent ✅ N/A (never broken)
Single-file publish ❌ No DropFromSingleFile="true" in dotnet/windowsdesktop sfxproj

@mmanolova-msft please validate this once. The changes you made for self-contained looks good.

DonnaChen888 pushed a commit to dotnet/sdk that referenced this pull request May 29, 2026
Self-contained WPF apps crash due to missing ijwhost.dll.
The fix is tracked in dotnet/wpf#11664.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Comment on lines +164 to +182
<!--
Exclude ijwhost.dll from the managed (lib/) folder packaging.
It must be placed in runtimes/win-{arch}/native/ instead so that it is
recognized as a native asset and included in RuntimeList.xml for
self-contained publish scenarios.
-->
<ItemGroup>
<FileNamesExcludedFromPackaging Include="ijwhost.dll" />
</ItemGroup>
<Target Name="_PackageIjwHostAsNativeAsset" BeforeTargets="CopyPackageAssets" AfterTargets="IdentifyPackageAssets" Condition="'$(PackageName)'!='' and Exists('$(OutDir)ijwhost.dll')">
<PropertyGroup>
<_IjwHostNativeDestFolder Condition="'$(Platform)'=='AnyCPU' or '$(Platform)'=='x86' or '$(Platform)'=='Win32'">runtimes\win-x86\native\</_IjwHostNativeDestFolder>
<_IjwHostNativeDestFolder Condition="'$(Platform)'=='x64'">runtimes\win-x64\native\</_IjwHostNativeDestFolder>
<_IjwHostNativeDestFolder Condition="'$(Platform)'=='arm64'">runtimes\win-arm64\native\</_IjwHostNativeDestFolder>
</PropertyGroup>
<ItemGroup>
<PackageAsset Include="$(OutDir)ijwhost.dll" RelativePath="$(ArtifactsPackagingDir)$(NormalizedPackageName)\$(_IjwHostNativeDestFolder)" />
</ItemGroup>
</Target>
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Since this is same as the one in DirectWriteForwader, I suggest we have this in a common place which these two files use. It would help us avoid missing updating individually if we face these types of regression later again.

</ItemGroup>
<Target Name="_PackageIjwHostAsNativeAsset" BeforeTargets="CopyPackageAssets" AfterTargets="IdentifyPackageAssets" Condition="'$(PackageName)'!='' and Exists('$(OutDir)ijwhost.dll')">
<PropertyGroup>
<_IjwHostNativeDestFolder Condition="'$(Platform)'=='AnyCPU' or '$(Platform)'=='x86' or '$(Platform)'=='Win32'">runtimes\win-x86\native\</_IjwHostNativeDestFolder>
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: we can simplify it further by moving it down after x64 and arm64. After x64 and arm64, if the value is still not set, we can keep it as x86. Better readability and avoid multiple platform conditions.

Copy link
Copy Markdown
Member

@dipeshmsft dipeshmsft left a comment

Choose a reason for hiding this comment

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

image image

Instead of adding code separately in both projects ( and if any other projects that will come under this in future ), how about fixing this issue in the Packaging.targets file ?

The above screenshot is a potential fix, though I haven't been able to test it yet, but the approach seems cleaner to me.

@pranav-gupta-msft
Copy link
Copy Markdown
Contributor

Agree, with Dipesh's comments.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Included in test pass PR metadata: Label to tag PRs, to facilitate with triage Status:Proposed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[NETSDKE2E] With NET11.0.100-preview.5.26264.105 SDK installed, WPF application which is generated by self-contained publish cannot run successfully.

6 participants