Skip to content
This repository has been archived by the owner on Mar 14, 2024. It is now read-only.

Custom marshal attribute #25

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
{
// Don't show the "Required assets are missing..." dialog on startup. We'll manage tasks.json / launch.json manually.
"csharp.suppressBuildAssetsNotification": true
"csharp.suppressBuildAssetsNotification": true,

// We try to limit the length of code lines to 100 columns.
"editor.rulers": [
100,
]
}
14 changes: 14 additions & 0 deletions Runtime/IJSMarshaler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@

namespace NodeApi;

/// <summary>
/// Interface for a custom implementation of marshaling .NET types to and from JavaScript values.
/// A type that implements this interface can be assigned to
/// <see cref="JSMarshalAsAttribute.CustomMarshalType" />.
/// </summary>
public interface IJSMarshaler<T>
{
JSValue MarshalToJS(T value);

T MarshalFromJS(JSValue value);
}
49 changes: 49 additions & 0 deletions Runtime/JSMarshal.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@

namespace NodeApi;

/// <summary>
/// Specifies marshalling behavior for <see cref="JSMarshalAsAttribute" />.
/// </summary>
public enum JSMarshal
{
/// <summary>
/// Default marshalling behavior.
/// </summary>
Default,

/// <summary>
/// Marshalling is handled by a custom marshaler type. When this is specified,
/// <see cref="JSMarshalAsAttribute.CustomMarshalType" /> must also be set.
/// </summary>
CustomMarshaler,

/// <summary>
/// A .NET object is marshalled as a JS external value. External values may not
/// be accessed by JS, but may be held and passed back to .NET code intact.
/// </summary>
ExternalObject,

/// <summary>
/// The object is marshalled by value, meaning all public properties are shallow-copied.
/// This is the default marshalling behavior for .NET structs.
/// </summary>
ByValObject,

/// <summary>
/// The object is marshalled by reference, meaning only a proxy to the object is passed.
/// This is the default marshalling behavior for .NET classes and interfaces.
/// </summary>
ByRefObject,

/// <summary>
/// Collection items are copied into a new collection in the target environment.
/// This is the default marshalling behavior for .NET arrays.
/// </summary>
ByValCollection = ByValObject,

/// <summary>
/// The collection is marshalled by reference, meaning only a proxy to the collection is passed.
/// This is the default marshalling behavior for .NET generic collections.
/// </summary>
ByRefCollection = ByRefObject,
}
37 changes: 37 additions & 0 deletions Runtime/JSMarshalAsAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@

using System;

namespace NodeApi;

/// <summary>
/// Specifies how .NET types are marshalled to and from JavaScript values.
/// </summary>
[AttributeUsage(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AttributeUsage

Should we also add fields?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Later, if and when we support marshalling fields.

Fields are not supported now, but there's no reason they can't be marshalled in almost the same way as properties, along with an optimization for const fields. I don't think it's a priority now though.

AttributeTargets.Property |
AttributeTargets.Parameter |
AttributeTargets.ReturnValue)]
public sealed class JSMarshalAsAttribute : Attribute
{
/// <summary>
/// Specifies how a .NET property, parameter, or return value is marshalled to
/// and from a JavaScript value.
/// </summary>
/// <param name="marshal">One of the available marshaling options.</param>
public JSMarshalAsAttribute(JSMarshal marshal)
{
Value = marshal;
}

/// <summary>
/// Gets the marshaling behavior indicated by the attribute.
/// </summary>
public JSMarshal Value { get; }

/// <summary>
/// When <see cref="Value" /> is <see cref="JSMarshal.CustomMarshaler" />, gets the type
/// that handles marshalling. The type must implement <see cref="IJSMarshaler{T}" />,
/// where the type argument matches the type being of the property, parameter, or
/// return value the <see cref="JSMarshalAsAttribute" /> is applied to.
/// </summary>
public Type? CustomMarshalType { get; init; }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Type?

I wonder if we can use the new generic attributes instead. In that case the restriction that the type must inherit from IJSMarshaler can be written as an explicit restriction checked by the C# compiler.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought about that, but we'd still need a non-generic JSMarshalAsAttribute to support uses that don't specify a custom marshaller type. So I didn't think it was worth creating another attribute.

}