1254 lines
51 KiB
C#
1254 lines
51 KiB
C#
#region License
|
|
//
|
|
// The Open Toolkit Library License
|
|
//
|
|
// Copyright (c) 2006 - 2013 Stefanos Apostolopoulos for the Open Toolkit library.
|
|
//
|
|
// 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.
|
|
//
|
|
#endregion
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Text.RegularExpressions;
|
|
using System.Xml.XPath;
|
|
using Bind.Structures;
|
|
using Delegate = Bind.Structures.Delegate;
|
|
|
|
namespace Bind
|
|
{
|
|
using Enum = Bind.Structures.Enum;
|
|
using Type = Bind.Structures.Type;
|
|
|
|
class FuncProcessor
|
|
{
|
|
static readonly Regex Endings = new Regex(
|
|
@"([fd]v?|u?[isb](64)?v?|v|i_v|fi)$",
|
|
RegexOptions.Compiled);
|
|
static readonly Regex EndingsNotToTrim = new Regex(
|
|
"(sh|ib|[tdrey]s|[eE]n[vd]|bled" +
|
|
"|Attrib|Access|Boolean|Coord|Depth|Feedbacks|Finish|Flag" +
|
|
"|Groups|IDs|Indexed|Instanced|Pixels|Queries|Status|Tess|Through" +
|
|
"|Uniforms|Varyings|Weight|Width)$",
|
|
RegexOptions.Compiled);
|
|
static readonly Regex EndingsAddV = new Regex("^0", RegexOptions.Compiled);
|
|
|
|
readonly IEnumerable<string> Overrides;
|
|
|
|
IBind Generator { get; set; }
|
|
Settings Settings { get { return Generator.Settings; } }
|
|
|
|
public FuncProcessor(IBind generator, IEnumerable<string> overrides)
|
|
{
|
|
if (generator == null)
|
|
throw new ArgumentNullException("generator");
|
|
if (overrides == null)
|
|
throw new ArgumentNullException("overrides");
|
|
|
|
Generator = generator;
|
|
Overrides = overrides;
|
|
}
|
|
|
|
public FunctionCollection Process(EnumProcessor enum_processor, DocProcessor doc_processor,
|
|
DelegateCollection delegates, EnumCollection enums, string apiname, string apiversion)
|
|
{
|
|
foreach (var file in Overrides)
|
|
{
|
|
Console.WriteLine("Processing funcs in {0}.", file);
|
|
|
|
var nav = new XPathDocument(file).CreateNavigator();
|
|
foreach (var version in apiversion.Split('|'))
|
|
{
|
|
// Translate each delegate:
|
|
// 1st using the <replace> elements in overrides.xml
|
|
// 2nd using the hardcoded rules in FuncProcessor (e.g. char* -> string)
|
|
foreach (var signatures in delegates.Values)
|
|
{
|
|
foreach (var d in signatures)
|
|
{
|
|
var replace = GetFuncOverride(nav, d, apiname, apiversion);
|
|
TranslateExtension(d);
|
|
TranslateReturnType(d, replace, nav, enum_processor, enums, apiname, version);
|
|
TranslateParameters(d, replace, nav, enum_processor, enums, apiname, version);
|
|
TranslateAttributes(d, replace, nav, apiname, version);
|
|
}
|
|
}
|
|
|
|
// Create overloads for backwards compatibility,
|
|
// by resolving <overload> elements
|
|
var overload_list = new List<Delegate>();
|
|
foreach (var d in delegates.Values.Select(v => v.First()))
|
|
{
|
|
var overload_elements = GetFuncOverload(nav, d, apiname, apiversion);
|
|
foreach (XPathNavigator overload_element in overload_elements)
|
|
{
|
|
var overload = new Delegate(d);
|
|
TranslateReturnType(overload, overload_element, nav, enum_processor, enums, apiname, version);
|
|
TranslateParameters(overload, overload_element, nav, enum_processor, enums, apiname, version);
|
|
TranslateAttributes(overload, overload_element, nav, apiname, version);
|
|
overload_list.Add(overload);
|
|
}
|
|
}
|
|
foreach (var overload in overload_list)
|
|
{
|
|
delegates.Add(overload);
|
|
}
|
|
}
|
|
}
|
|
|
|
Console.WriteLine("Generating wrappers.");
|
|
var wrappers = CreateWrappers(delegates, enums);
|
|
|
|
Console.WriteLine("Generating convenience overloads.");
|
|
wrappers.AddRange(CreateConvenienceOverloads(wrappers));
|
|
|
|
Console.WriteLine("Generating CLS compliant overloads.");
|
|
wrappers = CreateCLSCompliantWrappers(wrappers, enums);
|
|
|
|
Console.WriteLine("Removing non-CLS compliant duplicates.");
|
|
wrappers = MarkCLSCompliance(wrappers);
|
|
|
|
Console.WriteLine("Removing overloaded delegates.");
|
|
RemoveOverloadedDelegates(delegates, wrappers);
|
|
|
|
Console.WriteLine("Generating address table.");
|
|
GenerateAddressTable(delegates);
|
|
|
|
Console.WriteLine("Generating documentation.");
|
|
GenerateDocumentation(wrappers, enum_processor, doc_processor);
|
|
|
|
return wrappers;
|
|
}
|
|
|
|
#region Private Members
|
|
|
|
void GenerateDocumentation(FunctionCollection wrappers,
|
|
EnumProcessor enum_processor, DocProcessor doc_processor)
|
|
{
|
|
foreach (var list in wrappers)
|
|
{
|
|
foreach (var f in list.Value)
|
|
{
|
|
f.Documentation = doc_processor.Process(f,
|
|
enum_processor);
|
|
}
|
|
}
|
|
}
|
|
|
|
void GenerateAddressTable(DelegateCollection delegates)
|
|
{
|
|
// We allocate one slot per entry point. Rules:
|
|
// - All extensions get a slot
|
|
// - Core functions get a slot, unless UseDllImports is enabled
|
|
// - On Windows, core functions with version > 1.1 must be treated as extensions.
|
|
// This is controlled via the UseWindowsCompatibleGL setting.
|
|
// Entry points without a slot are assigned the magic slot index -1.
|
|
// Generator.Rewrite detects this and generates a static DllImport call
|
|
// instead of a calli instruction for these functions.
|
|
|
|
int slot = -1;
|
|
foreach (var list in delegates.Values)
|
|
{
|
|
var func = list.First();
|
|
if (func.RequiresSlot(Settings))
|
|
{
|
|
slot++;
|
|
foreach (var d in list)
|
|
{
|
|
d.Slot = slot;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Core function routed through DllImport - no slot generated
|
|
foreach (var d in list)
|
|
{
|
|
d.Slot = -1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// When we have a list of overloaded delegates, make sure that
|
|
// all generated wrappers use the first (original) delegate, not
|
|
// the overloaded ones. This allows us to reduce the amount
|
|
// of delegates we need to generate (1 per entry point instead
|
|
// of 1 per overload), which improves loading times.
|
|
static void RemoveOverloadedDelegates(DelegateCollection delegates, FunctionCollection wrappers)
|
|
{
|
|
foreach (var w in wrappers.Values.SelectMany(w => w))
|
|
{
|
|
var d = delegates[w.Name].First();
|
|
w.WrappedDelegate = d;
|
|
}
|
|
}
|
|
|
|
static string GetPath(string apipath, string apiname, string apiversion, string function, string extension)
|
|
{
|
|
var path = new StringBuilder();
|
|
path.Append("/signatures/");
|
|
path.Append(apipath);
|
|
if (!String.IsNullOrEmpty(apiname) && !String.IsNullOrEmpty(apiversion))
|
|
{
|
|
path.Append(String.Format(
|
|
"[contains(concat('|', @name, '|'), '|{0}|') and " +
|
|
"(contains(concat('|', @version, '|'), '|{1}|') or not(boolean(@version)))]",
|
|
apiname,
|
|
apiversion));
|
|
}
|
|
else if (!String.IsNullOrEmpty(apiname))
|
|
{
|
|
path.Append(String.Format("[contains(concat('|', @name, '|'), '|{0}|')]", apiname));
|
|
}
|
|
else if (!String.IsNullOrEmpty(apiversion))
|
|
{
|
|
path.Append(String.Format("[contains(concat('|', @version, '|'), '|{0}|') or not(boolean(@version))]", apiversion));
|
|
}
|
|
|
|
if (function != null)
|
|
{
|
|
if (extension != null)
|
|
{
|
|
// match an override that has this specific extension
|
|
// *or* one that has no extension at all (equivalent
|
|
// to "match all possible extensions")
|
|
path.Append(String.Format(
|
|
"/function[contains(concat('|', @name, '|'), '|{0}|') and " +
|
|
"(contains(concat('|', @extension, '|'), '|{1}|') or not(boolean(@extension)))]",
|
|
function,
|
|
extension));
|
|
}
|
|
else
|
|
{
|
|
path.Append(String.Format(
|
|
"/function[contains(concat('|', @name, '|'), '|{0}|')]",
|
|
function));
|
|
}
|
|
}
|
|
|
|
return path.ToString();
|
|
}
|
|
|
|
static string GetOverloadsPath(string apiname, string apiversion, string function, string extension)
|
|
{
|
|
return GetPath("overload", apiname, apiversion, function, extension);
|
|
}
|
|
|
|
static string GetOverridesPath(string apiname, string apiversion, string function, string extension)
|
|
{
|
|
return GetPath("replace", apiname, apiversion, function, extension);
|
|
}
|
|
|
|
void TranslateType(Bind.Structures.Type type,
|
|
XPathNavigator function_override, XPathNavigator overrides,
|
|
EnumProcessor enum_processor, EnumCollection enums,
|
|
string category, string apiname)
|
|
{
|
|
Bind.Structures.Enum @enum;
|
|
string s;
|
|
|
|
category = enum_processor.TranslateEnumName(category);
|
|
|
|
// Try to find out if it is an enum. If the type exists in the normal GLEnums list, use this.
|
|
// Special case for Boolean which is there simply because C89 does not support bool types.
|
|
// We don't really need that in C#
|
|
bool normal =
|
|
enums.TryGetValue(type.CurrentType, out @enum) ||
|
|
enums.TryGetValue(enum_processor.TranslateEnumName(type.CurrentType), out @enum);
|
|
|
|
// Translate enum types
|
|
type.IsEnum = false;
|
|
if (normal && @enum.Name != "GLenum" && @enum.Name != "Boolean")
|
|
{
|
|
type.IsEnum = true;
|
|
|
|
if ((Settings.Compatibility & Settings.Legacy.ConstIntEnums) != Settings.Legacy.None)
|
|
{
|
|
type.QualifiedType = "int";
|
|
}
|
|
else
|
|
{
|
|
// Some functions and enums have the same names.
|
|
// Make sure we reference the enums rather than the functions.
|
|
if (normal)
|
|
{
|
|
type.QualifiedType = String.Format("{0}.{1}", Settings.EnumsOutput, @enum.Name);
|
|
}
|
|
}
|
|
}
|
|
else if (Generator.GLTypes.TryGetValue(type.CurrentType, out s))
|
|
{
|
|
// Check if the parameter is a generic GLenum. If it is, search for a better match,
|
|
// otherwise fallback to Settings.CompleteEnumName (named 'All' by default).
|
|
if (s.Contains("GLenum") /*&& !String.IsNullOrEmpty(category)*/)
|
|
{
|
|
type.IsEnum = true;
|
|
|
|
if ((Settings.Compatibility & Settings.Legacy.ConstIntEnums) != Settings.Legacy.None)
|
|
{
|
|
type.QualifiedType = "int";
|
|
}
|
|
else
|
|
{
|
|
// Better match: enum.Name == function.Category (e.g. GL_VERSION_1_1 etc)
|
|
// Note: for backwards compatibility we use "category" only for the gl api.
|
|
// glcore, gles1 and gles2 use the All enum instead.
|
|
if (apiname == "gl" && enums.ContainsKey(category))
|
|
{
|
|
type.QualifiedType = String.Format("{0}{1}{2}", Settings.EnumsOutput,
|
|
Settings.NamespaceSeparator, enum_processor.TranslateEnumName(category));
|
|
}
|
|
else
|
|
{
|
|
type.QualifiedType = String.Format("{0}{1}{2}", Settings.EnumsOutput,
|
|
Settings.NamespaceSeparator, Settings.CompleteEnumName);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
type.QualifiedType = s;
|
|
}
|
|
}
|
|
|
|
if ((type.Array == 0 && type.Pointer == 0 && !type.Reference) &&
|
|
(type.QualifiedType.ToLower().Contains("buffersize") ||
|
|
type.QualifiedType.ToLower().Contains("sizeiptr") ||
|
|
type.QualifiedType.Contains("size_t")))
|
|
{
|
|
type.WrapperType |= WrapperTypes.SizeParameter;
|
|
}
|
|
|
|
type.CurrentType =
|
|
Generator.CSTypes.ContainsKey(type.CurrentType) ?
|
|
Generator.CSTypes[type.CurrentType] : type.CurrentType;
|
|
|
|
// Make sure that enum parameters follow enum overrides, i.e.
|
|
// if enum ErrorCodes is overriden to ErrorCode, then parameters
|
|
// of type ErrorCodes should also be overriden to ErrorCode.
|
|
XPathNavigator enum_override = overrides.SelectSingleNode(
|
|
EnumProcessor.GetOverridesPath(apiname, type.CurrentType));
|
|
if (enum_override != null)
|
|
{
|
|
// For consistency - many overrides use string instead of String.
|
|
if (enum_override.Value == "string")
|
|
type.QualifiedType = "String";
|
|
else if (enum_override.Value == "StringBuilder")
|
|
type.QualifiedType = "StringBuilder";
|
|
else
|
|
type.CurrentType = enum_override.Value;
|
|
}
|
|
|
|
if (type.CurrentType == "IntPtr" && String.IsNullOrEmpty(type.PreviousType))
|
|
type.Pointer = 0;
|
|
|
|
if (type.Pointer >= 3)
|
|
{
|
|
System.Diagnostics.Trace.WriteLine(String.Format(
|
|
"[Error] Type '{0}' has a high pointer level. Bindings will be incorrect.",
|
|
type));
|
|
}
|
|
|
|
if (!type.IsEnum)
|
|
{
|
|
// Remove qualifier if type is not an enum
|
|
// Resolves issues when replacing / overriding
|
|
// an enum parameter with a non-enum type
|
|
type.QualifiedType = type.CurrentType;
|
|
}
|
|
}
|
|
|
|
static string TranslateExtension(string extension)
|
|
{
|
|
extension = extension.ToUpper();
|
|
if (extension.Length > 2)
|
|
{
|
|
extension = extension[0] + extension.Substring(1).ToLower();
|
|
}
|
|
return extension;
|
|
}
|
|
|
|
void TranslateExtension(Delegate d)
|
|
{
|
|
d.Extension = TranslateExtension(d.Extension);
|
|
}
|
|
|
|
static string GetTrimmedExtension(string name, string extension)
|
|
{
|
|
// Extensions are always uppercase
|
|
int index = name.LastIndexOf(extension.ToUpper());
|
|
if (index >= 0)
|
|
{
|
|
name = name.Remove(index);
|
|
}
|
|
return name;
|
|
}
|
|
|
|
// Trims unecessary suffices from the specified OpenGL function name.
|
|
static string GetTrimmedName(Delegate d)
|
|
{
|
|
string name = d.Name;
|
|
string extension = d.Extension;
|
|
string trimmed_name = GetTrimmedExtension(name, extension);
|
|
|
|
// Note: some endings should not be trimmed, for example: 'b' from Attrib.
|
|
// Check the endingsNotToTrim regex for details.
|
|
Match m = EndingsNotToTrim.Match(trimmed_name);
|
|
if ((m.Index + m.Length) != trimmed_name.Length)
|
|
{
|
|
m = Endings.Match(trimmed_name);
|
|
|
|
if (m.Length > 0 && m.Index + m.Length == trimmed_name.Length)
|
|
{
|
|
// Only trim endings, not internal matches.
|
|
if (m.Value[m.Length - 1] == 'v' && EndingsAddV.IsMatch(name) &&
|
|
!name.StartsWith("Get") && !name.StartsWith("MatrixIndex"))
|
|
{
|
|
// Only trim ending 'v' when there is a number
|
|
trimmed_name = trimmed_name.Substring(0, m.Index) + "v";
|
|
}
|
|
else
|
|
{
|
|
if (!trimmed_name.EndsWith("xedv"))
|
|
{
|
|
trimmed_name = trimmed_name.Substring(0, m.Index);
|
|
}
|
|
else
|
|
{
|
|
trimmed_name = trimmed_name.Substring(0, m.Index + 1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return trimmed_name;
|
|
}
|
|
|
|
static XPathNodeIterator GetFuncOverload(XPathNavigator nav, Delegate d, string apiname, string apiversion)
|
|
{
|
|
// Try a few different extension variations that appear in the overrides xml file
|
|
string[] extensions = { d.Extension, TranslateExtension(d.Extension), d.Extension.ToUpper() };
|
|
string trimmed_name = GetTrimmedName(d);
|
|
XPathNodeIterator function_overload = null;
|
|
|
|
foreach (var ext in extensions)
|
|
{
|
|
string extensionless_name = GetTrimmedExtension(d.Name, ext);
|
|
function_overload = nav.Select(GetOverloadsPath(apiname, apiversion, d.Name, ext));
|
|
if (function_overload.Count != 0)
|
|
break;
|
|
function_overload = nav.Select(GetOverloadsPath(apiname, apiversion, extensionless_name, ext));
|
|
if (function_overload.Count != 0)
|
|
break;
|
|
function_overload = nav.Select(GetOverloadsPath(apiname, apiversion, trimmed_name, ext));
|
|
if (function_overload.Count != 0)
|
|
break;
|
|
}
|
|
return function_overload;
|
|
}
|
|
|
|
static XPathNavigator GetFuncOverride(XPathNavigator nav, Delegate d, string apiname, string apiversion)
|
|
{
|
|
// Try a few different extension variations that appear in the overrides xml file
|
|
string[] extensions = { d.Extension, TranslateExtension(d.Extension), d.Extension.ToUpper() };
|
|
string trimmed_name = GetTrimmedName(d);
|
|
XPathNavigator function_override = null;
|
|
|
|
foreach (var ext in extensions)
|
|
{
|
|
string extensionless_name = GetTrimmedExtension(d.Name, ext);
|
|
function_override =
|
|
nav.SelectSingleNode(GetOverridesPath(apiname, apiversion, d.Name, ext)) ??
|
|
nav.SelectSingleNode(GetOverridesPath(apiname, apiversion, extensionless_name, ext)) ??
|
|
nav.SelectSingleNode(GetOverridesPath(apiname, apiversion, trimmed_name, ext));
|
|
|
|
if (function_override != null)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
return function_override;
|
|
}
|
|
|
|
void TrimName(Function f)
|
|
{
|
|
f.TrimmedName = GetTrimmedName(f);
|
|
}
|
|
|
|
static void ApplyParameterReplacement(Delegate d, XPathNavigator function_override)
|
|
{
|
|
if (function_override != null)
|
|
{
|
|
for (int i = 0; i < d.Parameters.Count; i++)
|
|
{
|
|
XPathNavigator param_override = function_override.SelectSingleNode(String.Format(
|
|
"param[@name='{0}' or @index='{1}']",
|
|
d.Parameters[i].RawName,
|
|
i));
|
|
if (param_override != null)
|
|
{
|
|
foreach (XPathNavigator node in param_override.SelectChildren(XPathNodeType.Element))
|
|
{
|
|
switch (node.Name)
|
|
{
|
|
case "type":
|
|
d.Parameters[i].CurrentType = (string)node.TypedValue;
|
|
break;
|
|
case "name":
|
|
d.Parameters[i].Name = (string)node.TypedValue;
|
|
break;
|
|
case "flow":
|
|
d.Parameters[i].Flow = Parameter.GetFlowDirection((string)node.TypedValue);
|
|
break;
|
|
case "count":
|
|
int count;
|
|
if (Int32.TryParse(node.Value, out count))
|
|
d.Parameters[i].ElementCount = count;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void ApplyReturnTypeReplacement(Delegate d, XPathNavigator function_override)
|
|
{
|
|
if (function_override != null)
|
|
{
|
|
XPathNavigator return_override = function_override.SelectSingleNode("returns");
|
|
if (return_override != null)
|
|
{
|
|
d.ReturnType.CurrentType = return_override.Value;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Translates the opengl return type to the equivalent C# type.
|
|
//
|
|
// First, we use the official typemap (gl.tm) to get the correct type.
|
|
// Then we override this, when it is:
|
|
// 1) A string (we have to use Marshal.PtrToStringAnsi, to avoid heap corruption)
|
|
// 2) An array (translates to IntPtr)
|
|
// 3) A generic object or void* (translates to IntPtr)
|
|
// 4) A GLenum (translates to int on Legacy.Tao or GL.Enums.GLenum otherwise).
|
|
// Return types must always be CLS-compliant, because .Net does not support overloading on return types.
|
|
void TranslateReturnType(Delegate d,
|
|
XPathNavigator function_override, XPathNavigator nav,
|
|
EnumProcessor enum_processor, EnumCollection enums,
|
|
string apiname, string apiversion)
|
|
{
|
|
ApplyReturnTypeReplacement(d, function_override);
|
|
|
|
TranslateType(d.ReturnType, function_override, nav, enum_processor, enums, d.Category, apiname);
|
|
|
|
if (d.ReturnType.CurrentType.ToLower() == "void" && d.ReturnType.Pointer != 0)
|
|
{
|
|
d.ReturnType.QualifiedType = "IntPtr";
|
|
d.ReturnType.Pointer--;
|
|
d.ReturnType.WrapperType |= WrapperTypes.GenericReturnType;
|
|
}
|
|
|
|
if (d.ReturnType.CurrentType.ToLower() == "string")
|
|
{
|
|
d.ReturnType.QualifiedType = "IntPtr";
|
|
d.ReturnType.WrapperType |= WrapperTypes.StringReturnType;
|
|
}
|
|
|
|
if (d.ReturnType.CurrentType.ToLower() == "object")
|
|
{
|
|
d.ReturnType.QualifiedType = "IntPtr";
|
|
d.ReturnType.WrapperType |= WrapperTypes.GenericReturnType;
|
|
}
|
|
|
|
if (d.ReturnType.CurrentType.Contains("GLenum"))
|
|
{
|
|
if ((Settings.Compatibility & Settings.Legacy.ConstIntEnums) == Settings.Legacy.None)
|
|
d.ReturnType.QualifiedType = String.Format("{0}{1}{2}",
|
|
Settings.EnumsOutput, Settings.NamespaceSeparator, Settings.CompleteEnumName);
|
|
else
|
|
d.ReturnType.QualifiedType = "int";
|
|
}
|
|
|
|
if (d.ReturnType.CurrentType.ToLower().Contains("bool"))
|
|
{
|
|
d.ReturnType.QualifiedType = "byte";
|
|
d.ReturnType.WrapperType |= WrapperTypes.BoolParameter;
|
|
}
|
|
|
|
d.ReturnType.CurrentType = GetCLSCompliantType(d.ReturnType);
|
|
}
|
|
|
|
Delegate GetCLSCompliantDelegate(Delegate d)
|
|
{
|
|
Delegate f = new Delegate(d);
|
|
|
|
for (int i = 0; i < f.Parameters.Count; i++)
|
|
{
|
|
f.Parameters[i].CurrentType = GetCLSCompliantType(f.Parameters[i]);
|
|
}
|
|
|
|
f.ReturnType.CurrentType = GetCLSCompliantType(f.ReturnType);
|
|
|
|
return f;
|
|
}
|
|
|
|
void TranslateParameters(Delegate d,
|
|
XPathNavigator function_override, XPathNavigator nav,
|
|
EnumProcessor enum_processor, EnumCollection enums,
|
|
string apiname, string apiversion)
|
|
{
|
|
ApplyParameterReplacement(d, function_override);
|
|
|
|
for (int i = 0; i < d.Parameters.Count; i++)
|
|
{
|
|
TranslateParameter(d.Parameters[i], function_override, nav, enum_processor, enums, d.Category, apiname);
|
|
if (d.Parameters[i].CurrentType == "UInt16" && d.Name.Contains("LineStipple"))
|
|
d.Parameters[i].WrapperType |= WrapperTypes.UncheckedParameter;
|
|
}
|
|
}
|
|
|
|
void TranslateParameter(Parameter p,
|
|
XPathNavigator function_override, XPathNavigator overrides,
|
|
EnumProcessor enum_processor, EnumCollection enums,
|
|
string category, string apiname)
|
|
{
|
|
TranslateType(p, function_override, overrides, enum_processor, enums, category, apiname);
|
|
|
|
// Translate char* -> string. This simplifies the rest of the logic below
|
|
if (p.CurrentType.ToLower().Contains("char") && p.Pointer > 0)
|
|
{
|
|
p.CurrentType = "string";
|
|
p.Pointer--;
|
|
}
|
|
|
|
// Find out the necessary wrapper types.
|
|
if (p.CurrentType.ToLower() == "string" && p.Pointer == 0)
|
|
{
|
|
// char* -> IntPtr
|
|
// Due to a bug in the Mono runtime, we need
|
|
// to marshal [out] string parameters ourselves.
|
|
// StringBuilder crashes at runtime.
|
|
// For symmetry, and to avoid potential runtime bugs,
|
|
// we will also marshal [in] string types manually.
|
|
p.QualifiedType = "IntPtr";
|
|
p.WrapperType |= WrapperTypes.StringParameter;
|
|
}
|
|
|
|
if (p.CurrentType.ToLower() == "string" && p.Pointer >= 1)
|
|
{
|
|
// string* -> [In] String[]
|
|
// [Out] StringBuilder[] parameter is not currently supported
|
|
// Higher indirection levels are also not supported
|
|
if (p.Flow == FlowDirection.Out)
|
|
{
|
|
throw new NotSupportedException("[Out] String* parameters are not currently supported.");
|
|
}
|
|
if (p.Pointer >= 2)
|
|
{
|
|
throw new NotSupportedException("String arrays with arity >= 2 are not currently supported.");
|
|
}
|
|
|
|
p.QualifiedType = "IntPtr";
|
|
p.Pointer = 0;
|
|
p.Array = 0;
|
|
p.WrapperType |= WrapperTypes.StringArrayParameter;
|
|
}
|
|
|
|
if (p.Pointer > 0 && p.WrapperType == 0)
|
|
{
|
|
if (p.QualifiedType.ToLower().StartsWith("void"))
|
|
{
|
|
p.QualifiedType = "IntPtr";
|
|
p.Pointer = 0; // Generic parameters cannot have pointers
|
|
p.WrapperType |= WrapperTypes.GenericParameter;
|
|
p.WrapperType |= WrapperTypes.ArrayParameter;
|
|
p.WrapperType |= WrapperTypes.ReferenceParameter;
|
|
}
|
|
else
|
|
{
|
|
p.WrapperType |= WrapperTypes.ArrayParameter;
|
|
p.WrapperType |= WrapperTypes.ReferenceParameter;
|
|
p.WrapperType |= WrapperTypes.PointerParameter;
|
|
}
|
|
}
|
|
|
|
if (Utilities.Keywords(Settings.Language).Contains(p.Name))
|
|
p.Name = Settings.KeywordEscapeCharacter + p.Name;
|
|
|
|
// This causes problems with bool arrays
|
|
//if (CurrentType.ToLower().Contains("bool"))
|
|
// WrapperType = WrapperTypes.BoolParameter;
|
|
}
|
|
|
|
void TranslateAttributes(Delegate d,
|
|
XPathNavigator function_override, XPathNavigator nav,
|
|
string apiname, string apiversion)
|
|
{
|
|
if (function_override != null)
|
|
{
|
|
var version_override = function_override.SelectSingleNode("version");
|
|
if (version_override != null)
|
|
{
|
|
d.Version = version_override.Value;
|
|
}
|
|
|
|
var profile_override = function_override.SelectSingleNode("profile");
|
|
if (profile_override != null)
|
|
{
|
|
Debug.Print("Profile override not yet implemented");
|
|
}
|
|
|
|
var name_override = function_override.SelectSingleNode("name");
|
|
if (name_override != null)
|
|
{
|
|
d.Name = name_override.Value;
|
|
}
|
|
|
|
var obsolete = function_override.GetAttribute("obsolete", String.Empty);
|
|
if (!String.IsNullOrEmpty(obsolete))
|
|
{
|
|
d.Obsolete = obsolete;
|
|
}
|
|
}
|
|
}
|
|
|
|
FunctionCollection CreateWrappers(DelegateCollection delegates, EnumCollection enums)
|
|
{
|
|
var wrappers = new FunctionCollection();
|
|
foreach (var d in delegates.Values.SelectMany(v => v))
|
|
{
|
|
wrappers.AddRange(CreateNormalWrappers(d, enums));
|
|
}
|
|
|
|
if ((Settings.Compatibility & Settings.Legacy.KeepUntypedEnums) != 0)
|
|
{
|
|
// Generate an "All" overload for every function that takes strongly-typed enums
|
|
var overloads = new List<Function>();
|
|
foreach (var list in wrappers.Values)
|
|
{
|
|
overloads.AddRange(list.Where(f => f.Parameters.Any(p => p.IsEnum)).Select(f =>
|
|
{
|
|
var fnew = new Function(f);
|
|
fnew.Obsolete = "Use strongly-typed overload instead";
|
|
// Note that we can only overload parameters, not the return type
|
|
foreach (var p in fnew.Parameters)
|
|
{
|
|
if (p.IsEnum)
|
|
{
|
|
p.CurrentType = Settings.CompleteEnumName;
|
|
}
|
|
}
|
|
|
|
return fnew;
|
|
}));
|
|
}
|
|
wrappers.AddRange(overloads);
|
|
}
|
|
return wrappers;
|
|
}
|
|
|
|
FunctionCollection CreateCLSCompliantWrappers(FunctionCollection functions, EnumCollection enums)
|
|
{
|
|
// If the function is not CLS-compliant (e.g. it contains unsigned parameters)
|
|
// we need to create a CLS-Compliant overload. However, we should only do this
|
|
// iff the opengl function does not contain unsigned/signed overloads itself
|
|
// to avoid redefinitions.
|
|
var wrappers = new FunctionCollection();
|
|
foreach (var list in functions.Values)
|
|
{
|
|
foreach (var f in list)
|
|
{
|
|
wrappers.AddChecked(f);
|
|
|
|
if (!f.CLSCompliant)
|
|
{
|
|
// The return type must always be cls-compliant,
|
|
// since we cannot overload on return types alone.
|
|
f.ReturnType.CurrentType = GetCLSCompliantType(f.ReturnType);
|
|
|
|
// Create a cls-compliant wrapper for the parameters
|
|
Function cls = new Function(f);
|
|
bool modified = false;
|
|
for (int i = 0; i < f.Parameters.Count; i++)
|
|
{
|
|
cls.Parameters[i].CurrentType = GetCLSCompliantType(cls.Parameters[i]);
|
|
if (cls.Parameters[i].CurrentType != f.Parameters[i].CurrentType)
|
|
modified = true;
|
|
}
|
|
|
|
// Only add a cls-compliant overload if we have
|
|
// changed a parameter.
|
|
if (modified)
|
|
{
|
|
wrappers.AddChecked(cls);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return wrappers;
|
|
}
|
|
|
|
static FunctionCollection MarkCLSCompliance(FunctionCollection collection)
|
|
{
|
|
//foreach (var w in
|
|
// (from list in collection
|
|
// from w1 in list.Value
|
|
// from w2 in list.Value
|
|
// where
|
|
// w1.TrimmedName == w2.TrimmedName &&
|
|
// w1.Parameters.Count == w2.Parameters.Count &&
|
|
// ParametersDifferOnlyInReference(w1.Parameters, w2.Parameters)
|
|
// select !w1.Parameters.HasReferenceParameters ? w1 : w2))
|
|
// {
|
|
// results.Add(w);
|
|
// }
|
|
|
|
foreach (List<Function> wrappers in collection.Values)
|
|
{
|
|
var must_remove = new List<int>();
|
|
|
|
for (int i = 0; i < wrappers.Count; i++)
|
|
{
|
|
for (int j = i + 1; j < wrappers.Count; j++)
|
|
{
|
|
if (wrappers[i].TrimmedName == wrappers[j].TrimmedName && wrappers[i].Parameters.Count == wrappers[j].Parameters.Count)
|
|
{
|
|
bool function_i_is_problematic = false;
|
|
bool function_j_is_problematic = false;
|
|
|
|
int k;
|
|
for (k = 0; k < wrappers[i].Parameters.Count; k++)
|
|
{
|
|
if (wrappers[i].Parameters[k].CurrentType != wrappers[j].Parameters[k].CurrentType)
|
|
break;
|
|
|
|
if (wrappers[i].Parameters[k].DiffersOnlyOnReference(wrappers[j].Parameters[k]))
|
|
if (wrappers[i].Parameters[k].Reference)
|
|
function_i_is_problematic = true;
|
|
else
|
|
function_j_is_problematic = true;
|
|
}
|
|
|
|
if (k == wrappers[i].Parameters.Count)
|
|
{
|
|
if (function_i_is_problematic)
|
|
must_remove.Add(i);
|
|
if (function_j_is_problematic)
|
|
must_remove.Add(j);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int count = 0;
|
|
must_remove.Sort();
|
|
foreach (var i in must_remove)
|
|
{
|
|
// Careful: whenever we remove a function, the total count
|
|
// is reduced. We must account for that, or we will remove
|
|
// the wrong function!
|
|
wrappers.RemoveAt(i - count);
|
|
count++;
|
|
}
|
|
}
|
|
return collection;
|
|
}
|
|
|
|
string GetCLSCompliantType(Type type)
|
|
{
|
|
if (!type.CLSCompliant)
|
|
{
|
|
if (type.Pointer != 0 && Settings.Compatibility == Settings.Legacy.Tao)
|
|
return "IntPtr";
|
|
|
|
switch (type.CurrentType)
|
|
{
|
|
case "UInt16":
|
|
case "ushort":
|
|
return "Int16";
|
|
case "UInt32":
|
|
case "uint":
|
|
return "Int32";
|
|
case "UInt64":
|
|
case "ulong":
|
|
return "Int64";
|
|
case "SByte":
|
|
case "sbyte":
|
|
return "Byte";
|
|
case "UIntPtr":
|
|
return "IntPtr";
|
|
}
|
|
}
|
|
|
|
return type.CurrentType;
|
|
}
|
|
|
|
IEnumerable<Function> CreateNormalWrappers(Delegate d, EnumCollection enums)
|
|
{
|
|
Function f = new Function(d);
|
|
TrimName(f);
|
|
|
|
WrapReturnType(f);
|
|
foreach (var wrapper in WrapParameters(f, enums))
|
|
{
|
|
yield return wrapper;
|
|
}
|
|
}
|
|
|
|
IEnumerable<Function> CreateConvenienceOverloads(FunctionCollection wrappers)
|
|
{
|
|
var convenience_wrappers = new List<Function>();
|
|
foreach (var d in wrappers.Values.SelectMany(w => w))
|
|
{
|
|
if (d.Parameters.Count > 0 && d.Parameters.Count <= 2)
|
|
{
|
|
var p = d.Parameters.Last();
|
|
var r = d.ReturnType;
|
|
|
|
var name = d.Name;
|
|
|
|
bool is_candidate = true;
|
|
is_candidate &=
|
|
name.StartsWith("Get") || name.StartsWith("Gen") ||
|
|
name.StartsWith("Delete") || name.StartsWith("New");
|
|
is_candidate &= p.Pointer > 0;
|
|
// if there is a specific count set, such as "4", then this function
|
|
// returns a vector of specific dimensions and it would be wrong
|
|
// to generate an overload that returns a value of different size.
|
|
is_candidate &= p.ElementCount == 0 || p.ElementCount == 1;
|
|
is_candidate &= r.CurrentType == "void" && r.Pointer == 0;
|
|
|
|
Function f = null;
|
|
if (is_candidate && p.Flow == FlowDirection.Out)
|
|
{
|
|
// Match Gen*|Get*|New*([Out] int[] names) methods
|
|
f = CreateReturnTypeConvenienceWrapper(d);
|
|
}
|
|
else if (is_candidate && p.Flow != FlowDirection.Out)
|
|
{
|
|
// Match *Delete(int count, int[] names) methods
|
|
if (d.Parameters.Count == 2)
|
|
{
|
|
f = CreateArrayReturnTypeConvenienceWrapper(d);
|
|
}
|
|
}
|
|
|
|
if (f != null)
|
|
{
|
|
// If we have a convenience overload, we should turn its name from plural into singular
|
|
f.TrimmedName = f.TrimmedName.Replace("Queries", "Query").TrimEnd('s');
|
|
|
|
convenience_wrappers.Add(f);
|
|
}
|
|
}
|
|
|
|
// Check for IntPtr parameters that correspond to size_t (e.g. GLsizei)
|
|
// and add Int32 overloads for convenience.
|
|
{
|
|
Function f = null;
|
|
int i = 0;
|
|
foreach (var p in d.Parameters)
|
|
{
|
|
if ((p.WrapperType & WrapperTypes.SizeParameter) != 0)
|
|
{
|
|
f = f ?? new Function(d);
|
|
f.Parameters[i].QualifiedType = "Int32";
|
|
}
|
|
i++;
|
|
}
|
|
|
|
if (f != null)
|
|
{
|
|
convenience_wrappers.Add(f);
|
|
}
|
|
}
|
|
}
|
|
return convenience_wrappers;
|
|
}
|
|
|
|
static Function CreateReturnTypeConvenienceWrapper(Function d)
|
|
{
|
|
var f = new Function(d);
|
|
f.ReturnType = new Type(f.Parameters.Last());
|
|
f.ReturnType.Pointer = 0;
|
|
f.Parameters.RemoveAt(f.Parameters.Count - 1);
|
|
f.ReturnType.WrapperType |= WrapperTypes.ConvenienceReturnType;
|
|
|
|
if (f.Parameters.Count > 0)
|
|
{
|
|
var p_size = f.Parameters.Last();
|
|
if (p_size.CurrentType.ToLower().StartsWith("int") && p_size.Pointer == 0)
|
|
{
|
|
f.Parameters.RemoveAt(f.Parameters.Count - 1);
|
|
f.ReturnType.WrapperType |= WrapperTypes.ConvenienceArrayReturnType;
|
|
}
|
|
}
|
|
return f;
|
|
}
|
|
|
|
static Function CreateArrayReturnTypeConvenienceWrapper(Function d)
|
|
{
|
|
var f = new Function(d);
|
|
var p_array = f.Parameters.Last();
|
|
var p_size = f.Parameters[f.Parameters.Count - 2];
|
|
f.Parameters.RemoveAt(f.Parameters.Count - 2);
|
|
p_array.WrapperType |= WrapperTypes.ConvenienceArrayType;
|
|
// Since this is a 1-element overload, we don't need
|
|
// array or reference wrappers.
|
|
p_array.WrapperType &= ~(
|
|
WrapperTypes.ReferenceParameter |
|
|
WrapperTypes.ArrayParameter);
|
|
p_array.Array = p_array.Pointer = 0;
|
|
p_array.Reference = false;
|
|
return f;
|
|
}
|
|
|
|
List<Function> GetWrapper(IDictionary<WrapperTypes, List<Function>> dictionary, WrapperTypes key, Function raw)
|
|
{
|
|
if (!dictionary.ContainsKey(key))
|
|
{
|
|
dictionary.Add(key, new List<Function>());
|
|
if (raw != null)
|
|
{
|
|
dictionary[key].Add(new Function(raw));
|
|
}
|
|
}
|
|
return dictionary[key];
|
|
}
|
|
|
|
public IEnumerable<Function> WrapParameters(Function func, EnumCollection enums)
|
|
{
|
|
if (func.Parameters.Count == 0)
|
|
{
|
|
// Functions without parameters do not need
|
|
// parameter wrappers
|
|
yield return func;
|
|
yield break;
|
|
}
|
|
|
|
var wrappers = new Dictionary<WrapperTypes, List<Function>>();
|
|
for (int i = 0; i < func.Parameters.Count; i++)
|
|
{
|
|
var parameter = func.Parameters[i];
|
|
|
|
// Handle all non-generic parameters first.
|
|
// Generics are handled in a second pass.
|
|
if ((parameter.WrapperType & WrapperTypes.GenericParameter) == 0)
|
|
{
|
|
if ((parameter.WrapperType & WrapperTypes.ArrayParameter) != 0)
|
|
{
|
|
foreach (var wrapper in GetWrapper(wrappers, WrapperTypes.ArrayParameter, func))
|
|
{
|
|
var p = wrapper.Parameters[i];
|
|
|
|
if (p.ElementCount == 1)
|
|
{
|
|
p.Reference = true;
|
|
}
|
|
else
|
|
{
|
|
p.Array++;
|
|
}
|
|
p.Pointer--;
|
|
}
|
|
}
|
|
|
|
if ((parameter.WrapperType & WrapperTypes.ReferenceParameter) != 0)
|
|
{
|
|
foreach (var wrapper in GetWrapper(wrappers, WrapperTypes.ReferenceParameter, func))
|
|
{
|
|
var p = wrapper.Parameters[i];
|
|
|
|
p.Reference = true;
|
|
p.Pointer--;
|
|
}
|
|
}
|
|
|
|
if ((parameter.WrapperType & WrapperTypes.PointerParameter) != 0)
|
|
{
|
|
foreach (var wrapper in GetWrapper(wrappers, WrapperTypes.PointerParameter, func))
|
|
{
|
|
var p = wrapper.Parameters[i];
|
|
|
|
if (Settings.IsEnabled(Settings.Legacy.NoPublicUnsafeFunctions))
|
|
{
|
|
p.QualifiedType = "IntPtr";
|
|
p.Pointer = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (parameter.WrapperType == 0 ||
|
|
(parameter.WrapperType & WrapperTypes.ConvenienceArrayType) != 0 ||
|
|
(parameter.WrapperType & WrapperTypes.ConvenienceReturnType) != 0 ||
|
|
(parameter.WrapperType & WrapperTypes.ConvenienceArrayReturnType) != 0)
|
|
{
|
|
// We don't need to do anything, just add this function directly
|
|
// to the list of wrappers.
|
|
GetWrapper(wrappers, parameter.WrapperType, func);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Handle generic parameters
|
|
if (wrappers.Count == 0)
|
|
{
|
|
// Some functions, such as VDPAUInit, only have generic parameters
|
|
// This means no wrapper has been generated by any of the previous
|
|
// transformations. Since the generic translation below operates on
|
|
// existing wrappers, add one here to get the process started.
|
|
wrappers.Add(WrapperTypes.None, new List<Function> { new Function(func) });
|
|
}
|
|
var list = new List<Function>();
|
|
foreach (var wrapper in wrappers.Values.SelectMany(v => v))
|
|
{
|
|
// Add generic 'ref T' wrapper
|
|
Function generic_wrapper = null;
|
|
for (int i = 0; i < wrapper.Parameters.Count; i++)
|
|
{
|
|
var parameter = wrapper.Parameters[i];
|
|
if ((parameter.WrapperType & WrapperTypes.GenericParameter) != 0)
|
|
{
|
|
generic_wrapper = generic_wrapper ?? new Function(wrapper);
|
|
var p = generic_wrapper.Parameters[i];
|
|
|
|
p.Reference = true;
|
|
p.Pointer = 0;
|
|
p.Array = 0;
|
|
p.Generic = true;
|
|
p.QualifiedType = "T" + i.ToString();
|
|
p.Flow = FlowDirection.Undefined;
|
|
}
|
|
}
|
|
if (generic_wrapper != null)
|
|
{
|
|
list.Add(generic_wrapper);
|
|
}
|
|
|
|
// Add the following wrappers:
|
|
// 'IntPtr', 'T[]', 'T[,]' and 'T[,,]'
|
|
for (int arity = 0; arity < 4; arity++)
|
|
{
|
|
generic_wrapper = null;
|
|
for (int i = 0; i < wrapper.Parameters.Count; i++)
|
|
{
|
|
var parameter = wrapper.Parameters[i];
|
|
if ((parameter.WrapperType & WrapperTypes.GenericParameter) != 0)
|
|
{
|
|
generic_wrapper = generic_wrapper ?? new Function(wrapper);
|
|
if (arity > 0)
|
|
{
|
|
// Overloading on array arity is not CLS-compliant
|
|
generic_wrapper.CLSCompliant = false;
|
|
}
|
|
var p = generic_wrapper.Parameters[i];
|
|
|
|
p.Reference = false;
|
|
p.Pointer = 0;
|
|
p.Array = arity;
|
|
if (arity == 0)
|
|
{
|
|
p.QualifiedType = "IntPtr";
|
|
}
|
|
else
|
|
{
|
|
p.Generic = true;
|
|
p.QualifiedType = "T" + i.ToString();
|
|
p.Flow = FlowDirection.Undefined;
|
|
}
|
|
}
|
|
}
|
|
if (generic_wrapper != null)
|
|
{
|
|
list.Add(generic_wrapper);
|
|
}
|
|
}
|
|
}
|
|
GetWrapper(wrappers, WrapperTypes.GenericParameter, null)
|
|
.AddRange(list);
|
|
|
|
// Handle string parameters
|
|
foreach (var wrapper in wrappers.Values.SelectMany(v => v))
|
|
{
|
|
for (int i = 0; i < wrapper.Parameters.Count; i++)
|
|
{
|
|
var p = wrapper.Parameters[i];
|
|
if ((p.WrapperType & WrapperTypes.StringParameter) != 0)
|
|
{
|
|
if (p.Flow == FlowDirection.Out)
|
|
{
|
|
p.QualifiedType = "StringBuilder";
|
|
}
|
|
else
|
|
{
|
|
p.QualifiedType = "String";
|
|
}
|
|
}
|
|
|
|
if ((p.WrapperType & WrapperTypes.StringArrayParameter) != 0)
|
|
{
|
|
if (p.Flow == FlowDirection.Out)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
else
|
|
{
|
|
p.QualifiedType = "String";
|
|
p.Pointer = 0;
|
|
p.Array = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Return all generated wrappers
|
|
foreach (var w in wrappers.Values.SelectMany(v => v).OrderBy(v => v))
|
|
{
|
|
yield return w;
|
|
}
|
|
}
|
|
|
|
static void WrapReturnType(Function func)
|
|
{
|
|
if ((func.ReturnType.WrapperType & WrapperTypes.StringReturnType) != 0)
|
|
{
|
|
func.ReturnType.QualifiedType = "String";
|
|
}
|
|
|
|
if ((func.ReturnType.WrapperType & WrapperTypes.GenericReturnType) != 0)
|
|
{
|
|
// Nothing else we can do, using generics will break the runtime
|
|
func.ReturnType.QualifiedType = "IntPtr";
|
|
}
|
|
|
|
if ((func.ReturnType.WrapperType & WrapperTypes.BoolParameter) != 0)
|
|
{
|
|
func.ReturnType.QualifiedType = "bool";
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|