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

Wasm native dependencies: [DllImport] in own source code doesn't work #59255

Closed
SteveSandersonMS opened this issue Sep 17, 2021 · 1 comment
Closed
Assignees
Labels
arch-wasm WebAssembly architecture area-Interop-mono
Milestone

Comments

@SteveSandersonMS
Copy link
Member

SteveSandersonMS commented Sep 17, 2021

Thanks @radical for sorting out the native dependencies feature in time for RC2!

I've been trying it out today, and it works great if you're consuming an existing .dll that contains a [DllImport]. However, it doesn't seem to work if your own application source code contains the [DllImport]. I think I've tracked this down to an issue in the MSBuild logic:

  • When calling PInvokeTableGenerator, it passes the list of assemblies to scan by supplying _WasmAssembliesInternal
  • However, at this point in time, _WasmAssembliesInternal does not contain the main application assembly. Maybe it also lacks any other referenced class libraries in your solution, but I haven't checked that.
    • _WasmAssembliesInternal is populated by copying the contents of WasmAssembliesToBundle, which in turn is populated during the target _GatherWasmFilesToBuild. The source code for this does try to add @(MainAssembly), but it doesn't get added to the itemgroup. Maybe that's because it doesn't exist at the time, or maybe it doesn't match the metadata condition or something - I'm not sure.
  • Anyway, since the assemblies list passed to PInvokeTableGenerator doesn't contain YourApp.dll, it won't include any of its dllimports in pinvoke-table.h, and hence at runtime, attempts to call those dllimports will fail with Entrypoint not found.

I was able to confirm this is the only problem by adding an MSBuild hack that would add my application assembly to WasmAssembliesToBundle immediately after _GatherWasmFilesToBuild. Then I could successfully call my DllImport - details below.

Assuming I've got all this right, the impact of this issue is that people can't really work incrementally on applications that contain native dependencies, since you can only call into prebuilt assemblies that have [DllImport] - you can't just call imports that are only in your own source code.

Repro steps

Starting from @radical's blazor-native sample project, I changed <RunAOTCompilation>true</RunAOTCompilation> to <WasmBuildNative>true</WasmBuildNative>. Then I added the file mylib.cpp in the project root, containing:

#include <stdio.h>

extern "C" {
    int cpp_add(int a, int b) {
        return a + b;
    }
}

Then in the .csproj, added:

<NativeFileReference Include="mylib.cpp" />

Next, in a new file MyDllImports.cs file, added:

using System.Runtime.InteropServices;

namespace blazor_native;

public static class MyDllImports
{
    [DllImport("mylib")]
    public static extern int cpp_add(int a, int b);
}

And then in a Blazor event handler, added a call:

Console.WriteLine($"Result: {MyDllImports.cpp_add(123, 456)}");

Expected: Should work
Actual: The method call fails with Entrypoint not found

Also if you check pinvoke-table.h, it contains:

static PinvokeImport mylib_imports [] = {
{NULL, NULL}
};

... so it knows we're trying to do something, but has missed out the import itself.

As a workaround, I added the following to my csproj:

  <Target Name="FixWasmAssembliesToBundle" AfterTargets="_GatherWasmFilesToBuild">
    <ItemGroup>      
      <WasmAssembliesToBundle Include="C:\some\path\blazor-native\obj\Debug\net6.0\blazor-native.dll" />
    </ItemGroup>
  </Target>

... and now on build, pinvoke-table.h contains:

static PinvokeImport mylib_imports [] = {
{"cpp_add", cpp_add}, // blazor-native
{NULL, NULL}
};

... and at runtime my invocation of MyDllImports.cpp_add does actually work. Of course, this is not a good workaround that we would recommend to customers!

So, it seems we're very close to having this working end-to-end, but one possibly-small issue in the MSBuild means it doesn't quite.

@SteveSandersonMS SteveSandersonMS added the arch-wasm WebAssembly architecture label Sep 17, 2021
@ghost
Copy link

ghost commented Sep 17, 2021

Tagging subscribers to 'arch-wasm': @lewing
See info in area-owners.md if you want to be subscribed.

Issue Details

Thanks @radical for sorting out the native dependencies feature in time for RC2!

I've been trying it out today, and it works great if you're consuming an existing .dll that contains a [DllImport]. However, it doesn't seem to work if your own application source code contains the [DllImport]. I think I've tracked this down to an issue in the MSBuild logic:

  • When calling PInvokeTableGenerator, it passes the list of assemblies to scan by supplying _WasmAssembliesInternal
  • However, at this point in time, _WasmAssembliesInternal does not contain the main application assembly. Maybe it also lacks any other referenced class libraries in your solution, but I haven't checked that.
    • _WasmAssembliesInternal is populated by copying the contents of WasmAssembliesToBundle, which in turn is populated during the target _GatherWasmFilesToBuild. The source code for this does try to add @(MainAssembly), but it doesn't get added to the itemgroup. Maybe that's because it doesn't exist at the time, or maybe it doesn't match the metadata condition or something - I'm not sure.
  • Anyway, since the assemblies list passed to PInvokeTableGenerator doesn't contain YourApp.dll, it won't include any of its dllimports in pinvoke-table.h, and hence at runtime, attempts to call those dllimports will fail with Entrypoint not found.

I was able to confirm this is the only problem by adding an MSBuild hack that would add my application assembly to WasmAssembliesToBundle immediately after _GatherWasmFilesToBuild. Then I could successfully call my DllImport - details below.

Assuming I've got all this right, the impact of this issue is that people can't really work incrementally on applications that contain native dependencies, since you can only call into prebuilt assemblies that have [DllImport] - you can't just call imports that are only in your own source code.

Repro steps

Starting from @radical's blazor-native sample project, I added the file mylib.css in the project root, containing:

#include <stdio.h>

extern "C" {
    int cpp_add(int a, int b) {
        return a + b;
    }
}

Then in the .csproj, added:

<NativeFileReference Include="mylib.cpp" />

Next, in a new file MyDllImports.cs file, added:

using System.Runtime.InteropServices;

namespace blazor_native;

public static class MyDllImports
{
    [DllImport("mylib")]
    public static extern int cpp_add(int a, int b);
}

And then in a Blazor event handler, added a call:

Console.WriteLine($"Result: {MyDllImports.cpp_add(123, 456)}");

Expected: Should work
Actual: The method call fails with Entrypoint not found

Also if you check pinvoke-table.h, it contains:

static PinvokeImport mylib_imports [] = {
{NULL, NULL}
};

... so it knows we're trying to do something, but has missed out the import itself.

As a workaround, I added the following to my csproj:

  <Target Name="FixWasmAssembliesToBundle" AfterTargets="_GatherWasmFilesToBuild">
    <ItemGroup>      
      <WasmAssembliesToBundle Include="C:\some\path\blazor-native\obj\Debug\net6.0\blazor-native.dll" />
    </ItemGroup>
  </Target>

... and now on build, pinvoke-table.h contains:

static PinvokeImport mylib_imports [] = {
{"cpp_add", cpp_add}, // blazor-native
{NULL, NULL}
};

... and at runtime my invocation of MyDllImports.cpp_add does actually work. Of course, this is not a good workaround that we would recommend to customers!

So, it seems we're very close to having this working end-to-end, but one possibly-small issue in the MSBuild means it doesn't quite.

Author: SteveSandersonMS
Assignees: -
Labels:

arch-wasm

Milestone: -

@dotnet-issue-labeler dotnet-issue-labeler bot added area-Interop-coreclr untriaged New issue has not been triaged by the area owner labels Sep 17, 2021
@lewing lewing removed the untriaged New issue has not been triaged by the area owner label Sep 17, 2021
@lewing lewing added this to the 6.0.0 milestone Sep 17, 2021
radical added a commit to radical/sdk that referenced this issue Sep 17, 2021
radical added a commit to radical/runtime that referenced this issue Sep 17, 2021
.. and update BlazorOverwrite.targets to include the fix from
dotnet/sdk#21104 .
lewing pushed a commit to dotnet/sdk that referenced this issue Sep 20, 2021
@lewing lewing closed this as completed Sep 20, 2021
radical added a commit to radical/runtime that referenced this issue Sep 25, 2021
radical added a commit to dotnet/sdk that referenced this issue Sep 28, 2021
@ghost ghost locked as resolved and limited conversation to collaborators Nov 3, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
arch-wasm WebAssembly architecture area-Interop-mono
Projects
None yet
Development

No branches or pull requests

4 participants