// Copyright (c) 2010-2014 SharpDX - Alexandre Mutel
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using SharpGen.Logging;
using SharpGen.CppModel;
using SharpGen.Model;
namespace SharpGen.Transform
{
///
/// Transforms a C++ struct to a C# struct.
///
public class StructTransform : TransformBase, ITransformer, ITransformPreparer
{
private TypeRegistry TypeRegistry => Ioc.TypeRegistry;
private readonly MarshalledElementFactory factory;
private readonly NamespaceRegistry namespaceRegistry;
public StructTransform(NamingRulesManager namingRules,
NamespaceRegistry namespaceRegistry,
MarshalledElementFactory factory,
Ioc ioc) : base(namingRules, ioc)
{
this.namespaceRegistry = namespaceRegistry;
this.factory = factory;
factory.RequestStructProcessing += Process;
}
private readonly Dictionary _mapMoveStructToInner = new();
///
/// Moves a C++ struct to an inner C# struct.
///
/// From C++ struct regex query.
/// To C# struct.
public void MoveStructToInner(string fromStruct, string toStruct)
{
_mapMoveStructToInner.Add(new Regex("^" + fromStruct + "$"), toStruct);
}
///
/// Prepares C++ struct for mapping. This method is creating the associated C# struct.
///
/// The c++ struct.
///
public override CsStruct Prepare(CppStruct cppStruct)
{
// Create a new C# struct
var nameSpace = namespaceRegistry.ResolveNamespace(cppStruct);
var csStruct = new CsStruct(cppStruct, NamingRules.Rename(cppStruct))
{
// IsFullyMapped to false => The structure is being mapped
IsFullyMapped = false
};
// Add the C# struct to its namespace
nameSpace.Add(csStruct);
// Map the C++ name to the C# struct
TypeRegistry.BindType(cppStruct.Name, csStruct, source: cppStruct.ParentInclude?.Name);
return csStruct;
}
///
/// Maps the C++ struct to C# struct.
///
/// The c sharp struct.
public override void Process(CsStruct csStruct)
{
// TODO: this mapping must be robust. Current calculation for field offset is not always accurate for union.
// TODO: need to handle align/packing correctly.
// If a struct was already mapped, then return immediately
// The method MapStruct can be called recursively
if (csStruct.IsFullyMapped)
return;
// Set IsFullyMappy in order to avoid recursive mapping
csStruct.IsFullyMapped = true;
// Get the associated CppStruct and CSharpTag
var cppStruct = (CppStruct)csStruct.CppElement;
// If this structure need to me moved to another container, move it now
foreach (var keyValuePair in _mapMoveStructToInner)
{
if (keyValuePair.Key.Match(csStruct.CppElementName).Success)
{
string cppName = keyValuePair.Key.Replace(csStruct.CppElementName, keyValuePair.Value);
var destSharpStruct = (CsStruct)TypeRegistry.FindBoundType(cppName);
// Remove the struct from his container
csStruct.Parent.Remove(csStruct);
// Add this struct to the new container struct
destSharpStruct.Add(csStruct);
}
}
// Current offset of a field
uint currentFieldAbsoluteOffset = 0;
// Last field offset
int previousFieldOffsetIndex = -1;
// Size of the last field
uint previousFieldSize = 0;
uint maxSizeOfField = 0;
bool isNonSequential = false;
int cumulatedBitOffset = 0;
var inheritedStructs = new Stack();
var currentStruct = cppStruct;
while (currentStruct != null && currentStruct.Base != currentStruct.Name)
{
inheritedStructs.Push(currentStruct);
currentStruct = TypeRegistry.FindBoundType(currentStruct.Base)?.CppElement as CppStruct;
}
while (inheritedStructs.Count > 0)
{
currentStruct = inheritedStructs.Pop();
var fields = currentStruct.Fields.ToArray();
int fieldCount = fields.Length;
var fieldNames = NamingRules.Rename(fields);
// -------------------------------------------------------------------------------
// Iterate on all fields and perform mapping
// -------------------------------------------------------------------------------
for (int fieldIndex = 0; fieldIndex < fieldCount; fieldIndex++)
{
var cppField = fields[fieldIndex];
var fieldName = fieldNames[fieldIndex];
Logger.RunInContext(
cppField.ToString(),
() =>
{
var csField = factory.Create(cppField, fieldName);
csStruct.Add(csField);
// If last field has same offset, then it's a union
// CurrentOffset is not moved
if (isNonSequential && previousFieldOffsetIndex != cppField.Offset)
{
previousFieldSize = maxSizeOfField;
maxSizeOfField = 0;
isNonSequential = false;
}
currentFieldAbsoluteOffset += previousFieldSize;
// If field alignment is null, then we have a pointer somewhere so we can't align
if (csField.MarshalType.Alignment is { } fieldAlignment)
{
// otherwise, align the field on the alignment requirement of the field
var delta = currentFieldAbsoluteOffset % fieldAlignment;
if (delta != 0)
currentFieldAbsoluteOffset += fieldAlignment - delta;
}
// Get correct offset (for handling union)
csField.Offset = currentFieldAbsoluteOffset;
// Handle bit fields : calculate BitOffset and BitMask for this field
if (previousFieldOffsetIndex != cppField.Offset)
cumulatedBitOffset = 0;
if (cppField.IsBitField)
{
var lastCumulatedBitOffset = cumulatedBitOffset;
cumulatedBitOffset += cppField.BitOffset;
csField.BitMask = (1 << cppField.BitOffset) - 1;
csField.BitOffset = lastCumulatedBitOffset;
}
var nextFieldIndex = fieldIndex + 1;
if (previousFieldOffsetIndex == cppField.Offset
|| nextFieldIndex < fieldCount && fields[nextFieldIndex].Offset == cppField.Offset)
{
if (previousFieldOffsetIndex != cppField.Offset)
maxSizeOfField = 0;
maxSizeOfField = Math.Max(csField.Size, maxSizeOfField);
isNonSequential = true;
csStruct.ExplicitLayout = true;
previousFieldSize = 0;
}
else
{
previousFieldSize = csField.Size;
}
previousFieldOffsetIndex = cppField.Offset;
}
);
}
}
// In case of explicit layout, check that we can safely generate it on both x86 and x64 (in case of an union
// using pointers, we can't)
if (!csStruct.HasCustomMarshal && csStruct.ExplicitLayout && !cppStruct.IsUnion)
{
var fieldList = csStruct.Fields;
for (var i = 0; i < fieldList.Count; i++)
{
var field = fieldList[i];
var fieldAlignment = field.MarshalType.Alignment;
if (fieldAlignment.HasValue)
continue;
// If pointer field is not the last one, than we can't handle it
if (i + 1 >= fieldList.Count)
continue;
Logger.Error(
LoggingCodes.NonPortableAlignment,
"The field [{0}] in structure [{1}] has pointer alignment within a structure that requires explicit layout. This situation cannot be handled on both 32-bit and 64-bit architectures. This structure needs manual layout (remove fields from definition) and write them manually in xml mapping files",
field.CppElementName,
csStruct.CppElementName
);
break;
}
}
csStruct.StructSize = currentFieldAbsoluteOffset + previousFieldSize;
}
}
}