dotnet / corert

This repo contains CoreRT, an experimental .NET Core runtime optimized for AOT (ahead of time compilation) scenarios, with the accompanying compiler toolchain.
http://dot.net
MIT License
2.91k stars 508 forks source link

Multiple csproj projects referenced and using NativeCallable #7215

Open philcarbone opened 5 years ago

philcarbone commented 5 years ago

I can't seem to get multiple project using NativeCallableAttribute to expose these functions. Only the project that has the references (the project being compiled) has it's functions exposed as native callable functions.

Project A has a native function 'add':

[NativeCallable (EntryPoint = "add", CallingConvention = CallingConvention.StdCall)]
public static int Add (int a, int b) {
    return a + b;
}

Project B has a native function 'subtract':

[NativeCallable (EntryPoint = "subtract", CallingConvention = CallingConvention.StdCall)]
public static int Subtract (int a, int b) {
    return a - b;
}
  1. Project A has a project reference to Project B
  2. Project A is compiled
  3. I can only access Project A's 'add' function, the 'subtract' function is not available.

More Notes

Project A's nuget.config

<?xml version="1.0" encoding="utf-8"?>
<configuration>
 <packageSources>
    <clear />
    <add key="dotnet-core" value="https://dotnet.myget.org/F/dotnet-core/api/v3/index.json" />
    <add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
 </packageSources>
</configuration>

Project A's csproj file:

<Project Sdk="Microsoft.NET.Sdk">
    <PropertyGroup>
        <OutputType>Library</OutputType>
            <TargetFramework>netcoreapp3.0</TargetFramework>
            <RuntimeIdentifiers>win-x64;osx-x64;linux-x64</RuntimeIdentifiers>
            <NativeLib>Static</NativeLib>
            <RootNamespace>HostA</RootNamespace>
            <LangVersion>8.0</LangVersion>
            <NullableContextOptions>enable</NullableContextOptions>
    </PropertyGroup>
    <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
            <LangVersion>8.0</LangVersion>
            <NullableContextOptions>enable</NullableContextOptions>
    </PropertyGroup>
    <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
            <LangVersion>8.0</LangVersion>
            <NullableContextOptions>enable</NullableContextOptions>
    </PropertyGroup>
    <ItemGroup>
            <PackageReference Include="Microsoft.DotNet.ILCompiler" Version="1.0.0-alpha-*" />
            <PackageReference Include="Microsoft.Extensions.Configuration" Version="2.2.0" />
            <PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="2.2.0" />
            <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.2.0" />
            <PackageReference Include="System.Runtime.InteropServices" Version="4.3.*" />
    </ItemGroup>
    <ItemGroup>
        <ProjectReference Include="..\ProjectB\ProjectB.csproj" />
    </ItemGroup>
    <ItemGroup>
            <RdXmlFile Include="rd.xml" />
            <IlcArg Include="--stacktracedata" />
    </ItemGroup>
</Project>

Command used to build:

dotnet publish /p:NativeLib=Shared -r win-x64 -c release
MichalStrehovsky commented 5 years ago

We currently only look for native callable methods in the root assembly (the assembly that is getting published).

I'm not sure extending this to look at all assemblies would result in a good experience. E.g. what if there's a conflicting name of the export between two assemblies and both of them come from e.g. two different NuGets that are not under your direct control? Or what if the NuGet has a NativeCallable method you don't actually want to export?

Could you describe your scenario a bit?

jkotas commented 5 years ago

You can root more assemblies by including them in IlcCompileInput. For example, the following should work for your example above:

<ItemGroup>
    <ProjectReference Include="..\ProjectB\ProjectB.csproj" />
    <IlcCompileInput Include="$([System.IO.Path]::GetFullPath(..\ProjectB\bin\$(Configuration)\$(TargetFramework)\ProjectB.dll))" />
</ItemGroup>

It would be nice to have more first class way to do this.

philcarbone commented 5 years ago

@MichalStrehovsky I'm actually creating a Library to make native code more approachable. The goals are:

So, this project is already off the ground and doing well. I have a steel-thread that I have used in the ways described above. I'm now refactoring this into something more robust and moving the functionality around so it may be leveraged through a library. Unfortunately, I ran into this limitation :(

With AOT compilation creating a single assembly, it masks away the library's functions (of which I've named in such a way as to avoid naming conflicts). I would assume that if someone implemented a function with the same name that it would take precedence over the third-party library (my library). Or maybe there is a configuration that the implementor sets to allow the functions to pass through.

Thoughts?

philcarbone commented 5 years ago

You can root more assemblies by including them in IlcCompileInput. For example, the following should work for your example above:

<ItemGroup>
    <ProjectReference Include="..\ProjectB\ProjectB.csproj" />
    <IlcCompileInput Include="$([System.IO.Path]::GetFullPath(..\ProjectB\bin\$(Configuration)\$(TargetFramework)\ProjectB.dll))" />
</ItemGroup>

It would be nice to have more first class way to do this.

Thanks @jkotas ! I'm glad there is at least a way to do this!!! Ideally, a first-class way would be better, but I'll take any way right now too :)

philcarbone commented 5 years ago

Hi Again! (@MichalStrehovsky & @jkotas)

Now I've hit a new issue. I can't iterate over all of the input assemblies at run-time, from the published corert assembly. Is this currently possible? I'm trying to use this to discover the other methods that I'm extracting metadata for (reflection).

All of the assemblies are added to the rd.xml file:

<Directives xmlns="http://schemas.microsoft.com/netfx/2013/01/metadata"> 
    <Application>
        <Assembly Name="ProjectA" Dynamic="Required All" DataContractSerializer="All"/>     
    <Assembly Name="ProjectB" Dynamic="Required All" DataContractSerializer="All"/>     
    </Application> 
</Directives>
MichalStrehovsky commented 5 years ago

Try AppDomain.CurrentDomain.GetAssemblies().