#region --- License --- /* Copyright (c) 2006, 2007 Stefanos Apostolopoulos * See license.txt for license info */ #endregion using System; using System.Collections.Generic; using System.Text; using System.Runtime.InteropServices; using System.Diagnostics; using System.IO; namespace Bind.Structures { /// /// Represents an opengl function. /// The return value, function name, function parameters and opengl version can be retrieved or set. /// public class Delegate { internal static DelegateCollection Delegates; private static bool delegatesLoaded; #region internal static void Initialize(string glSpec, string glSpecExt) internal static void Initialize(string glSpec, string glSpecExt) { if (!delegatesLoaded) { using (StreamReader sr = Utilities.OpenSpecFile(Settings.InputPath, glSpec)) { Delegates = Bind.MainClass.Generator.ReadDelegates(sr); } if (!String.IsNullOrEmpty(glSpecExt)) { using (StreamReader sr = Utilities.OpenSpecFile(Settings.InputPath, glSpecExt)) { foreach (Delegate d in Bind.MainClass.Generator.ReadDelegates(sr).Values) { Utilities.Merge(Delegates, d); } } } delegatesLoaded = true; } } #endregion #region --- Constructors --- public Delegate() { Parameters = new ParameterCollection(); } public Delegate(Delegate d) { this.Category = d.Category; this.Name = d.Name; this.Parameters = new ParameterCollection(d.Parameters); this.ReturnType = new Type(d.ReturnType); this.Version = d.Version; //this.Version = !String.IsNullOrEmpty(d.Version) ? new string(d.Version.ToCharArray()) : ""; } #endregion #region --- Properties --- #region public bool CLSCompliant /// /// Gets the CLSCompliant property. True if the delegate is not CLSCompliant. /// public bool CLSCompliant { get { if (Unsafe) return false; if (!ReturnType.CLSCompliant) return false; foreach (Parameter p in Parameters) { if (!p.CLSCompliant) return false; } return true; } } #endregion #region public string Category private string _category; public string Category { get { return _category; } set { _category = value; } } #endregion #region public bool NeedsWrapper /// /// Indicates whether this function needs to be wrapped with a Marshaling function. /// This flag is set if a function contains an Array parameter, or returns /// an Array or string. /// public bool NeedsWrapper { //get { return _needs_wrapper; } //set { _needs_wrapper = value; } get { // TODO: Add special cases for (Get)ShaderSource. if (ReturnType.WrapperType != WrapperTypes.None) return true; foreach (Parameter p in Parameters) { if (p.WrapperType != WrapperTypes.None) return true; } return false; } } #endregion #region public virtual bool Unsafe /// /// True if the delegate must be declared as 'unsafe'. /// public virtual bool Unsafe { //get { return @unsafe; } //set { @unsafe = value; } get { if (ReturnType.Pointer) return true; foreach (Parameter p in Parameters) { if (p.Pointer) { return true; } } return false; } } #endregion #region public Parameter ReturnType Type _return_type = new Type(); /// /// Gets or sets the return value of the opengl function. /// public Type ReturnType { get { return _return_type; } set { _return_type = Type.Translate(value); } } #endregion #region public virtual string Name string _name; /// /// Gets or sets the name of the opengl function. /// public virtual string Name { get { return _name; } set { if (!String.IsNullOrEmpty(value)) { _name = value.Trim(); } } } #endregion #region public ParameterCollection Parameters ParameterCollection _parameters; public ParameterCollection Parameters { get { return _parameters; } protected set { _parameters = value; } } #endregion #region public string Version string _version; /// /// Defines the opengl version that introduced this function. /// public string Version { get { return _version; } set { _version = value; } } #endregion #region public bool Extension string _extension; public string Extension { //get { return _extension; } //set { _extension = value; } get { if (!String.IsNullOrEmpty(Name)) { _extension = Utilities.GetGL2Extension(Name); return String.IsNullOrEmpty(_extension) ? "Core" : _extension; } else { return null; } } } #endregion #endregion #region --- Strings --- #region public string CallString() public string CallString() { StringBuilder sb = new StringBuilder(); sb.Append(Settings.DelegatesClass); sb.Append("."); sb.Append(Settings.FunctionPrefix); sb.Append(Name); sb.Append(Parameters.CallString()); return sb.ToString(); } #endregion #region public string DeclarationString() public string DeclarationString() { StringBuilder sb = new StringBuilder(); sb.Append(Unsafe ? "unsafe " : ""); sb.Append(ReturnType); sb.Append(" "); sb.Append(Name); sb.Append(Parameters.ToString()); return sb.ToString(); } #endregion #region override public string ToString() /// /// Gets the string representing the full function declaration without decorations /// (ie "void glClearColor(float red, float green, float blue, float alpha)" /// override public string ToString() { StringBuilder sb = new StringBuilder(); sb.Append(Unsafe ? "unsafe " : ""); sb.Append("delegate "); sb.Append(ReturnType); sb.Append(" "); sb.Append(Name); sb.Append(Parameters.ToString()); return sb.ToString(); } #endregion public Delegate GetCLSCompliantDelegate() { Delegate f = new Delegate(this); for (int i = 0; i < f.Parameters.Count; i++) { f.Parameters[i].CurrentType = f.Parameters[i].GetCLSCompliantType(); } f.ReturnType.CurrentType = f.ReturnType.GetCLSCompliantType(); return f; } #endregion #region --- Wrapper Creation --- #region public IEnumerable CreateWrappers() public void CreateWrappers() { if (this.Name.Contains("EndList")) { } List wrappers = new List(); if (!NeedsWrapper) { // No special wrapper needed - just call this delegate: Function f = new Function(this); if (f.ReturnType.CurrentType.ToLower().Contains("void")) f.Body.Add(String.Format("{0};", f.CallString())); else f.Body.Add(String.Format("return {0};", f.CallString())); wrappers.Add(f); } else { Function f = WrapReturnType(); WrapParameters(new Function((Function)f ?? this), wrappers); } // 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. foreach (Function f in wrappers) { Bind.Structures.Function.Wrappers.AddChecked(f); //Bind.Structures.Function.Wrappers.Add(f); if (!f.CLSCompliant) { Function cls = new Function(f); cls.Body.Clear(); if (!cls.NeedsWrapper) { cls.Body.Add((f.ReturnType.CurrentType != "void" ? "return " + this.CallString() : this.CallString()) + ";"); } else { cls.Body.AddRange(this.CreateBody(cls, true)); } bool somethingChanged = false; for (int i = 0; i < f.Parameters.Count; i++) { cls.Parameters[i].CurrentType = cls.Parameters[i].GetCLSCompliantType(); if (cls.Parameters[i].CurrentType != f.Parameters[i].CurrentType) somethingChanged = true; } if (somethingChanged) Bind.Structures.Function.Wrappers.AddChecked(cls); } } } #endregion #region protected Function WrapReturnType() protected Function WrapReturnType() { // We have to add wrappers for all possible WrapperTypes. Function f; // First, check if the return type needs wrapping: switch (this.ReturnType.WrapperType) { // If the function returns a string (glGetString) we must manually marshal it // using Marshal.PtrToStringXXX. Otherwise, the GC will try to free the memory // used by the string, resulting in corruption (the memory belongs to the // unmanaged boundary). case WrapperTypes.StringReturnType: f = new Function(this); f.ReturnType.CurrentType = "System.String"; f.Body.Add( String.Format( "return System.Runtime.InteropServices.Marshal.PtrToStringAnsi({0});", this.CallString() ) ); return f; // Only occurs in glGetString, there's no need to check parameters. // If the function returns a void* (GenericReturnValue), we'll have to return an IntPtr. // The user will unfortunately need to marshal this IntPtr to a data type manually. case WrapperTypes.GenericReturnType: ReturnType.CurrentType = "IntPtr"; ReturnType.Pointer = false; break; case WrapperTypes.None: default: // No return wrapper needed break; } return null; } #endregion #region protected void WrapParameters(Function function, List wrappers) protected static int index; /// /// This function needs some heavy refactoring. I'm ashamed I ever wrote it, but it works... /// What it does is this: it adds to the wrapper list all possible wrapper permutations /// for functions that have more than one IntPtr parameter. Example: /// "void Delegates.f(IntPtr p, IntPtr q)" where p and q are pointers to void arrays needs the following wrappers: /// "void f(IntPtr p, IntPtr q)" /// "void f(IntPtr p, object q)" /// "void f(object p, IntPtr q)" /// "void f(object p, object q)" /// protected void WrapParameters(Function function, List wrappers) { if (index == 0) { if (function.Parameters.HasPointerParameters) { wrappers.Add(DefaultWrapper(function)); } //else if (containsReferenceParameters) //{ //} else { if (function.Body.Count == 0) wrappers.Add(DefaultWrapper(function)); else wrappers.Add(function); return; } } if (index >= 0 && index < function.Parameters.Count) { Function f; if (function.Parameters[index].WrapperType == WrapperTypes.None) { // No wrapper needed, visit the next parameter ++index; WrapParameters(function, wrappers); --index; } else { switch (function.Parameters[index].WrapperType) { case WrapperTypes.ArrayParameter: // Recurse to the last parameter ++index; WrapParameters(function, wrappers); --index; //if (function.Name == "UseFontOutlinesA") { } // On stack rewind, create array wrappers f = new Function(function); f.Parameters[index].Reference = false; f.Parameters[index].Array = 1; f.Parameters[index].Pointer = false; f.Body = CreateBody(f, false); //f = ReferenceWrapper(new Function(function), index); wrappers.Add(f); // Recurse to the last parameter again, keeping the Array wrappers ++index; WrapParameters(f, wrappers); --index; f = new Function(function); f.Parameters[index].Reference = true; f.Parameters[index].Array = 0; f.Parameters[index].Pointer = false; f.Body = CreateBody(f, false); //f = ReferenceWrapper(new Function(function), index); wrappers.Add(f); // Keeping the current Ref wrapper, visit all other parameters once more ++index; WrapParameters(f, wrappers); --index; break; case WrapperTypes.GenericParameter: // Recurse to the last parameter ++index; WrapParameters(function, wrappers); --index; // On stack rewind, create array wrappers f = new Function(function); f.Parameters[index].Reference = false; f.Parameters[index].Array = 0; f.Parameters[index].Pointer = false; f.Parameters[index].CurrentType = "object"; f.Parameters[index].Flow = Parameter.FlowDirection.Undefined; f.Body = CreateBody(f, false); wrappers.Add(f); // Keeping the current Object wrapper, visit all other parameters once more ++index; WrapParameters(f, wrappers); --index; break; case WrapperTypes.ReferenceParameter: // Recurse to the last parameter ++index; WrapParameters(function, wrappers); --index; // On stack rewind, create reference wrappers f = new Function(function); f.Parameters[index].Reference = true; f.Parameters[index].Array = 0; f.Parameters[index].Pointer = false; f.Body = CreateBody(f, false); //f = ReferenceWrapper(new Function(function), index); wrappers.Add(f); // Keeping the current Object wrapper, visit all other parameters once more ++index; WrapParameters(f, wrappers); --index; break; } } } } #endregion #region protected Function DefaultWrapper(Function f) protected Function DefaultWrapper(Function f) { bool returns = f.ReturnType.CurrentType.ToLower().Contains("void") && !f.ReturnType.Pointer; string callString = String.Format( "{0} {1}{2}; {3}", Unsafe ? "unsafe {" : "", returns ? "" : "return ", this.CallString(), Unsafe ? "}" : ""); f.Body.Add(callString); return f; } #endregion #region protected FunctionBody CreateBody(Function fun, bool wantCLSCompliance) static List handle_statements = new List(); static List handle_release_statements = new List(); static List fixed_statements = new List(); static List assign_statements = new List(); protected FunctionBody CreateBody(Function fun, bool wantCLSCompliance) { Function f = new Function(fun); f.Body.Clear(); handle_statements.Clear(); handle_release_statements.Clear(); fixed_statements.Clear(); assign_statements.Clear(); //if (f.Name == "LoadDisplayColorTableEXT") { } // Obtain pointers by pinning the parameters foreach (Parameter p in f.Parameters) { if (p.NeedsPin) { if (p.WrapperType == WrapperTypes.GenericParameter) { // Use GCHandle to obtain pointer to generic parameters and 'fixed' for arrays. // This is because fixed can only take the address of fields, not managed objects. handle_statements.Add(String.Format( "{0} {1}_ptr = {0}.Alloc({1}, System.Runtime.InteropServices.GCHandleType.Pinned);", "System.Runtime.InteropServices.GCHandle", p.Name)); handle_release_statements.Add(String.Format("{0}_ptr.Free();", p.Name)); if (p.Flow == Parameter.FlowDirection.Out) { assign_statements.Add(String.Format( " {0} = ({1}){0}_ptr.Target;", p.Name, p.CurrentType)); } // Note! The following line modifies f.Parameters, *not* this.Parameters p.Name = "(void*)" + p.Name + "_ptr.AddrOfPinnedObject()"; } else if (p.WrapperType == WrapperTypes.PointerParameter || p.WrapperType == WrapperTypes.ArrayParameter || p.WrapperType == WrapperTypes.ReferenceParameter) { // A fixed statement is issued for all non-generic pointers, arrays and references. fixed_statements.Add(String.Format( "fixed ({0}* {1} = {2})", wantCLSCompliance && !p.CLSCompliant ? p.GetCLSCompliantType() : p.CurrentType, p.Name + "_ptr", p.Array > 0 ? p.Name : "&" + p.Name)); if (p.Flow == Parameter.FlowDirection.Out && p.Array == 0) // Fixed Arrays of blittable types don't need explicit assignment. { assign_statements.Add(String.Format(" {0} = *{0}_ptr;", p.Name)); } p.Name = p.Name + "_ptr"; } else { throw new ApplicationException("Unknown parameter type"); } } } //if (!f.Unsafe && (fixed_statements.Count > 0 || fixed_statements.Count > 0)) { f.Body.Add("unsafe"); f.Body.Add("{"); f.Body.Indent(); } if (fixed_statements.Count > 0) { f.Body.AddRange(fixed_statements); f.Body.Add("{"); f.Body.Indent(); } if (handle_statements.Count > 0) { f.Body.AddRange(handle_statements); f.Body.Add("try"); f.Body.Add("{"); f.Body.Indent(); } if (f.ReturnType.CurrentType.ToLower().Contains("void")) { f.Body.Add(String.Format("{0};", f.CallString())); } else { f.Body.Add(String.Format("{0} {1} = {2};", f.ReturnType.CurrentType, "retval", f.CallString())); } if (assign_statements.Count > 0) { f.Body.AddRange(assign_statements); } // Return: if (!f.ReturnType.CurrentType.ToLower().Contains("void")) { f.Body.Add("return retval;"); } // Free all allocated GCHandles if (handle_statements.Count > 0) { f.Body.Unindent(); f.Body.Add("}"); f.Body.Add("finally"); f.Body.Add("{"); f.Body.Indent(); f.Body.AddRange(handle_release_statements); f.Body.Unindent(); f.Body.Add("}"); } if (fixed_statements.Count > 0) { f.Body.Unindent(); f.Body.Add("}"); } //if (!f.Unsafe && (fixed_statements.Count > 0 || fixed_statements.Count > 0)) { f.Body.Unindent(); f.Body.Add("}"); } return f.Body; } #endregion #endregion #region Translate /// /// Translates the opengl return type to the equivalent C# type. /// /// The opengl function to translate. /// /// 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. /// protected virtual void TranslateReturnType() { if (Bind.Structures.Type.GLTypes.ContainsKey(ReturnType.CurrentType)) ReturnType.CurrentType = Bind.Structures.Type.GLTypes[ReturnType.CurrentType]; if (Bind.Structures.Type.CSTypes.ContainsKey(ReturnType.CurrentType)) ReturnType.CurrentType = Bind.Structures.Type.CSTypes[ReturnType.CurrentType]; if (ReturnType.CurrentType.ToLower().Contains("void") && ReturnType.Pointer) { ReturnType.WrapperType = WrapperTypes.GenericReturnType; } if (ReturnType.CurrentType.ToLower().Contains("string")) { ReturnType.CurrentType = "IntPtr"; ReturnType.WrapperType = WrapperTypes.StringReturnType; } if (ReturnType.CurrentType.ToLower().Contains("object")) { ReturnType.CurrentType = "IntPtr"; ReturnType.WrapperType |= WrapperTypes.GenericReturnType; } if (ReturnType.CurrentType.Contains("GLenum")) { if (Settings.Compatibility == Settings.Legacy.None) ReturnType.CurrentType = String.Format("{0}.{1}", Settings.NormalEnumsClass, Settings.CompleteEnumName); else ReturnType.CurrentType = "int"; } if (ReturnType.CurrentType.ToLower().Contains("bool")) { // TODO: Is the translation to 'int' needed 100%? It breaks WGL. /* if (Settings.Compatibility == Settings.Legacy.Tao) { ReturnType.CurrentType = "int"; } else { } */ //ReturnType.WrapperType = WrapperTypes.ReturnsBool; } ReturnType.CurrentType = ReturnType.GetCLSCompliantType(); } protected virtual void TranslateParameters() { if (this.Name.Contains("SetLayerPaletteEntries")) { // Console.WriteLine(); } for (int i = 0; i < Parameters.Count; i++) { Parameters[i] = Parameter.Translate(Parameters[i], this.Category); // Special cases: glLineStipple and gl(Get)ShaderSource: // Check for LineStipple (should be unchecked) if (Parameters[i].CurrentType == "UInt16" && Name.Contains("LineStipple")) { Parameters[i].WrapperType = WrapperTypes.UncheckedParameter; } if (Name.Contains("ShaderSource") && Parameters[i].CurrentType.ToLower().Contains("string")) { // Special case: these functions take a string[] //IsPointer = true; Parameters[i].Array = 1; } } } internal void Translate() { TranslateReturnType(); TranslateParameters(); CreateWrappers(); } #endregion } #region class DelegateCollection : Dictionary class DelegateCollection : Dictionary { public void Add(Delegate d) { if (!this.ContainsKey(d.Name)) { this.Add(d.Name, d); } else { Trace.WriteLine(String.Format( "Spec error: function {0} redefined, ignoring second definition.", d.Name)); } } } #endregion }