Skip to content

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

@SteveSandersonMS

Description

@SteveSandersonMS

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.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions