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

Use AppContext.BaseDirectory #993

Merged
merged 2 commits into from
Jul 26, 2022
Merged

Conversation

kant2002
Copy link
Contributor

@kant2002 kant2002 commented Sep 15, 2021

instead of Assembly.GetExecutingAssembly().Location.
I change this pattern in tests too, in case somebody would like to execute them after trimming or AOT context
Related #992

@manodasanW
Copy link
Member

/azp run

@azure-pipelines
Copy link

No pipelines are associated with this pull request.

@manodasanW
Copy link
Member

@kant2002 Thanks for addressing this. I ran the pipeline and it looks like the following test failed. Can you check if it passes for you or not? Might be related to the trailing slash when appending filename.

[xUnit.net 00:00:10.33] UnitTest.TestCSharp.TestStorageFile [FAIL]
Failed UnitTest.TestCSharp.TestStorageFile [32 ms]
Error Message:
System.AggregateException : One or more errors occurred. (Value does not fall within the expected range.)
---- System.ArgumentException : Value does not fall within the expected range.
Stack Trace:
at System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions) in System.Private.CoreLib.dll:token 0x6002dd6+0x11
at System.Threading.Tasks.Task.Wait(Int32 millisecondsTimeout, CancellationToken cancellationToken) in System.Private.CoreLib.dll:token 0x6002df2+0x3f
at System.Threading.Tasks.Task.Wait(Int32 millisecondsTimeout) in System.Private.CoreLib.dll:token 0x6002df1+0x0
at UnitTest.TestCSharp.TestStorageFile() in D:\a\1\s\src\Tests\UnitTest\TestComponentCSharp_Tests.cs:line 540
----- Inner Stack Trace -----
at UnitTest.TestCSharp.TestStorageFileAsync() in D:\a\1\s\src\Tests\UnitTest\TestComponentCSharp_Tests.cs:line 532

@@ -528,7 +528,7 @@ public void TestBuffer()

async Task TestStorageFileAsync()
{
var folderPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
var folderPath = AppContext.BaseDirectory;
StorageFile file = await StorageFile.GetFileFromPathAsync(folderPath + "\\UnitTest.dll");
Copy link
Member

Choose a reason for hiding this comment

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

This is the problematic line the AppContext.BaseDirectory already contains a forward slash at the end of the path, for example cswinrt\src\Tests\unittest\bin\x64\Release\net6.0\
Changing the code to

StorageFile file = await StorageFile.GetFileFromPathAsync($"{folderPath}UnitTest.dll");

worked for me.
Having said that I think we might need something to test even this same test storage test in single file mode. I think files like the Benchmarks.manifest will not be bundled and getting the base directory and then looking for the file will work. But I'm not completely sure about UnitTest.dll if it gets bundled along with the other dlls you might not find it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Does UnitTest.dll used just because it happens to exists in the build of the test project? Maybe test can have some non-executable file which will be read. Or this code tests that executable file can be read?

@@ -107,7 +107,7 @@ internal class DllModule
readonly DllGetActivationFactory _GetActivationFactory;
readonly DllCanUnloadNow _CanUnloadNow; // TODO: Eventually periodically call

static readonly string _currentModuleDirectory = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
static readonly string _currentModuleDirectory = AppContext.BaseDirectory;
Copy link
Member

Choose a reason for hiding this comment

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

I'm not fully sure if this is the right fix, in single-file mode the AppContext.BaseDirectory will point to the location of the single-file executable. Depending on how the _currentModuleDirectory is used later, the code could break which is why I opened the issue. If someone uses the _currentModuleDirectory to find stuff that got bundled in the exe that is going to break
cc @agocke

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The only usage for that variable is here

// Explicitly look for module in the same directory as this one, and
// use altered search path to ensure any dependencies in the same directory are found.
var moduleHandle = Platform.LoadLibraryExW(System.IO.Path.Combine(_currentModuleDirectory, fileName), IntPtr.Zero, /* LOAD_WITH_ALTERED_SEARCH_PATH */ 8);

Which is just used to get DllGetActivationFactory from DLL. That's for fallback, if RoGetActivationFactory cannot find factory for the class. And you are prbably right, I'm too fast to apply fix here.

Do I properly understand that DllGetActivationFactory is created in DLL by WinRT-component vendor. In case if CsWinRT was used it is comes from here

// Assumes the managed assembly to load and its runtime configuration file are next to the host
wchar_t buffer[MAX_PATH];
auto size = ::GetModuleFileName((HINSTANCE)&__ImageBase, buffer, _countof(buffer));
std::filesystem::path host_module(buffer);

Short glance at the code indicates that it does not take into account single-file deployment at all. Do I see things correctly?

Copy link
Member

Choose a reason for hiding this comment

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

At least the assumption that managed assemblies and the runtime configuration will be next to the host is not true per the documentation https://docs.microsoft.com/en-us/dotnet/core/deploying/single-file they are both going to be bundled into the single file.

Copy link
Member

Choose a reason for hiding this comment

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

My understanding is that the code is loading a native dll from that path. Native dlls don't get bundled by default, so using the BaseDirectory will work in that case. Unfortunately if somebody specifies IncludeNativeLibrariesForSelfExtract the native dll will be bundled and then extracted into a "Temp" directory. But currently we don't provide a good way to find that directory - see dotnet/runtime#43010.

I think one of the native library APIs should be able to try to load the library by a simple name (the same way DllImport does), which will find the library always, since the host correctly sets up NATIVE_DLL_SEARCH_DIRECTORIES. @elinor-fung Do you know which of the NativeLibrary APIs (if any) will trigger the normal runtime's "load library" behavior.

Copy link
Member

@elinor-fung elinor-fung Sep 23, 2021

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I probably misled people a bit here by not showing subsequent code, with assumption that it would be inspected. See below.

// Explicitly look for module in the same directory as this one, and
// use altered search path to ensure any dependencies in the same directory are found.
var moduleHandle = Platform.LoadLibraryExW(System.IO.Path.Combine(_currentModuleDirectory, fileName), IntPtr.Zero, /* LOAD_WITH_ALTERED_SEARCH_PATH */ 8);
#if !NETSTANDARD2_0 && !NETCOREAPP2_0
if (moduleHandle == IntPtr.Zero)
{
NativeLibrary.TryLoad(fileName, Assembly.GetExecutingAssembly(), null, out moduleHandle);
}
#endif

shows that Platform.LoadLibraryExW used primarily for NETSTANDARD2.0 or NETCOREAPP2.0, when on more modern platforms subsequently used NativeLibrary.TryLoad which is essentially same as what @elinor-fung proposed. If dllImportSearchPath == null trigger use NATIVE_DLL_SEARCH_DIRECTORIES then probably that's enough.

Only case which I think is left, is when somehow somebody would like to have "out-of-process" hosting with single-file package. That's WinRT.Host.cpp file is for which I mention in previous comments. Is this supported scenario?

Copy link
Member

Choose a reason for hiding this comment

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

Single-file is only supported for applications (exe), I would not expect out-of-proc hosted components to be built as applications (similar to COM host components, those are also classlib projects not apps). So that should already disqualify almost all such cases.

On top of that, hosting APIs (hostfxr) don't support single-file in any form currently, so even if the SDK would somehow build it, it won't run.

@kant2002
Copy link
Contributor Author

@tlakollo do you have any other concerns? It seems that everything was sorted out in the end. Isn't it?

@tlakollo
Copy link
Member

From what I read the scenario that uses Assembly.Location is not supported in single-file mode, so I'm fine with either the current change just run the CI and merge or leave the old code and just suppress the warning with the justification that mentions the code won't reach that path in single-file mode. Let me see if I have permission to run the pipeline which I doubt

@tlakollo
Copy link
Member

/azp run

@azure-pipelines
Copy link

Commenter does not have sufficient privileges for PR 993 in repo microsoft/CsWinRT

@tlakollo
Copy link
Member

@manodasanW can you trigger the pipeline run to verify the change doesn't break anything?

@manodasanW
Copy link
Member

/azp run

@microsoft microsoft deleted a comment from azure-pipelines bot Sep 28, 2021
@manodasanW
Copy link
Member

Looks like all tests passed, but none of the tests I believe today exercise single file mode. We should try out a CsWinRT projection consumption scenario with single file mode and ensure that it works with these changes. @angelazhangmsft can you help out here trying this build with one of our console app samples?

@kant2002
Copy link
Contributor Author

kant2002 commented Sep 28, 2021 via email

@manodasanW
Copy link
Member

@kant2002 I agree we should add some single file test scenarios to validate this. From a quick look, it wasn't clear to me if we would be able to get our MSTest tests to run also in single file mode. But we can probably have a console app consuming a CsWinRT test projection that publishes to single file and then run it in our test pass as a form of validating it. Thoughts or any other suggestions?

@angelazhangmsft
Copy link
Contributor

Looks like all tests passed, but none of the tests I believe today exercise single file mode. We should try out a CsWinRT projection consumption scenario with single file mode and ensure that it works with these changes. @angelazhangmsft Angela Zhang (SHE/HER) FTE can you help out here trying this build with one of our console app samples?

Looks like the changes here enable CsWinRT projection consumption scenario with single file mode. We should address #999 before merging the PR

instead of Assembly.GetExecutingAssembly().Location.

Related microsoft#992
@manodasanW
Copy link
Member

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@manodasanW manodasanW merged commit 06b377d into microsoft:master Jul 26, 2022
@kant2002 kant2002 deleted the kant/issue-922 branch July 26, 2022 05:32
@kant2002
Copy link
Contributor Author

Thanks you!

manodasanW added a commit that referenced this pull request Jul 29, 2022
instead of Assembly.GetExecutingAssembly().Location.

Related #992

Co-authored-by: Manodasan Wignarajah <[email protected]>
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.

7 participants