dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
15.16k stars 4.72k forks source link

TerraFx.Interop.Windows is not getting trimmed when compiled using NativeAOT #66593

Closed jkotas closed 2 years ago

jkotas commented 2 years ago

Repro (this is slightly modified repro from https://github.com/dotnet/runtime/issues/66545):

using System;
using System.Reflection;

using TerraFX.Interop.Windows;
using static TerraFX.Interop.Windows.Windows;
using static TerraFX.Interop.Windows.COINITBASE;
using static TerraFX.Interop.Windows.CLSCTX;
using System.Runtime.InteropServices;

namespace MyApp // Note: actual namespace depends on the project name.
{
    internal unsafe class Program
    {

        static void Main(string[] args)
        {
            var a = Assembly.Load("TerraFx.Interop.Windows");
            foreach (var t in a.GetTypes()) Console.WriteLine(t);

            Console.WriteLine(WMI("Win32_Processor", "ProcessorId"));
        }
        public static string WMI(string wmiClass, string wmiProperty)
        {
            var hresult = CoInitializeEx(null, (uint)COINITBASE_MULTITHREADED);
            if (FAILED(hresult))
                return "";
            hresult = CoInitializeSecurity(
        null,
        -1,
        null,
        null,
        0,
        3,
        null,
        0,
        null
    );
            if (FAILED(hresult))
                return "";
            IWbemLocator* pLoc = null;
            hresult = CoCreateInstance(
        __uuidof<WbemLocator>(),
        null,
        (uint)CLSCTX_INPROC_SERVER,
        __uuidof<IWbemLocator>(),
        (void**)(&pLoc)
    );
            if (FAILED(hresult))
                return "";
            IWbemServices* pServ = null;
            hresult = pLoc->ConnectServer(
                    (ushort*)Marshal.StringToBSTR("ROOT\\CIMV2").ToPointer(),
        null,
        null,
        null,
        0,
        null,
        null,
        &pServ
    );
            if (FAILED(hresult))
                return "";
            hresult = CoSetProxyBlanket(
        (IUnknown*)pServ,
        10,
        0,
        null,
        3,
        3,
        null,
        0
    );
            if (FAILED(hresult))
                return "";
            IEnumWbemClassObject* pEnumerator = null;
            hresult = pServ->ExecQuery(
                (ushort*)Marshal.StringToBSTR("WQL").ToPointer(),
                (ushort*)Marshal.StringToBSTR($"SELECT * FROM {wmiClass}").ToPointer(),
                (int)(WBEM_GENERIC_FLAG_TYPE.WBEM_FLAG_FORWARD_ONLY | WBEM_GENERIC_FLAG_TYPE.WBEM_FLAG_RETURN_IMMEDIATELY),
                null,
                &pEnumerator
            );
            if (FAILED(hresult))
                return "";
            IWbemClassObject* pclsObj = null;
            uint uReturn = 0;

            string result = "";

            while (pEnumerator != null)
            {
                HRESULT hr =
                    pEnumerator->Next((int)WBEM_TIMEOUT_TYPE.WBEM_INFINITE, 1, &pclsObj, &uReturn);
                if (uReturn == 0)
                    break;
                VARIANT vtProp;

                fixed (char* lpString = wmiProperty)
                    hr = pclsObj->Get((ushort*)lpString, 0, &vtProp, null, null);
                string manufacturer = Marshal.PtrToStringBSTR((IntPtr)vtProp.bstrVal);

                //hr = pclsObj->Get(L"Product", 0, &vtProp, 0, 0);
                //string product = W2A(vtProp.bstrVal);
                //hr = pclsObj->Get(L"SerialNumber", 0, &vtProp, 0, 0);
                //string serialNumber = W2A(vtProp.bstrVal);

                result = manufacturer;

                VariantClear(&vtProp);

                pclsObj->Release();
                break;
            }
            pServ->Release();
            pLoc->Release();
            pEnumerator->Release();
            CoUninitialize();
            return result;
        }
    }
}

.csproj:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net6.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
    <PublishTrimmed>true</PublishTrimmed>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="TerraFx.Interop.Windows" Version="10.0.20348" />
    <PackageReference Include="Microsoft.DotNet.ILCompiler" Version="7.0.0-*" />
  </ItemGroup>

</Project>
dotnet publish -c Release -r win-x64

Actual result:

The final binary is 36MB. It includes code and metadata for all types in TerraFx.Interop.Windows.

Expected result:

The final binary is <10MB. It only includes code and metadata for types that are actually used by the program.

Publishing with regular IL trimming using IL linker does not have this issue.

dotnet-issue-labeler[bot] commented 2 years ago

I couldn't figure out the best area label to add to this issue. If you have write-permissions please help me learn by adding exactly one area label.

jkotas commented 2 years ago

cc @MichalStrehovsky @agocke

tannergooding commented 2 years ago

Noting that TerraFX.Interop.Windows is opting into trimming support here: https://github.com/terrafx/terrafx.interop.windows/blob/main/Directory.Build.targets#L23-L32

If you clone the repo and do dotnet publish -c Release -f net6.0-windows -r win-x64 --self-contained in <repo-root>\samples\DirectX you'll get:

If there is something I'm missing for NAOT support, I'm happy to add it. But AFAIK, I'm following all the documented bits required.

jkotas commented 2 years ago

The fix is available on nightly feed now.

The final binary is 36MB. It includes code and metadata for all types in TerraFx.Interop.Windows.

The binary is <5MB now. However, there is still minor size inefficiency. Opened #66716.