[Audio] Don't crash when Alc.GetString() returns null
authorthefiddler <stapostol@gmail.com>
Sun, 12 Jan 2014 21:05:15 +0000 (22:05 +0100)
committerthefiddler <stapostol@gmail.com>
Sun, 12 Jan 2014 21:05:15 +0000 (22:05 +0100)
Alc.GetString() could crash if the unmanaged code returned null due to
any kind of failure. This is now fixed and better documented.

Additionally, the array overload for Alc.GetString() will now correctly
forward the ‘device’ parameter to unmanaged code.

Source/OpenTK/Audio/OpenAL/Alc/Alc.cs

index 38767a1..371bd81 100644 (file)
@@ -9,6 +9,7 @@
 
 using System;
 using System.Collections.Generic;
+using System.Diagnostics;
 using System.Runtime.InteropServices;
 using System.Security;
 
@@ -262,7 +263,13 @@ namespace OpenTK.Audio.OpenAL
         /// <returns>A string containing the name of the Device.</returns>
         public static string GetString(IntPtr device, AlcGetString param)
         {
-            return Marshal.PtrToStringAnsi(GetStringPrivate(device, param));
+            IntPtr pstr = GetStringPrivate(device, param);
+            string str = String.Empty;
+            if (pstr != IntPtr.Zero)
+            {
+                str = Marshal.PtrToStringAnsi(pstr);
+            }
+            return str;
         }
 
         /// <summary>This function returns a List of strings related to the context.</summary>
@@ -277,26 +284,54 @@ namespace OpenTK.Audio.OpenAL
         public static IList<string> GetString(IntPtr device, AlcGetStringList param)
         {
             List<string> result = new List<string>();
-            IntPtr t = GetStringPrivate(IntPtr.Zero, (AlcGetString)param);
-            System.Text.StringBuilder sb = new System.Text.StringBuilder();
-            byte b;
-            int offset = 0;
-            do
+
+            // We cannot use Marshal.PtrToStringAnsi(),
+            //  because alcGetString is defined to return either a nul-terminated string,
+            //  or an array of nul-terminated strings terminated by an extra nul.
+            // Marshal.PtrToStringAnsi() will fail in the latter case (it will only
+            // return the very first string in the array.)
+            // We'll have to marshal this ourselves.
+            IntPtr t = GetStringPrivate(device, (AlcGetString)param);
+            if (t != IntPtr.Zero)
             {
-                b = Marshal.ReadByte(t, offset++);
-                if (b != 0)
-                    sb.Append((char)b);
-                if (b == 0)
+                System.Text.StringBuilder sb = new System.Text.StringBuilder();
+                byte b;
+                int offset = 0;
+                do
                 {
-                    result.Add(sb.ToString());
-                    if (Marshal.ReadByte(t, offset) == 0) // offset already properly increased through ++
-                        break; // 2x null
+                    b = Marshal.ReadByte(t, offset++);
+                    if (b != 0)
+                    {
+                        sb.Append((char)b);
+                    }
                     else
-                        sb.Remove(0, sb.Length); // 1x null
+                    {
+                        // One string from the array is complete
+                        result.Add(sb.ToString());
+
+                        // Check whether the array has finished
+                        // Note: offset already been increased through offset++ above
+                        if (Marshal.ReadByte(t, offset) == 0)
+                        {
+                            // 2x consecutive nuls, we've read the whole array
+                            break;
+                        }
+                        else
+                        {
+                            // Another string is starting, clear the StringBuilder
+                            sb.Remove(0, sb.Length);
+                        }
+                    }
                 }
-            } while (true);
+                while (true);
+            }
+            else
+            {
+                Debug.Print("[Audio] Alc.GetString({0}, {1}) returned null.",
+                    device, param);
+            }
 
-            return (IList<string>)result;
+            return result;
         }
 
         [DllImport(Alc.Lib, EntryPoint = "alcGetIntegerv", ExactSpelling = true, CallingConvention = Alc.Style, CharSet = CharSet.Ansi), SuppressUnmanagedCodeSecurity()]