dotnet / runtime

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

Single-File Executable Cannot Use Registration Free COM Interop in .NET 5-Preview 7 #41309

Open andrewmd5 opened 4 years ago

andrewmd5 commented 4 years ago

Description

Normally, when selecting that a COM dependency should have it's interop types embedded and run isolated (registration free) the C# compiler deals with updating the app manifest automatically. However when publishing a single file executable the manifest does not update properly and the COM dependency is not found, thus it cannot run registration free.

image

If the app.manifest is manually updated to contain all the necessary references, the COM DLL cannot be found still. If you create a bundle and then manually copying the COM DLL that does work however. Setting the DLL as content solution does not help with resolving it.

The expected outcome when building a single file executable is that the "Isolated" flag of the COM dependency properly updates the app manifest and references the COM DLL. I believe it is failing because the manifest is searching for <file name="SomeFile.dll"> which cannot be found until manually pasting it into the working directory of the bundle.

ghost commented 4 years ago

Tagging subscribers to this area: @swaroop-sridhar, @agocke See info in area-owners.md if you want to be subscribed.

agocke commented 4 years ago

cc @vitek-karas

vitek-karas commented 4 years ago

I assume the COM DLL in this case is not a .NET assembly (.NET Framework or .NET Core) right? How do you try to include the COM DLL in the app? (I assume there's something about it in the project file...)

I don't know about updating the manifest, that sounds like COM SDK integration issue - /cc @AaronRobinsonMSFT on that.

If the COM DLL is .NET Core based then this is currently not supported - see https://github.com/dotnet/runtime/issues/40802.

roman380 commented 4 years ago

It's a native code COM DLL with a type library, which we expected to be imported and converted into manifest entries. This used to work at some point with .NET Core 3 builds fine in particular. We have the C++ project as a dependency project in the same solution, if this is important (it might make a difference in terms of cross-project dependency).

Our workaround here is to manually add the necessary missing manifest entries into .NET5 project's manifest file, build the project and then copy COM DLL dependency into final bundle.

The manifest entries in question are comInterfaceExternalProxyStub elements and typelib, comClass under file element.

We expect this to happen automatically once we make the COM dependency Copy Local = Yes, Embed Interop Types = Yes, Isolated = Yes. It does not happen.

vitek-karas commented 4 years ago

Thanks for the detailed info - I'll have to leave most of this up to @AaronRobinsonMSFT since he knows the details of the manifest generation.

build the project and then copy COM DLL dependency into final bundle.

I wonder about this part. .NET 5 bundle by default doesn't include native libraries (from your description the COM DLL should be a native dll) - because we have no way to load them directly from the bundle. So by default these files should be left next to the single-file bundle. You can "Force" them into the bundle via a property, but what that will do is reenable the auto-extraction behavior (Similar to .NET Core 3.1 single-file) which comes with certain complexities. Not sure what behavior you would prefer - add couple more files to the app (since coreclr.dll is already next to the bundle anyway), or use the auto-extract functionality with its complexities.

roman380 commented 4 years ago

Also, it might be unrelated to this question - I would like to mention one another side effect. If bundle executable is used against BeginUpdateResourceW API, the update strips significant part of the resource and damages the binary.

I am guessing the executable itself is fine (we all know it works well otherwise if used as built) however the API is out of date and does not take into account something "new" existing in the produced bundle executable.

It was one of the ways we used to attach necessary regfree COM manifest information to the built binary and it is no longer available because of the mentioned API failure. Putting the data into project's does work though, and elements from app.manifest are transferred to single file executable in the way that gives us a workaround, however this way it makes it more difficult to automate builds: we have to extract parts of type library information converted to manifest XML and then merge manually with project's manifest file.

I am guessing it is expected and assumed to work this way: (a) when COM dependency is defined via project references and properties are set for regfree COM use, the build process would update manifest as necessary and then - this might require additional project configuration though because, in particular, native DLL dependency might have its own dependencies in turn such as statically linked dynamic libraries - copy the dependency files into target folder where single file executable is published to.

roman380 commented 4 years ago

In the view of incompatibility with BeginUpdateResourceW API mentioned above I gave a look at how you generate the singlefilehost.exe thing. So you seem to append dependencies to the PE binary without including them into the parent PE structure. No wonder the API strips the byte that do not belong and that are no referenced by the master binary structure.

I wonder if this is documented anywhere? I am curious, specifically, how the bundle executable are internally referencing this appended data, by fixed offsets or maybe it parses the executable and identifies chained binaries (assemblies)? If I would update resources of singlefilehost.exe in the way that changes the file size so that appended data is shifted in the bundle, would that work out.

AaronRobinsonMSFT commented 4 years ago

@roman380 Support for consuming COM servers in .NET shouldn't be impacted much - at least nothing on the runtime side has changed here. The project system however is non-trivial since it attempts to reconcile the .NET Framework approach and .NET Core. The only scenarios that have changed from the runtime's perspective is authoring a COM server written in .NET - consumption shouldn't really be impacted, but I don't know the inner workings of how MSBuild handles this.

Would it be possible for you to create a simple repo that demonstrates the failure? It doesn't have to run, manually looking at the final binary with the missing manifest entries should be enough to investigate where in the build pipeline it is failing.

@vitek-karas It appears the generated SxS manifest logic isn't being told about these COM assets and thus doesn't include the appropriate entries. I personally don't see how we can make single file work with reg free COM without copying over the native binary - embedding it in the single file binary isn't going to work. I guess in the "old" way where the embedded assets were extracted and written to disk it would be possible, but the native COM server would need to be written to disk.

vitek-karas commented 4 years ago

I agree that the native binary needs to live on disk - either from the start as a file next to the bundle, or extracted on startup. In 5.0 we have both options available and it's up to the application to pick one or the other. I was just curious what happens in this specific case, since the default should be that the native binary gets put next to the bundle (no self-extract by default).

AaronRobinsonMSFT commented 4 years ago

In 5.0 we have both options available and it's up to the application to pick one or the other.

Nice. I didn't realize that.

I was just curious what happens in this specific case, since the default should be that the native binary gets put next to the bundle (no self-extract by default).

I think the issue here is the native binary isn't known by the publish step and probably isn't copied to the publish directory. We hit issues with COM references not being copied during publish and that required letting the Target know it needed to copy additional assets - see https://github.com/dotnet/sdk/issues/10908.

roman380 commented 4 years ago

@AaronRobinsonMSFT

Reproducer: https://github.com/roman380/ComDependency_41309 see README for instructions

Branch workaround has a solution for the issue: disable isolation, add it manually via manifest, copy the dependency to singlefilehost target folder after the build (see branch README at the bottom)

AaronRobinsonMSFT commented 4 years ago

@roman380 I am getting the following error when I attempt to build from an elevated Visual Studio 2019. I am using x64 and Release.

1>------ Build started: Project: App, Configuration: Release Any CPU ------
1>You are using a preview version of .NET. See: https://aka.ms/dotnet-core-preview
1>C:\Program Files (x86)\Microsoft Visual Studio\2019\Preview\MSBuild\Current\Bin\Microsoft.Common.CurrentVersion.targets(3895,5): error MSB3179: Problem isolating COM reference 'LibLib.dll': No registered classes were detected for this component.
1>Done building project "App.csproj" -- FAILED.
========== Build: 0 succeeded, 1 failed, 1 up-to-date, 0 skipped ==========
roman380 commented 4 years ago

@AaronRobinsonMSFT can it be because your Visual Studio is started without elevation? This (running VS "as administrator") might be necessary for Lib.dll to get registered and then to look registered type library up.

BTW you'd probably need to rebuild the solution from the point of the quote above: lib.dll is already up-to-date and won't be built, and. won't be registered. With solution rebuild elevated VS would complete DLL COM registration, then C# app will be built as expected.

AaronRobinsonMSFT commented 4 years ago

can it be because your Visual Studio is started without elevation?

@roman380 Thanks for getting back to me. I launched Visual Studio elevated when I started and it was elevated the entire time. This morning, to make sure I wasn't mistaken, I closed down Visual Studio, relaunched again elevated, and then rebuilt - the same error occurs.

Here is a screenshot:

image

I don't typically do a lot with Visual Studio when authoring COM components so must apologize for my ignorance here. Can you try changing the library GUID on your end to see if it is being reused?

roman380 commented 4 years ago

@AaronRobinsonMSFT a few notes on how to possibly resolve build problem

  1. we are using different VS versions, I have this:

Microsoft Visual Studio Community 2019 Preview Version 16.8.0 Preview 2.0

and you seem to have some (fresher? Internal Preview?) one; this might explain the difference

  1. I assume you have the lib.dll built and it's the other project (app) which triggers a failure. The COM DLL is different from default template project just by a few lines and I expect that DLL is built correctly, it's the COM dependency which is the problem here

  2. Since I assume this is a COM reference problem, this is the easiest way to re-add it.

First, remove existing reference.

image

Then add COM dependency

image

in the opened form click Browse and choose the lib.dll built previously (x64\Release\Lib.dll from the repo root), then select new dependency

image

and select Yes on Copy Local, Embed Interop Types, and Isolated.

I did all this myself before pushing to the repository, re-adding the dependency might just possibly fix the reference. Rebuild the solution.

(LibLib.dll is an interop library created from Lib.dll's COM type library, this explains the name difference).

The error "No registered classes were detected for this component." is suspicious and suggests that type library or interop library is "empty". I don't think this expected behavior for VS for this code and project.