Browse Source

Dynamic library loader work

miguel 6 years ago
parent
commit
27c5468a10

+ 1 - 0
Terminal.Gui/Drivers/ConsoleDriver.cs

@@ -141,6 +141,7 @@ namespace Terminal.Gui {
 		/// The base color scheme, for the default toplevel views.
 		/// The base color scheme, for the default toplevel views.
 		/// </summary>
 		/// </summary>
 		public static ColorScheme Base;
 		public static ColorScheme Base;
+		
 		/// <summary>
 		/// <summary>
 		/// The dialog color scheme, for standard popup dialog boxes
 		/// The dialog color scheme, for standard popup dialog boxes
 		/// </summary>
 		/// </summary>

+ 262 - 0
Terminal.Gui/MonoCurses/UnmanagedLibrary.cs

@@ -0,0 +1,262 @@
+
+
+// Copyright 2015 gRPC authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+#define GUICS
+
+using System;
+using System.IO;
+using System.Reflection;
+using System.Runtime.InteropServices;
+using System.Threading;
+
+
+
+namespace Mono.Terminal.Internal {
+	/// <summary>
+	/// Represents a dynamically loaded unmanaged library in a (partially) platform independent manner.
+	/// First, the native library is loaded using dlopen (on Unix systems) or using LoadLibrary (on Windows).
+	/// dlsym or GetProcAddress are then used to obtain symbol addresses. <c>Marshal.GetDelegateForFunctionPointer</c>
+	/// transforms the addresses into delegates to native methods.
+	/// See http://stackoverflow.com/questions/13461989/p-invoke-to-dynamically-loaded-library-on-mono.
+	/// </summary>
+	internal class UnmanagedLibrary {
+		const string UnityEngineApplicationClassName = "UnityEngine.Application, UnityEngine";
+		const string XamarinAndroidObjectClassName = "Java.Lang.Object, Mono.Android";
+		const string XamarinIOSObjectClassName = "Foundation.NSObject, Xamarin.iOS";
+		static bool IsWindows, IsLinux, IsMacOS;
+		static bool Is64Bit;
+		static bool IsMono, IsUnity, IsXamarinIOS, IsXamarinAndroid, IsXamarin;
+		static bool IsNetCore;
+
+		public static bool IsMacOSPlatform => IsMacOS;
+		
+		[DllImport ("libc")]
+		static extern int uname (IntPtr buf);
+
+		static string GetUname ()
+		{
+			var buffer = Marshal.AllocHGlobal (8192);
+			try {
+				if (uname (buffer) == 0) {
+					return Marshal.PtrToStringAnsi (buffer);
+				}
+				return string.Empty;
+			} catch {
+				return string.Empty;
+			} finally {
+				if (buffer != IntPtr.Zero) {
+					Marshal.FreeHGlobal (buffer);
+				}
+			}
+		}
+
+		static UnmanagedLibrary ()
+		{
+			var platform = Environment.OSVersion.Platform;
+
+			IsMacOS = (platform == PlatformID.Unix && GetUname () == "Darwin");
+			IsLinux = (platform == PlatformID.Unix && !IsMacOS);
+			IsWindows = (platform == PlatformID.Win32NT || platform == PlatformID.Win32S || platform == PlatformID.Win32Windows);
+			Is64Bit = Marshal.SizeOf (typeof (IntPtr)) == 8;
+			IsMono = Type.GetType ("Mono.Runtime") != null;
+			if (!IsMono) {
+				IsNetCore = Type.GetType ("System.MathF") != null;
+			}
+#if GUICS
+			IsUnity = IsXamarinIOS = IsXamarinAndroid = IsXamarin = false;
+#else
+			IsUnity = Type.GetType (UnityEngineApplicationClassName) != null;
+			IsXamarinIOS = Type.GetType (XamarinIOSObjectClassName) != null;
+			IsXamarinAndroid = Type.GetType (XamarinAndroidObjectClassName) != null;
+			IsXamarin = IsXamarinIOS || IsXamarinAndroid;
+#endif
+
+		}
+
+		// flags for dlopen
+		const int RTLD_LAZY = 1;
+		const int RTLD_GLOBAL = 8;
+
+		readonly string libraryPath;
+		readonly IntPtr handle;
+
+		//
+		// if isFullPath is set to true, the provided array of libraries are full paths
+		// and are tested for the file existing, otherwise the file is merely the name
+		// of the shared library that we pass to dlopen
+		//
+		public UnmanagedLibrary (string [] libraryPathAlternatives, bool isFullPath)
+		{
+			if (isFullPath){
+				this.libraryPath = FirstValidLibraryPath (libraryPathAlternatives);
+				this.handle = PlatformSpecificLoadLibrary (this.libraryPath);
+			} else {
+				foreach (var lib in libraryPathAlternatives){
+					this.handle = PlatformSpecificLoadLibrary (lib);
+					if (this.handle != IntPtr.Zero)
+						break;
+				}
+			}
+
+			if (this.handle == IntPtr.Zero) {
+				throw new IOException (string.Format ("Error loading native library \"{0}\"", this.libraryPath));
+			}
+		}
+
+		/// <summary>
+		/// Loads symbol in a platform specific way.
+		/// </summary>
+		/// <param name="symbolName"></param>
+		/// <returns></returns>
+		private IntPtr LoadSymbol (string symbolName)
+		{s
+			if (IsWindows) {
+				// See http://stackoverflow.com/questions/10473310 for background on this.
+				if (Is64Bit) {
+					return Windows.GetProcAddress (this.handle, symbolName);
+				} else {
+					// Yes, we could potentially predict the size... but it's a lot simpler to just try
+					// all the candidates. Most functions have a suffix of @0, @4 or @8 so we won't be trying
+					// many options - and if it takes a little bit longer to fail if we've really got the wrong
+					// library, that's not a big problem. This is only called once per function in the native library.
+					symbolName = "_" + symbolName + "@";
+					for (int stackSize = 0; stackSize < 128; stackSize += 4) {
+						IntPtr candidate = Windows.GetProcAddress (this.handle, symbolName + stackSize);
+						if (candidate != IntPtr.Zero) {
+							return candidate;
+						}
+					}
+					// Fail.
+					return IntPtr.Zero;
+				}
+			}
+			if (IsLinux) {
+				if (IsMono) {
+					return Mono.dlsym (this.handle, symbolName);
+				}
+				if (IsNetCore) {
+					return CoreCLR.dlsym (this.handle, symbolName);
+				}
+				return Linux.dlsym (this.handle, symbolName);
+			}
+			if (IsMacOS) {
+				return MacOSX.dlsym (this.handle, symbolName);
+			}
+			throw new InvalidOperationException ("Unsupported platform.");
+		}
+
+		public T GetNativeMethodDelegate<T> (string methodName)
+		    where T : class
+		{
+			var ptr = LoadSymbol (methodName);
+			if (ptr == IntPtr.Zero) {
+				throw new MissingMethodException (string.Format ("The native method \"{0}\" does not exist", methodName));
+			}
+            		return Marshal.GetDelegateForFunctionPointer<T>(ptr);  // non-generic version is obsolete
+		}
+
+		/// <summary>
+		/// Loads library in a platform specific way.
+		/// </summary>
+		private static IntPtr PlatformSpecificLoadLibrary (string libraryPath)
+		{
+			if (IsWindows) {
+				return Windows.LoadLibrary (libraryPath);
+			}
+			if (IsLinux) {
+				if (IsMono) {
+					return Mono.dlopen (libraryPath, RTLD_GLOBAL + RTLD_LAZY);
+				}
+				if (IsNetCore) {
+					return CoreCLR.dlopen (libraryPath, RTLD_GLOBAL + RTLD_LAZY);
+				}
+				return Linux.dlopen (libraryPath, RTLD_GLOBAL + RTLD_LAZY);
+			}
+			if (IsMacOS) {
+				return MacOSX.dlopen (libraryPath, RTLD_GLOBAL + RTLD_LAZY);
+			}
+			throw new InvalidOperationException ("Unsupported platform.");
+		}
+
+		private static string FirstValidLibraryPath (string [] libraryPathAlternatives)
+		{
+			foreach (var path in libraryPathAlternatives) {
+				if (File.Exists (path)) {
+					return path;
+				}
+			}
+			throw new FileNotFoundException (
+			    String.Format ("Error loading native library. Not found in any of the possible locations: {0}",
+				string.Join (",", libraryPathAlternatives)));
+		}
+
+		private static class Windows
+		{
+			[DllImport ("kernel32.dll")]
+			internal static extern IntPtr LoadLibrary (string filename);
+
+			[DllImport ("kernel32.dll")]
+			internal static extern IntPtr GetProcAddress (IntPtr hModule, string procName);
+		}
+
+		private static class Linux
+		{
+			[DllImport ("libdl.so")]
+			internal static extern IntPtr dlopen (string filename, int flags);
+
+			[DllImport ("libdl.so")]
+			internal static extern IntPtr dlsym (IntPtr handle, string symbol);
+		}
+
+		private static class MacOSX
+		{
+			[DllImport ("libSystem.dylib")]
+			internal static extern IntPtr dlopen (string filename, int flags);
+
+			[DllImport ("libSystem.dylib")]
+			internal static extern IntPtr dlsym (IntPtr handle, string symbol);
+		}
+
+		/// <summary>
+		/// On Linux systems, using using dlopen and dlsym results in
+		/// DllNotFoundException("libdl.so not found") if libc6-dev
+		/// is not installed. As a workaround, we load symbols for
+		/// dlopen and dlsym from the current process as on Linux
+		/// Mono sure is linked against these symbols.
+		/// </summary>
+		private static class Mono
+		{
+			[DllImport ("__Internal")]
+			internal static extern IntPtr dlopen (string filename, int flags);
+
+			[DllImport ("__Internal")]
+			internal static extern IntPtr dlsym (IntPtr handle, string symbol);
+		}
+
+		/// <summary>
+		/// Similarly as for Mono on Linux, we load symbols for
+		/// dlopen and dlsym from the "libcoreclr.so",
+		/// to avoid the dependency on libc-dev Linux.
+		/// </summary>
+		private static class CoreCLR
+		{
+			[DllImport ("libcoreclr.so")]
+			internal static extern IntPtr dlopen (string filename, int flags);
+
+			[DllImport ("libcoreclr.so")]
+			internal static extern IntPtr dlsym (IntPtr handle, string symbol);
+		}
+	}
+}

+ 121 - 0
Terminal.Gui/MonoCurses/binding.cs

@@ -40,6 +40,121 @@ using System.IO;
 using System.Runtime.InteropServices;
 using System.Runtime.InteropServices;
 
 
 namespace Unix.Terminal {
 namespace Unix.Terminal {
+	internal class Delegates {
+		public delegate IntPtr initscr ();
+		public delegate int endwin ();
+		public delegate bool isendwin ();
+		public delegate int cbreak ();
+		public delegate int nocbreak ();
+		public delegate int echo ();
+		public delegate int noecho ();
+		public delegate int halfdelay (int t);
+		public delegate int raw ();
+		public delegate int noraw ();
+		public delegate void noqiflush ();
+		public delegate void qiflush ();
+		public delegate int typeahead (IntPtr fd);
+		public delegate int timeout (int delay);
+		public delegate int wtimeout (IntPtr win, int delay);
+		public delegate int notimeout (IntPtr win, bool bf);
+		public delegate int keypad (IntPtr win, bool bf);
+		public delegate int meta (IntPtr win, bool bf);
+		public delegate int intrflush (IntPtr win, bool bf);
+		public delegate int clearok (IntPtr win, bool bf);
+		public delegate int idlok (IntPtr win, bool bf);
+		public delegate void idcok (IntPtr win, bool bf);
+		public delegate void immedok (IntPtr win, bool bf);
+		public delegate int leaveok (IntPtr win, bool bf);
+		public delegate int wsetscrreg (IntPtr win, int top, int bot);
+		public delegate int scrollok (IntPtr win, bool bf);
+		public delegate int nl();
+		public delegate int nonl();
+		public delegate int setscrreg (int top, int bot);
+		public delegate int refresh ();
+		public delegate int doupdate();
+		public delegate int wrefresh (IntPtr win);
+		public delegate int redrawwin (IntPtr win);
+		public delegate int wredrawwin (IntPtr win, int beg_line, int num_lines);
+		public delegate int wnoutrefresh (IntPtr win);
+		public delegate int move (int line, int col);
+		public delegate int addch (int ch);
+		public delegate int addstr (string s);
+		public delegate int wmove (IntPtr win, int line, int col);
+		public delegate int waddch (IntPtr win, int ch);
+		public delegate int attron (int attrs);
+		public delegate int attroff (int attrs);
+		public delegate int attrset (int attrs);
+		public delegate int getch ();
+		public delegate int get_wch (out int sequence);
+		public delegate int ungetch (int ch);
+		public delegate int mvgetch (int y, int x);
+		public delegate bool has_colors ();
+		public delegate int start_color ();
+		public delegate int init_pair (short pair, short f, short b);
+		public delegate int use_default_colors ();
+		public delegate int COLOR_PAIRS();
+		public delegate uint getmouse (out MouseEvent ev);
+		public delegate uint ungetmouse (ref MouseEvent ev);
+		public delegate int mouseinterval (int interval);
+	}
+
+	internal class NativeMethods {
+		public readonly Delegates.initscr initscr;
+		public readonly Delegates.endwin endwin;
+		public readonly Delegates.isendwin isendwin;
+		public readonly Delegates.cbreak cbreak;
+		public readonly Delegates.nocbreak nocbreak;
+		public readonly Delegates.echo echo;
+		public readonly Delegates.noecho noecho;
+		public readonly Delegates.halfdelay halfdelay;
+		public readonly Delegates.raw raw;
+		public readonly Delegates.noraw noraw;
+		public readonly Delegates.noqiflush noqiflush;
+		public readonly Delegates.qiflush qiflush;
+		public readonly Delegates.typeahead typeahead;
+		public readonly Delegates.timeout timeout;
+		public readonly Delegates.wtimeout wtimeout;
+		public readonly Delegates.notimeout notimeout;
+		public readonly Delegates.keypad keypad;
+		public readonly Delegates.meta meta;
+		public readonly Delegates.intrflush intrflush;
+		public readonly Delegates.clearok clearok;
+		public readonly Delegates.idlok idlok;
+		public readonly Delegates.idcok idcok;
+		public readonly Delegates.immedok immedok;
+		public readonly Delegates.leaveok leaveok;
+		public readonly Delegates.wsetscrreg wsetscrreg;
+		public readonly Delegates.scrollok scrollok;
+		public readonly Delegates.nl nl;
+		public readonly Delegates.nonl nonl;
+		public readonly Delegates.setscrreg setscrreg;
+		public readonly Delegates.refresh refresh;
+		public readonly Delegates.doupdate doupdate;
+		public readonly Delegates.wrefresh wrefresh;
+		public readonly Delegates.redrawwin redrawwin;
+		public readonly Delegates.wredrawwin wredrawwin;
+		public readonly Delegates.wnoutrefresh wnoutrefresh;
+		public readonly Delegates.move move;
+		public readonly Delegates.addch addch;
+		public readonly Delegates.addstr addstr;
+		public readonly Delegates.wmove wmove;
+		public readonly Delegates.waddch waddch;
+		public readonly Delegates.attron attron;
+		public readonly Delegates.attroff attroff;
+		public readonly Delegates.attrset attrset;
+		public readonly Delegates.getch getch;
+		public readonly Delegates.get_wch get_wch;
+		public readonly Delegates.ungetch ungetch;
+		public readonly Delegates.mvgetch mvgetch;
+		public readonly Delegates.has_colors has_colors;
+		public readonly Delegates.start_color start_color;
+		public readonly Delegates.init_pair init_pair;
+		public readonly Delegates.use_default_colors use_default_colors;
+		public readonly Delegates.COLOR_PAIR COLOR_PAIR;
+		public readonly Delegates.getmouse getmouse;
+		public readonly Delegates.ungetmouse ungetmouse;
+		public readonly Delegates.mouseinterval mouseinterval;
+	}
 
 
 	internal partial class Curses {
 	internal partial class Curses {
 		[StructLayout (LayoutKind.Sequential)]
 		[StructLayout (LayoutKind.Sequential)]
@@ -56,6 +171,12 @@ namespace Unix.Terminal {
 		// If true, uses the DllImport into "ncurses", otherwise "libncursesw.so.5"
 		// If true, uses the DllImport into "ncurses", otherwise "libncursesw.so.5"
 		static bool use_naked_driver;
 		static bool use_naked_driver;
 
 
+		static void LoadMethods ()
+		{
+			var libs = UnmanagedLibrary.IsMacOSPlatform ? new string [] { "libncurses.dylib" } : new string { "libncursesw.so.6", "libncursesw.so.5" };
+			var lib = new UnmanagedLibrary (libs);
+		}
+		
 		//
 		//
 		// Ugly hack to P/Invoke into either libc, or libdl, again, because
 		// Ugly hack to P/Invoke into either libc, or libdl, again, because
 		// we can not have nice things - .NET Core in this day and age still
 		// we can not have nice things - .NET Core in this day and age still

+ 4 - 0
Terminal.Gui/Terminal.Gui.csproj

@@ -6,6 +6,7 @@
     <DebugType>full</DebugType>
     <DebugType>full</DebugType>
     <DocumentationFile>bin\Release\Terminal.Gui.xml</DocumentationFile>
     <DocumentationFile>bin\Release\Terminal.Gui.xml</DocumentationFile>
     <GenerateDocumentationFile Condition=" '$(Configuration)' == 'Release' ">true</GenerateDocumentationFile>
     <GenerateDocumentationFile Condition=" '$(Configuration)' == 'Release' ">true</GenerateDocumentationFile>
+    <TargetFramework>net47</TargetFramework>
   </PropertyGroup>
   </PropertyGroup>
   <PropertyGroup>
   <PropertyGroup>
     <GeneratePackageOnBuild Condition=" '$(Configuration)' == 'Release' ">true</GeneratePackageOnBuild>
     <GeneratePackageOnBuild Condition=" '$(Configuration)' == 'Release' ">true</GeneratePackageOnBuild>
@@ -64,4 +65,7 @@
       <HintPath>..\..\..\Users\miguel\.nuget\packages\nstack.core\0.11.0\lib\netstandard1.5\NStack.dll</HintPath>
       <HintPath>..\..\..\Users\miguel\.nuget\packages\nstack.core\0.11.0\lib\netstandard1.5\NStack.dll</HintPath>
     </Reference>
     </Reference>
   </ItemGroup>
   </ItemGroup>
+  <ItemGroup>
+    <None Remove="Drivers\#ConsoleDriver.cs#" />
+  </ItemGroup>
 </Project>
 </Project>