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

[NativeAOT] Document steps for consuming static libraries #77951

Closed
SparkyTD opened this issue Nov 6, 2022 · 10 comments
Closed

[NativeAOT] Document steps for consuming static libraries #77951

SparkyTD opened this issue Nov 6, 2022 · 10 comments
Labels
area-NativeAOT-coreclr documentation Documentation bug or enhancement, does not impact product or test code

Comments

@SparkyTD
Copy link

SparkyTD commented Nov 6, 2022

Description

Static library files compiled and published with NativeAOT are not exporting methods marked with the UnmanagedCallersOnly attribute. Specifying a calling convention doesn't seem to affect this faulty behavior (I've tried cdecl, stdcall and unspecified).

The dynamic dll generator seems to be working correctly, exporting all the necessary functions.

Reproduction Steps

  1. Create a new 'Class Library' project with the net7.0 framework
  2. Create a simple exported method:
    [UnmanagedCallersOnly(EntryPoint = nameof(TestMethod))]
    public static void TestMethod() => Console.Out.WriteLine("Hello from C#");
    
  3. Build the project
  4. Publish the project with the following command:
    dotnet publish -c Release -r win-x64 -p:PublishAot=true -p:NativeLib=Static -p:SelfContained=true
    
  5. Run the dumpbin utility with the /exports argument to inspect the generated .lib file, or try to link the file into a native project (e.g. C++)

Expected behavior

The exported user functions should be listed by the dumpbin utility, along with a built-in function called DotNetRuntimeDebugHeader.

Linking a C++ project against the generated .lib file should allow the user to call the exported managed methods from an unmanaged environment.

Actual behavior

The dumpbin utility doesn't display any exported names whatsoever. See example output here.

Linking against the generated file from a C++ project results in exactly 3203 linker errors, mostly complaining about undefined reference to functions such as RhpAssignRef and RhpNewFast.

cpp_linker_errors.log

Regression?

No response

Known Workarounds

Compile as a shared library (-p:NativeLib=Shared). Not exactly a workaround, but rather an alternative solution.

Configuration

  • Framework: 7.0.100-rc.2.22477.23
  • Operating System: Windows 11 22H2 (OS Build 22623.875)
  • Architecture: win-x64
  • IDE Used: JetBrains Rider 2022.3 EAP 7
  • Installed VS: Visual Studio 2022 with both C++ and C# workloads fully installed

Other information

The first time I tried to compile a .lib file, the compiler correctly generated a static library containing the exported functions. Subsequent attempts to re-publish the library have all failed, even in new projects, resulting in the behavior described above. To my knowledge, no changes have been made to the example code, or the project structure between the first successful and subsequent failed compilations. I have attached the project that I was testing on.

Although I'm using an IDE that is not supported by Microsoft, I do not believe that this is an IDE-specific issue, since it can be reproduced without an IDE, with just a notepad and a command prompt.

NativeAotTest.zip

@ghost ghost added the untriaged New issue has not been triaged by the area owner label Nov 6, 2022
@dotnet-issue-labeler
Copy link

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
Copy link
Member

jkotas commented Nov 6, 2022

This is duplicate of #70277

The exported user functions should be listed by the dumpbin utility

Entrypoints for UnmanagedCallersOnly methods are compiled with publicly visible symbols, but they are not automatically dllexported from the library today. Dllexporting is meant to be done via .def file passed to the linker (it is how NativeLib=Shared does it).

Linking against the generated file from a C++ project results in exactly 3203 linker errors,

These linker errors are caused by missing runtime libraries (Runtime.WorkstationGC.lib, etc.) on the linker command line.

The easiest way to find everything that's required on the linker command line is to run NativeLib=Shared with verbose logging and check the linker command line.

@jkotas jkotas closed this as completed Nov 6, 2022
@ghost ghost removed the untriaged New issue has not been triaged by the area owner label Nov 6, 2022
@SparkyTD
Copy link
Author

SparkyTD commented Nov 6, 2022

@jkotas I apologize for returning to this, but the linked issue seems to be strictly Unix-focused, whereas I am currently trying to get this to work in win-x64.

I find it strange that the generated file doesn't already contain all the necessary framework dependencies, given that I compiled it with -p:SelfContained=true, and it's almost 22 MB in size with effectively a single line of source code that prints "Hello World" from the exported method.

Regardless, looking at the verbose output of the publish tool, I was able to get a list of necessary libraries, and linking then into my C++ test application allowed me to successfully compile an executable. However, running it results in an Access Violation error (0xC0000005) with no further information. As far as I understand, this usually happens when trying to execute code in non-allocated, or non-executable memory.

My test application is quite straightforward:

extern "C" void TestMethod();
int main() {
    TestMethod();
    return 0;
}

I'm not sure how I'm supposed to use the .def file generated by dotnet, since I'm building an executable application, not a shared library.

@jkotas
Copy link
Member

jkotas commented Nov 6, 2022

the linked issue seems to be strictly Unix-focused, whereas I am currently trying to get this to work in win-x64.

The linked issue discusses the details on Unix examples. The shape of the problem and the set of workarounds is same on all platforms.

I find it strange that the generated file doesn't already contain all the necessary framework dependencies,

As #70277 (comment) said, the experience is not fully fleshed out. You can make it work, but it requires some understanding of how the system is put together.

However, running it results in an Access Violation error (0xC0000005) with no further information. As far as I understand, this usually happens when trying to execute code in non-allocated, or non-executable memory.

The most common reason for Access Violation error (0xC0000005) is accessing null pointer in unmanaged code. My guess that it is the case in your case. It is likely caused by missing native aot runtime bootstrapper. Do you have /INCLUDE:NativeAOT_StaticInitialization on your linker command line?

I'm not sure how I'm supposed to use the .def file generated by dotnet

.def file is necessary only if you need to dllexport the UnmanagedCallersOnly entrypoints. It is not necessary for building ordinary .exe that typically does not have any entrypoints dllexported.

@SparkyTD
Copy link
Author

SparkyTD commented Nov 6, 2022

Do you have /INCLUDE:NativeAOT_StaticInitialization on your linker command line?

I do now, and everything works! 😊

Thank you, and sorry again for bugging you with this.

I'm not sure who maintains the github documentations, but it might be worth mentioning these workarounds on the following page: https://github.com/dotnet/samples/tree/main/core/nativeaot/NativeLibrary

@jkotas jkotas changed the title [NativeAOT] Published static library is not exporting managed functions [NativeAOT] Document steps for consuming static libraries Nov 6, 2022
@jkotas jkotas added the documentation Documentation bug or enhancement, does not impact product or test code label Nov 6, 2022
@jkotas
Copy link
Member

jkotas commented Nov 6, 2022

it might be worth mentioning these workarounds on the following page: https://github.com/dotnet/samples/tree/main/core/nativeaot/NativeLibrary

I agree that the sample should have steps to make it actually work. Keeping this issue open to track that.

@jkotas jkotas reopened this Nov 6, 2022
@ghost ghost added the untriaged New issue has not been triaged by the area owner label Nov 6, 2022
@SparkyTD
Copy link
Author

SparkyTD commented Nov 6, 2022

Proposed modification: SparkyTD/dotnet-samples@32c5d40

@jkotas
Copy link
Member

jkotas commented Nov 6, 2022

Thank you! I have left a few comments. Would you like to submit PR to https://github.com/dotnet/samples repo with the proposed change?

@SparkyTD
Copy link
Author

SparkyTD commented Nov 6, 2022

@jkotas I submitted a PR with your suggested changes applied. dotnet/samples#5438

@jkotas
Copy link
Member

jkotas commented Nov 7, 2022

Fixed by dotnet/samples#5438

@jkotas jkotas closed this as completed Nov 7, 2022
@ghost ghost removed the untriaged New issue has not been triaged by the area owner label Nov 7, 2022
@ghost ghost locked as resolved and limited conversation to collaborators Dec 7, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-NativeAOT-coreclr documentation Documentation bug or enhancement, does not impact product or test code
Projects
None yet
Development

No branches or pull requests

2 participants