microsoft / node-api-dotnet

Advanced interoperability between .NET and JavaScript in the same process.
MIT License
426 stars 49 forks source link

JSMarshallerException error occurs while generating System.Drawing.RectangleF GetRectangleF() method #280

Closed alexeybut closed 2 months ago

alexeybut commented 2 months ago

Hello, The next error occurs while generating node module for the sample code. The code:

    [JSExport]
    public class Document
    {
        public RectangleF GetRectangleF() => throw new NotImplementedException();
    }

The error:

CSC : error NAPI1001: JSMarshallerException : Failed to build JS callback adapter expression for .NET method. Type: Console1.Document, Member: System.Drawing.RectangleF GetRectangleF()   at Microsoft.JavaScript.NodeApi.DotNetHost.JSMarshaller.BuildFromJSMethodExpression(MethodInfo method, Boolean asExtensionMethod)   at Microsoft.JavaScript.NodeApi.Generator.ModuleGenerator.ExportMethod(SourceBuilder& s, IEnumerable`1 methods, String exportName)   at Microsoft.JavaScript.NodeApi.Generator.ModuleGenerator.ExportMembers(SourceBuilder& s, ITypeSymbol type)   at Microsoft.JavaScript.NodeApi.Generator.ModuleGenerator.ExportType(SourceBuilder& s, ITypeSymbol type, String exportName)   at Microsoft.JavaScript.NodeApi.Generator.ModuleGenerator.ExportModule(SourceBuilder& s, ITypeSymbol moduleType, IEnumerable`1 exportItems)   at Microsoft.JavaScript.NodeApi.Generator.ModuleGenerator.GenerateModuleInitializer(ISymbol moduleInitializer, IEnumerable`1 exportItems)   at Microsoft.JavaScript.NodeApi.Generator.ModuleGenerator.Execute(GeneratorExecutionContext context) [ConsoleApp1.csproj]
CSC : error NAPI1001: JSMarshallerException : Failed to build expression for conversion to JS value. Type: System.Drawing.RectangleF   at Microsoft.JavaScript.NodeApi.DotNetHost.JSMarshaller.GetToJSValueExpression(Type fromType)   at Microsoft.JavaScript.NodeApi.DotNetHost.JSMarshaller.BuildResultExpression(Expression resultVariable, Type resultType)   at Microsoft.JavaScript.NodeApi.DotNetHost.JSMarshaller.BuildFromJSInstanceMethodExpression(MethodInfo method)   at Microsoft.JavaScript.NodeApi.DotNetHost.JSMarshaller.BuildFromJSMethodExpression(MethodInfo method, Boolean asExtensionMethod) [ConsoleApp1.csproj]
CSC : error NAPI1001: TypeLoadException : Could not load type 'System.Drawing.PointF' from assembly 'Microsoft.JavaScript.NodeApi.Generator.SymbolExtensions_1, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'.   at System.Signature.GetSignature(Void* pCorSig, Int32 cCorSig, RuntimeFieldHandleInternal fieldHandle, IRuntimeMethodInfo methodHandle, RuntimeType declaringType)   at System.Reflection.RuntimeMethodInfo.<get_Signature>g__LazyCreateSignature|25_0()   at System.Reflection.RuntimeMethodInfo.FetchNonReturnParameters()   at System.Reflection.RuntimeMethodInfo.GetParameters()   at Microsoft.JavaScript.NodeApi.DotNetHost.JSMarshaller.BuildToJSFromStructExpressions(Type fromType, List`1 variables, Expression valueExpression)+MoveNext()   at System.Collections.Generic.LargeArrayBuilder`1.AddRange(IEnumerable`1 items)   at System.Collections.Generic.EnumerableHelpers.ToArray[T](IEnumerable`1 source)   at System.Dynamic.Utils.CollectionExtensions.ToReadOnly[T](IEnumerable`1 enumerable)   at System.Linq.Expressions.Expression.Block(Type type, IEnumerable`1 variables, IEnumerable`1 expressions)   at Microsoft.JavaScript.NodeApi.DotNetHost.JSMarshaller.BuildConvertToJSValueExpression(Type fromType) [ConsoleApp1.csproj]

Version: 0.7.5 Command:

dotnet build-server shutdown
dotnet publish -c Release -r win-x64 -f net8.0

AOT is enabled.

jasongin commented 2 months ago

PR #281 fixes this exception at build time.

However, this is still not going to work at runtime. When trying to access a property or method that uses a type from another assembly (like System.Drawing in this example) you will get an exception like this:

Error: Struct not registered for JS export: System.Drawing.RectangleF

This is because there is a limitation that any exported types must be defined either in the code of the module project or in one of a specific set of core assemblies (currently System.Runtime, System.Numerics, and Microsoft.JavaScript.NodeApi). At the moment this is sort of by design, though it is not documented very well. Actually I think the module generator should report an error when this limitation is violated, though it doesn't now.

WIth more work it might be possible for the source-generator to automatically export referenced types from other assemblies, but that could get messy when those other types are not specifically designed for export to JS.

jasongin commented 2 months ago

the module generator should report an error when this limitation is violated, though it doesn't now.

I will implement this build error in another PR.

alexey-butalov-aspose commented 2 months ago

@jasongin Is it possible to ignore the exported types? Currently all exported types are processed (with dependency tree). It takes too much times. Very often the process hangs, gets stick in a loop or specific errors occur while analyzing these types. Please see the log of processing one class with one method: log.txt For a real project this log can take about 1GB.

jasongin commented 2 months ago

It's not clear what you mean by "ignore the exported types".

For items which are exported, it has to follow the complete dependency tree of all referenced types before it can start generating the exports and marshalling code. If that's slow, I think there is potential for optimization by avoiding some repeated work. If it hangs or gets stuck in a loop, that's a bug that needs to be fixed.

alexey-butalov-aspose commented 2 months ago

@jasongin It's hard to detect hangs or infinity loops because it happens while analyzing referenced libraries. Perhaps I'll try to modify SymbolExtansions.AsType method to ignore such external types.

jasongin commented 2 months ago

Oh, did you mean to suggest "ignore non-exported types"?

Currently the code in SymbolExtensions does not check anything about what types/members are exported or not with [JSExport], but maybe it could. There are potential problems with that though, for example if a class implements an interface but one of the interface members is not exported, then constructing the Type will fail since the interface implementation is incomplete.

jasongin commented 2 months ago

From the log I see you're working with SkiaSharp. I can try setting up an example project with that library and see if I encounter any issues.

alexey-butalov-aspose commented 2 months ago

For example the next code fails the generator:

namespace ConsoleApp1
{
    [JSExport]
    public class Class4
    {
        public void Do3(SKRect c)
        {
        }

        public void Do3(SKBitmap c)
        {
        }
    }
}

I'm trying to find out simple issues first. Maybe infinity loops will be fixed too.

jasongin commented 2 months ago

For example the next code fails the generator:

namespace ConsoleApp1 ...

I don't get any build error with that code using the latest version (0.7.7) of the Microsoft.JavaScript.NodeApi.Generator package.

However the types from the SkiSharp assembly are not going to be usable in JS at runtime due to the limitation I descirbed above. I don't know what you're trying to build with SkiaSharp, but I guess that might be a blocking issue for your scenario.