Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ internal struct ValueDesc
public readonly bool IsArray => (Flags & ValueFlags.Array) != 0;
public readonly bool IsConstant => (Flags & ValueFlags.Constant) != 0;
public readonly bool IsCopy => (Flags & ValueFlags.Copy) != 0;
public readonly bool IsReference => (Flags & ValueFlags.Reference) != 0;
public Action<object> WriteCustomAttrs { get; set; }
public object CustomAttrGeneratorData { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ internal enum ValueFlags
Constant = 1 << 1,
Copy = 1 << 2,
Array = 1 << 3,
Reference = 1 << 4,
}
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ public void BeginValue(in ValueDesc desc)
Write(GetAccessSpecifierString(desc.AccessSpecifier, isNested: true));
Write(" static ");

if (_generator.Config.GenerateUnmanagedConstants && desc.IsConstant && !desc.IsCopy)
if (_generator.Config.GenerateUnmanagedConstants && desc.IsConstant && (!desc.IsCopy || desc.IsReference))
{
if (desc.IsArray)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -502,7 +502,13 @@ private CXXRecordDecl GetRecordDecl(CXXBaseSpecifier cxxBaseSpecifier)

if (IsType<RecordType>(cxxBaseSpecifier, baseType, out var recordType))
{
return (CXXRecordDecl)recordType.Decl;
var recordDecl = (CXXRecordDecl)recordType.Decl;

// The referenced decl can be a redeclaration (e.g. a MIDL forward `typedef interface`)
// rather than the definition. A non-definition carries the base list but no members, so
// resolving to the definition is required to flatten every inherited method into the
// derived vtable.
return (CXXRecordDecl?)recordDecl.Definition ?? recordDecl;
}

// A dependent base (such as `Base<T>` used within a class template) has no resolved
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -441,7 +441,10 @@ private string GetTypeName(Cursor? cursor, Cursor? context, Type rootType, Type
{
case CXTemplateArgumentKind_Type:
{
typeName = GetRemappedTypeName(cursor, context: null, arg.AsType, out var nativeAsTypeName, skipUsing: true, isTemplate: true);
// The argument name is emitted as part of the generic type (e.g.
// `IReference<Matrix4x4>`), so its namespace using must be emitted too;
// the enclosing type name is not itself resolved through `--with-namespace`.
typeName = GetRemappedTypeName(cursor, context: null, arg.AsType, out var nativeAsTypeName, skipUsing: false, isTemplate: true);
break;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1360,7 +1360,7 @@ void ForFunctionDecl(ParmVarDecl parmVarDecl, FunctionDecl functionDecl)
if (defaultArg is not null)
{
csharpOutputBuilder.WriteCustomAttribute("Optional, DefaultParameterValue(", () => {
generator.Visit(defaultArg);
generator.VisitTransparentStructDefaultArg(defaultArg);
csharpOutputBuilder.Write(')');
});
}
Expand Down Expand Up @@ -1486,6 +1486,24 @@ bool IsDefaultValue(Expr defaultArg)
}
}

private void VisitTransparentStructDefaultArg(Expr defaultArg)
{
// A default value cast to a transparent struct (e.g. `S_OK` => `(HRESULT)0`) is not a
// constant expression, so `DefaultParameterValue` must receive the underlying constant.
if (IsStmtAsWritten<ExplicitCastExpr>(defaultArg, out var castExpr, removeParens: true))
{
var typeName = GetRemappedTypeName(castExpr, context: null, castExpr.Type, out _);

if (_config.WithTransparentStructs.ContainsKey(typeName))
{
Visit(castExpr.SubExprAsWritten);
return;
}
}

Visit(defaultArg);
}

private void VisitTranslationUnitDecl(TranslationUnitDecl translationUnitDecl)
{
PreprocessUuidNameOverrides(translationUnitDecl.Decls);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,14 @@ private void VisitVarDecl(VarDecl varDecl)
flags |= ValueFlags.Constant;
}

if (varDecl.HasInit && IsStmtAsWritten<CXXUuidofExpr>(varDecl.Init, out _, removeParens: true))
{
// A `__uuidof` binding (e.g. WinRT `MIDL_CONST_ID IID& IID_X = __uuidof(X)`) is
// already emitted as a `ref readonly Guid` via _uuidsToGenerate; skip the redundant
// declaration to avoid a duplicate member.
return;
}

if (IsStmtAsWritten<StringLiteral>(varDecl.Init, out var stringLiteral, removeParens: true))
{
kind = ValueKind.String;
Expand Down Expand Up @@ -195,6 +203,15 @@ private void VisitVarDecl(VarDecl varDecl)
// It's easiest just to let _uuidsToGenerate handle it
return;
}

// clang wraps an alias to another constant (e.g. `#define IID_X IID_Y`) in a
// copy-constructor. The referenced constant has backing storage, so keep the
// `ref readonly` alias rather than emitting a by-value copy.
if (IsStmtAsWritten<DeclRefExpr>(cxxConstructExpr.Args[0], out _, removeParens: true))
{
flags |= ValueFlags.Reference;
}

flags |= ValueFlags.Copy;
}
else if (IsStmtAsWritten<CXXNullPtrLiteralExpr>(varDecl.Init, out _, removeParens: true))
Expand Down
15 changes: 15 additions & 0 deletions sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2511,6 +2511,14 @@ private void UncheckStmt(string targetTypeName, Stmt stmt)
needsCast = true;
}

// A signed-negative value does not implicitly convert to an unsigned target in C#
// (e.g. `uint x = -1`), so emit the explicit cast to the target type.
if (IsPrevContextDecl<VarDecl>(out _, out _) && IsUnsigned(targetTypeName)
&& !IsStmtAsWritten<CastExpr>(stmt, out _, removeParens: true) && IsNegativeConstant(stmt))
{
needsCast = true;
}

if (needsCast)
{
_outputBuilder.BeginInnerValue();
Expand Down Expand Up @@ -2545,6 +2553,13 @@ private void UncheckStmt(string targetTypeName, Stmt stmt)
}
}

private static bool IsNegativeConstant(Stmt stmt)
{
var written = (stmt is Expr expr) ? GetExprAsWritten(expr, removeParens: true) : stmt;
var evaluation = written.Handle.Evaluate;
return (evaluation.Kind == CXEval_Int) && (evaluation.AsLongLong < 0);
}

private bool EnumConstantInitNeedsCast(string targetTypeName, Stmt stmt)
{
// A C# enum member initializer must be implicitly convertible to the enum's underlying
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System.Runtime.InteropServices;

namespace ClangSharp.Test
{
public static partial class Methods
{
[DllImport("ClangSharpPInvokeGenerator", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern void MyFunction([Optional, DefaultParameterValue(0)] HRESULT value);

[NativeTypeName("#define S_OK ((HRESULT)0L)")]
public const int S_OK = ((int)(0));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<bindings>
<namespace name="ClangSharp.Test">
<class name="Methods" access="public" static="true">
<function name="MyFunction" access="public" lib="ClangSharpPInvokeGenerator" convention="Cdecl" static="true">
<type>void</type>
<param name="value">
<type>HRESULT</type>
<init>
<code>((HRESULT)(<value>0</value>))</code>
</init>
</param>
</function>
<constant name="S_OK" access="public">
<type primitive="True">int</type>
<value>
<code>((int)(<value>0</value>))</code>
</value>
</constant>
</class>
</namespace>
</bindings>
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System.Runtime.InteropServices;

namespace ClangSharp.Test
{
public static partial class Methods
{
[DllImport("ClangSharpPInvokeGenerator", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern void MyFunction([NativeTypeName("unsigned int")] uint value = unchecked((uint)(-1)));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<bindings>
<namespace name="ClangSharp.Test">
<class name="Methods" access="public" static="true">
<function name="MyFunction" access="public" lib="ClangSharpPInvokeGenerator" convention="Cdecl" static="true">
<type>void</type>
<param name="value">
<type>uint</type>
<init>
<unchecked>
<value>
<cast>uint</cast>
<value>
<code>-1</code>
</value>
</value>
</unchecked>
</init>
</param>
</function>
</class>
</namespace>
</bindings>
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
using ClangSharp.Test;
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace System
{
public unsafe partial struct Guid
{
[NativeTypeName("unsigned long")]
public uint Data1;

[NativeTypeName("unsigned short")]
public ushort Data2;

[NativeTypeName("unsigned short")]
public ushort Data3;

[NativeTypeName("unsigned char[8]")]
public fixed byte Data4[8];
}

[Guid("79EAC9E0-BAF9-11CE-8C82-00AA004BA90B")]
public partial struct IInternet
{
public int x;
}

public static partial class Methods
{
[NativeTypeName("#define IID_IOInet IID_IInternet")]
public static ref readonly Guid IID_IOInet => ref IID_IInternet;

public static ref readonly Guid IID_IInternet
{
get
{
ReadOnlySpan<byte> data = new byte[] {
0xE0, 0xC9, 0xEA, 0x79,
0xF9, 0xBA,
0xCE, 0x11,
0x8C,
0x82,
0x00,
0xAA,
0x00,
0x4B,
0xA9,
0x0B
};

Debug.Assert(data.Length == Unsafe.SizeOf<Guid>());
return ref Unsafe.As<byte, Guid>(ref MemoryMarshal.GetReference(data));
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
using ClangSharp.Test;
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace System
{
public partial struct Guid
{
[NativeTypeName("unsigned long")]
public uint Data1;

[NativeTypeName("unsigned short")]
public ushort Data2;

[NativeTypeName("unsigned short")]
public ushort Data3;

[NativeTypeName("unsigned char[8]")]
public _Data4_e__FixedBuffer Data4;

[InlineArray(8)]
public partial struct _Data4_e__FixedBuffer
{
public byte e0;
}
}

[Guid("79EAC9E0-BAF9-11CE-8C82-00AA004BA90B")]
public partial struct IInternet
{
public int x;
}

public static partial class Methods
{
[NativeTypeName("#define IID_IOInet IID_IInternet")]
public static ref readonly Guid IID_IOInet => ref IID_IInternet;

public static ref readonly Guid IID_IInternet
{
get
{
ReadOnlySpan<byte> data = [
0xE0, 0xC9, 0xEA, 0x79,
0xF9, 0xBA,
0xCE, 0x11,
0x8C,
0x82,
0x00,
0xAA,
0x00,
0x4B,
0xA9,
0x0B
];

Debug.Assert(data.Length == Unsafe.SizeOf<Guid>());
return ref Unsafe.As<byte, Guid>(ref MemoryMarshal.GetReference(data));
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<bindings>
<namespace name="System">
<struct name="Guid" access="public" unsafe="true">
<field name="Data1" access="public">
<type native="unsigned long">uint</type>
</field>
<field name="Data2" access="public">
<type native="unsigned short">ushort</type>
</field>
<field name="Data3" access="public">
<type native="unsigned short">ushort</type>
</field>
<field name="Data4" access="public">
<type native="unsigned char[8]" count="8" fixed="_Data4_e__FixedBuffer">byte</type>
</field>
</struct>
<struct name="IInternet" access="public" uuid="79eac9e0-baf9-11ce-8c82-00aa004ba90b">
<field name="x" access="public">
<type>int</type>
</field>
</struct>
<class name="Methods" access="public" static="true">
<constant name="IID_IOInet" access="public">
<type primitive="False">Guid</type>
<value>
<code>ref IID_IInternet</code>
</value>
</constant>
<iid name="IID_IInternet" value="0x79EAC9E0, 0xBAF9, 0x11CE, 0x8C, 0x82, 0x00, 0xAA, 0x00, 0x4B, 0xA9, 0x0B" />
</class>
</namespace>
</bindings>
Loading
Loading