SharpGen/Transform/MarshalledElementFactory.cs (206 lines of code) (raw):
using System;
using System.Diagnostics;
using System.Linq;
using SharpGen.Config;
using SharpGen.CppModel;
using SharpGen.Logging;
using SharpGen.Model;
namespace SharpGen.Transform
{
public sealed class MarshalledElementFactory
{
private readonly Ioc ioc;
private Logger Logger => ioc.Logger;
private GlobalNamespaceProvider GlobalNamespace => ioc.GlobalNamespace;
private TypeRegistry TypeRegistry => ioc.TypeRegistry;
public MarshalledElementFactory(Ioc ioc)
{
this.ioc = ioc ?? throw new ArgumentNullException(nameof(ioc));
}
public event Action<CsStruct> RequestStructProcessing;
private void CreateCore(CsMarshalBase csMarshallable)
{
var marshallable = (CppMarshallable) csMarshallable.CppElement;
CsTypeBase publicType = null;
CsTypeBase marshalType = null;
var mappingRule = marshallable.Rule;
var publicTypeName = mappingRule is {MappingType: { } mapType} ? mapType : marshallable.TypeName;
// If CppType is an array, try first to get the binding for this array
if (csMarshallable.IsArray)
{
publicType = TypeRegistry.FindBoundType(publicTypeName + "[" + marshallable.ArrayDimension + "]");
if (publicType != null)
csMarshallable.IsArray = false;
}
if (publicType == null)
{
void AssignStringTypes(CsFundamentalType charType)
{
publicType = csMarshallable.HasPointer || csMarshallable.IsArray
? TypeRegistry.String
: charType;
marshalType = csMarshallable.IsArray ? charType : null;
}
switch (publicTypeName)
{
case "char":
AssignStringTypes(TypeRegistry.UInt8);
break;
case "wchar_t":
csMarshallable.IsWideChar = true;
AssignStringTypes(TypeRegistry.Char);
break;
default:
// Try to get a declared type
if (!TypeRegistry.FindBoundType(publicTypeName, out var boundType))
{
Logger.Fatal("Unknown type found [{0}]", publicTypeName);
return;
}
publicType = boundType.CSharpType;
// By default, use the underlying native type as the marshal type
// if it differs from the public type.
marshalType = TypeRegistry.FindBoundType(marshallable.TypeName);
if (publicType == marshalType)
marshalType = null;
// Otherwise, get the registered marshal type if one exists
marshalType ??= boundType.MarshalType;
break;
}
}
switch (publicType)
{
case CsStruct csStruct:
{
if (!csStruct.IsFullyMapped)
// If a structure was not already parsed, then parse it before going further
RequestStructProcessing?.Invoke(csStruct);
if (!csStruct.IsFullyMapped)
// No one tried to map the struct so we can't continue.
Logger.Fatal(
$"No struct processor processed {csStruct.QualifiedName}. Cannot continue processing"
);
if (csStruct.HasMarshalType && !csMarshallable.HasPointer)
// If referenced structure has a specialized marshalling, then use the structure's built-in marshalling
marshalType = publicType;
break;
}
case CsEnum:
// enums don't need a marshal type. They can always marshal as their underlying type.
marshalType = null;
break;
}
if (publicType.IsWellKnownType(GlobalNamespace, WellKnownName.PointerSize))
marshalType = PointerType(mappingRule);
// Present void* elements as IntPtr. Marshal strings as IntPtr
if (marshallable.HasPointer)
{
if (publicType == TypeRegistry.Void)
publicType = marshalType = PointerType(mappingRule);
else if (publicType == TypeRegistry.String)
marshalType = TypeRegistry.IntPtr;
}
csMarshallable.PublicType = publicType;
csMarshallable.MarshalType = marshalType;
csMarshallable.Relations = RelationParser.ParseRelation(mappingRule.Relation, Logger);
}
private static CsFundamentalType PointerType(MappingRule mappingRule) => mappingRule.KeepPointers != true
? TypeRegistry.IntPtr
: TypeRegistry.VoidPtr;
public CsReturnValue Create(CppReturnValue cppReturnValue)
{
CsReturnValue retVal = new(ioc, cppReturnValue);
CreateCore(retVal);
MakeGeneralPointersBeIntPtr(retVal);
if (retVal.PublicType is CsInterface {IsCallback: true} iface)
{
retVal.PublicType = iface.GetNativeImplementationOrThis();
}
return retVal;
}
public CsField Create(CppField cppField, string name)
{
CsField field = new(ioc, cppField, name);
CreateCore(field);
MakeGeneralPointersBeIntPtr(field);
return field;
}
private static void MakeGeneralPointersBeIntPtr(CsMarshalBase csMarshallable)
{
if (!csMarshallable.HasPointer)
return;
if (csMarshallable is CsReturnValue {PublicType: CsStruct} && !csMarshallable.IsArray)
return;
csMarshallable.MarshalType = TypeRegistry.IntPtr;
if (csMarshallable.IsString || csMarshallable.IsInterface)
return;
csMarshallable.PublicType = TypeRegistry.IntPtr;
}
public CsParameter Create(CppParameter cppParameter, string name)
{
CsParameter param = new(ioc, cppParameter, name);
CreateCore(param);
if (cppParameter.IsAttributeRuleRedundant)
Logger.Message("Parameter [{0}] has redundant attribute rule specification", cppParameter.FullName);
static bool HasFlag(ParamAttribute value, ParamAttribute flag) => (value & flag) == flag;
// --------------------------------------------------------------------------------
// Pointer - Handle special cases
// --------------------------------------------------------------------------------
if (param.HasPointer)
{
var paramRule = cppParameter.Rule;
var numIndirections = cppParameter.Pointer.Count(static p => p is '*' or '&');
bool isBuffer, isIn, isInOut, isOut;
{
var cppAttribute = cppParameter.Attribute;
// Force Interface** to be ParamAttribute.Out
if (param.PublicType is CsInterface && cppAttribute == ParamAttribute.In && numIndirections == 2)
cppAttribute = ParamAttribute.Out;
isBuffer = HasFlag(cppAttribute, ParamAttribute.Buffer);
isIn = HasFlag(cppAttribute, ParamAttribute.In);
isInOut = HasFlag(cppAttribute, ParamAttribute.InOut);
isOut = HasFlag(cppAttribute, ParamAttribute.Out);
}
// Either In, InOut or Out is set
Debug.Assert((isIn ? 1 : 0) + (isInOut ? 1 : 0) + (isOut ? 1 : 0) == 1);
// --------------------------------------------------------------------------------
// Handling Parameter Interface
// --------------------------------------------------------------------------------
if (param.PublicType is CsInterface)
{
// Simplify logic by assuming interface instance pointer is the interface itself.
--numIndirections;
if (isOut)
param.Attribute = CsParameterAttribute.Out;
}
else if (isIn)
{
var publicType = param.PublicType;
param.Attribute = publicType is CsFundamentalType {IsPointerSize: true}
|| publicType.IsWellKnownType(GlobalNamespace, WellKnownName.FunctionCallback)
? CsParameterAttribute.In
: CsParameterAttribute.RefIn;
}
else if (isInOut)
{
if (param.IsOptional)
{
param.SetPublicResetMarshalType(PointerType(paramRule));
param.Attribute = CsParameterAttribute.In;
}
else
{
param.Attribute = CsParameterAttribute.Ref;
}
}
else if (isOut)
{
param.Attribute = CsParameterAttribute.Out;
}
switch (param.PublicType)
{
// Handle void* with Buffer attribute
case CsFundamentalType {IsUntypedPointer: true} when isBuffer:
param.Attribute = CsParameterAttribute.In;
param.IsArray = false;
break;
// Handle strings with Out attribute
case CsFundamentalType {IsString: true} when isOut:
param.Attribute = CsParameterAttribute.In;
param.IsArray = false;
param.SetPublicResetMarshalType(TypeRegistry.IntPtr);
break;
// There's no way to know how to deallocate native-allocated memory correctly
// since we don't know what allocator the native memory uses,
// so we treat any extra pointer indirections as IntPtr
case not CsFundamentalType {IsUntypedPointer: true} when numIndirections > 1:
param.IsArray = false;
param.SetPublicResetMarshalType(TypeRegistry.IntPtr);
break;
}
}
if (param.Relations.OfType<StructSizeRelation>().Any())
Logger.Error(
LoggingCodes.InvalidRelation,
$"Parameter [{cppParameter}] marked with a struct-size relationship"
);
return param;
}
}
}