Skip to content

"Winapi" support on NS20 and .NET 5 #561

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

Merged
merged 2 commits into from
Aug 7, 2021
Merged
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
2 changes: 2 additions & 0 deletions src/Assimp/Silk.NET.Assimp/Assimp.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Runtime.InteropServices;
using Silk.NET.Core.Contexts;
using Silk.NET.Core.Loader;
using Silk.NET.Core.Native;
Expand All @@ -7,6 +8,7 @@

namespace Silk.NET.Assimp
{
[NativeApi(Convention = CallingConvention.Winapi)]
public partial class Assimp
{
public static Assimp GetApi()
Expand Down
21 changes: 19 additions & 2 deletions src/Core/Silk.NET.Core/Native/SilkMarshal.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using Silk.NET.Core.Loader;

namespace Silk.NET.Core.Native
{
Expand All @@ -19,6 +17,25 @@ namespace Silk.NET.Core.Native
/// </summary>
public static class SilkMarshal
{
/// <summary>
/// Gets a value indicating whether <see cref="CallingConvention.Winapi"/> is equivalent to
/// <see cref="CallingConvention.StdCall"/>.
/// </summary>
/// <remarks>
/// If false, <see cref="CallingConvention.Winapi"/> can be generally assumed to be equivalent to
/// <see cref="CallingConvention.Cdecl"/>.
/// </remarks>
public static readonly bool IsWinapiStdcall;

static SilkMarshal()
{
#if NET5_0
IsWinapiStdcall = OperatingSystem.IsWindows();
#else
IsWinapiStdcall = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
#endif
}

/// <summary>
/// Allocate a new BStr pointer.
/// </summary>
Expand Down
1 change: 1 addition & 0 deletions src/Core/Silk.NET.SilkTouch/MarshalContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,7 @@ public void DeclareExtraRef(int id, int amount = 1)

public BlockSyntax BuildFinalBlock()
{

// add return
if (!ReturnsVoid)
{
Expand Down
227 changes: 183 additions & 44 deletions src/Core/Silk.NET.SilkTouch/NativeApiGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -392,40 +392,66 @@ private static void ProcessMethod
SyntaxList<UsingDirectiveSyntax> generationusings
)
{
void BuildLoadInvoke(ref IMarshalContext ctx, Action next)
{
ctx.TransitionTo(SilkTouchStage.PreLoad);

// this is terminal, we never call next

var parameters = ctx.ResolveAllLoadParameters();

var fPtrType = FunctionPointerType
const string invocationShimName = "StCall";
static FunctionPointerTypeSyntax GetFuncPtrType
(
CallingConvention callingConvention,
ITypeSymbol[] loadTypes
) => FunctionPointerType
(
callingConvention == CallingConvention.Winapi ? FunctionPointerCallingConvention
(
Token(SyntaxKind.UnmanagedKeyword)
) : FunctionPointerCallingConvention
(
FunctionPointerCallingConvention
Token(SyntaxKind.UnmanagedKeyword),
FunctionPointerUnmanagedCallingConventionList
(
Token(SyntaxKind.UnmanagedKeyword),
FunctionPointerUnmanagedCallingConventionList
SingletonSeparatedList
(
SingletonSeparatedList
(
FunctionPointerUnmanagedCallingConvention
(Identifier(GetCallingConvention(callingConvention)))
)
FunctionPointerUnmanagedCallingConvention
(Identifier(GetCallingConvention(callingConvention)))
)
),
FunctionPointerParameterList
)
),
FunctionPointerParameterList
(
SeparatedList
(
SeparatedList
loadTypes.Select
(
ctx.LoadTypes.Select
(
x => FunctionPointerParameter
(IdentifierName(x.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)))
)
x => FunctionPointerParameter
(IdentifierName(x.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)))
)
)
);
)
);

static MemberAccessExpressionSyntax GetFuncPtrExpr
(
string generatedVTableName,
string entryPoint
) => MemberAccessExpression
(
SyntaxKind.SimpleMemberAccessExpression,
ParenthesizedExpression
(
BinaryExpression
(
SyntaxKind.AsExpression, IdentifierName("CurrentVTable"),
IdentifierName(generatedVTableName)
)
), IdentifierName(FirstLetterToUpper(entryPoint))
);

void BuildLoadInvoke(ref IMarshalContext ctx, Action next)
{
ctx.TransitionTo(SilkTouchStage.PreLoad);

// this is terminal, we never call next

var parameters = ctx.ResolveAllLoadParameters();


entryPoints.Add(entryPoint);
processedEntrypoints.Add
Expand All @@ -445,34 +471,45 @@ void BuildLoadInvoke(ref IMarshalContext ctx, Action next)

Func<IMarshalContext, ExpressionSyntax> expression;

var defs = declaration.SyntaxTree.Options.PreprocessorSymbolNames;

// ReSharper disable PossibleMultipleEnumeration - just not an issue
var hasFastWinapi = defs.Contains("NET5_0") ||
defs.Contains("NET6_0") ||
defs.Contains("NET5_0_OR_GREATER"); // newer SDKs (circa .NET 6) have _OR_GREATER
// ReSharper restore PossibleMultipleEnumeration

var needsInvocationShim = callingConvention == CallingConvention.Winapi && !hasFastWinapi;

if ((classIsSealed || generateSeal) && generateVTable)
{
// build load + invocation
expression = ctx => InvocationExpression
(
ParenthesizedExpression
expression = ctx =>
{
var fPtrType = GetFuncPtrType(callingConvention, ctx.LoadTypes);
return InvocationExpression
(
CastExpression
needsInvocationShim ? IdentifierName(invocationShimName) : ParenthesizedExpression
(
fPtrType, MemberAccessExpression
CastExpression
(
SyntaxKind.SimpleMemberAccessExpression,
ParenthesizedExpression
(
BinaryExpression
(
SyntaxKind.AsExpression, IdentifierName("CurrentVTable"), IdentifierName(generatedVTableName)
)
), IdentifierName(FirstLetterToUpper(entryPoint))
fPtrType,
GetFuncPtrExpr(generatedVTableName, entryPoint)
)
)
), ArgumentList(SeparatedList(parameters.Select(x => Argument(x.Value))))
);
), ArgumentList(SeparatedList(parameters.Select(x => Argument(x.Value))))
);
};
}
else
{
throw new Exception("FORCE-USE-VTABLE");
}


if (needsInvocationShim)
{
ctx.AddSideEffect(ManualWinapiInvokeShim);
}

if (ctx.ReturnsVoid)
{
Expand Down Expand Up @@ -512,8 +549,7 @@ void BuildLoadInvoke(ref IMarshalContext ctx, Action next)
block = Block(UnsafeStatement(Token(SyntaxKind.UnsafeKeyword), block));
}

var method = declaration.WithBody
(block)
var method = declaration.WithBody(block)
.WithAttributeLists(default)
.WithSemicolonToken(default)
.WithParameterList
Expand Down Expand Up @@ -572,6 +608,109 @@ void BuildLoadInvoke(ref IMarshalContext ctx, Action next)
sourceContext.ReportDiagnostic
(Diagnostic.Create(Diagnostics.MethodClassFailure, declaration.GetLocation(), ex.ToString()));
}

InvocationExpressionSyntax ManualWinapiInvokeInnerExpr
(
IMarshalContext ctx,
CallingConvention callingConvention
) => InvocationExpression
(
ParenthesizedExpression
(
CastExpression
(
GetFuncPtrType(callingConvention, ctx.LoadTypes),
GetFuncPtrExpr(generatedVTableName, entryPoint)
)
),
ArgumentList
(
SeparatedList
(
Enumerable.Range
(0, ctx.LoadTypes.Length - 1)
.Select(x => Argument(IdentifierName($"arg{x}")))
)
)
);

StatementSyntax ManualWinapiInvokeShim(IMarshalContext ctx)
{
var stdCallStmt = ManualWinapiInvokeInnerExpr(ctx, CallingConvention.StdCall);
var cdeclStmt = ManualWinapiInvokeInnerExpr(ctx, CallingConvention.Cdecl);
return LocalFunctionStatement
(
IdentifierName(ctx.ReturnLoadType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)),
invocationShimName
)
.WithParameterList
(
ParameterList
(
SeparatedList
(
new ArraySegment<ITypeSymbol>(ctx.LoadTypes, 0, ctx.LoadTypes.Length - 1).Select
(
(x, i) => Parameter(Identifier($"arg{i}"))
.WithType
(
IdentifierName(x.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat))
)
)
)
)
)
.WithBody
(
Block
(
IfStatement
(
MemberAccessExpression
(
SyntaxKind.SimpleMemberAccessExpression,
MemberAccessExpression
(
SyntaxKind.SimpleMemberAccessExpression,
MemberAccessExpression
(
SyntaxKind.SimpleMemberAccessExpression,
MemberAccessExpression
(
SyntaxKind.SimpleMemberAccessExpression,
MemberAccessExpression
(
SyntaxKind.SimpleMemberAccessExpression, IdentifierName("Silk"),
IdentifierName("NET")
), IdentifierName("Core")
), IdentifierName("Native")
), IdentifierName("SilkMarshal")
), IdentifierName("IsWinapiStdcall")
),
Block
(
ctx.ReturnsVoid
? ExpressionStatement(stdCallStmt)
.WithSemicolonToken(Token(SyntaxKind.SemicolonToken))
: ReturnStatement(stdCallStmt)
.WithSemicolonToken(Token(SyntaxKind.SemicolonToken))
),
ElseClause
(
Block
(
ctx.ReturnsVoid
? ExpressionStatement(cdeclStmt)
.WithSemicolonToken(Token(SyntaxKind.SemicolonToken))
: ReturnStatement(cdeclStmt)
.WithSemicolonToken(Token(SyntaxKind.SemicolonToken))
)
)
)
)
)
.WithSemicolonToken(Token(SyntaxKind.SemicolonToken));
}
}

private static string GetCallingConvention(CallingConvention convention)
Expand Down