Skip to content

[NativeAOT] Decorating functions with UnmanagedCallersOnlyAttribute does not export them for executables #78663

Closed

Description

@JamesMenetrey

Introduction

NativeAOT conveniently enables Class Library projects to export native symbols in the export table of the PE, typically functions, which is an excellent asset for interoperability with unmanaged software.

In native applications (e.g., written in C/C++), using the directive extern __declspec(dllexport) similarly creates an entry in the export table for the given function. This capability works for both unmanaged libraries (.dll) and unmanaged executables (.exe).

Currently, when decorating a function with the attribute UnmanagedCallersOnlyAttrribute does not insert a symbol in the export table when the managed project is an application (.exe). Copy/paste the definition of the same function in a project of type Class Library works.

Motivation

I'm looking forward to similarly exporting functions in the export table of my managed applications (.exe) for interoperability reasons.
Admittedly, the use case of exporting functions for executables is small compared to libraries. Nonetheless, I find this is a valuable capability on my end, where I mainly work on low-level forms of interoperability (i.e., creating threads in other processes for code execution).

Furthermore, I would expect the compiler to honor the attribute UnmanagedCallersOnlyAttribute for exporting the function in the export table since both libraries (.dll) and executables (.exe) use the PE file format, which enables the export of symbol of functions.

Bug report

Version Used: Retail .NET SDK 7.0.100 with Visual Studio 17.4.0

Steps to Reproduce:

  1. Create a Console Application application using the default template in Visual Studio 2022.
  2. Write a function decorated with the attribute, e.g.:
[UnmanagedCallersOnly(EntryPoint = "FancyName2")]
public static int FancyName2(int a, int b)
{
    return a + b;
}
  1. Compile with the application for win-x64 using NativeAOT.
  2. Inspect the export table of the resulting executable, for example, using CFF Explorer.

Expected Behavior:

The export table of the PE format shows the FancyName2 function.

Actual Behavior

The function FancyName2 is not present. Note that performing the same operation for a project of type Class Library correctly exports the function.

Additional details

Inspecting a running application compiled using NativeAOT, I could notice various symbols present in the memory of the process. As an example, the picture below highlights a static function called FancyName2 in the class Program, from a Windows Forms application.

image

Creating a thread at this address properly invokes the managed function. Hence, I think tagging this location in the export table using the same logic as for managed libraries should be doable.

Activity

  1. ghost added
    untriagedNew issue has not been triaged by the area owner
    on Nov 21, 2022
  2. transferred this issue fromdotnet/roslynon Nov 21, 2022
  3. added this to the Future milestone on Nov 22, 2022
  4. removed
    untriagedNew issue has not been triaged by the area owner
    on Nov 22, 2022
  5. agocke commented on Nov 22, 2022

    @agocke
    Member

    Seems reasonable, but a little niche. Would take a PR if there were one ready.

  6. jkotas commented on Nov 22, 2022

    @jkotas
    Member

    I agree that this is niche. You can work around this by passing the .def file with the list of entrypoints to the linker yourself.

    I expect that most uses of UnmanagedCallersOnlyAttribute for interopability would not want the entrypoints to be automatically exported in .exe. If the option for dllexporting is added, it should be off by default.

  7. MichalStrehovsky commented on Nov 22, 2022

    @MichalStrehovsky
    Member

    I expect that most uses of UnmanagedCallersOnlyAttribute for interopability would not want the entrypoints to be automatically exported in .exe. If the option for dllexporting is added, it should be off by default.

    I think the request is to only expose those that specify the EntryPoint property - the uses for interoperability wouldn't specify an EntryPoint.

    @JamesMenetrey you can already do this today if you add:

        <ExportsFile>$(MSBuildThisFileDirectory)exports.def</ExportsFile>
    

    To a PropertyGroup in your project file. It's a bit of a backhanded way to exploit the way things are set up in the targets.

    The actual product change to fix this in a more supportable way would be a change to the targets in https://github.com/dotnet/runtime/tree/main/src/coreclr/nativeaot/BuildIntegration.

  8. jkotas commented on Nov 22, 2022

    @jkotas
    Member

    the uses for interoperability wouldn't specify an EntryPoint.

    Consider statically linking some extra C code into the exe and calling between C# and C in both directions.

  9. added a commit that references this issue on Nov 22, 2022
    228775c
  10. JamesMenetrey commented on Nov 22, 2022

    @JamesMenetrey
    ContributorAuthor

    Dear all,

    First, thanks for your detailed input and insight that allowed me to understand the underlying mechanism works better.

    @MichalStrehovsky Many thanks for pointing me to a working workaround. Adding this line in the project file correctly exports the symbols as annotated with UnmanagedCallersOnlyAttribute for executables.

    I have reviewed the many files in the link you provided to me and created a PR fixing the issue (#78738).

    After further testing with both a .NET library and two .NET executables (a console and Windows Forms application), I noticed that decorating a function with UnmanagedCallersOnlyAttribute without specifying the property EntryPoint does not export the symbol. This behavior goes in the direction of @jkotas, who stated that the usage of UnmanagedCallersOnlyAttribute should not export that symbol by default.

    Filling the property EntryPoint adds a symbol for both libraries and executables once the PR is applied (tested on my Windows machine).

    Could you please review my PR (#78738) and provide feedback if this change is sound?

    Thanks!

  11. ghost locked as resolved and limited conversation to collaborators on Dec 24, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    • Status
      No status

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions