Reloading native plugins

Hi

I’m developing a plugin for a project. To give a bit of context its a tool for generating assets in the editor, and needs c++ to voxelise efficiently. However, I’ve rapidly hit upon the issue that once Unity loads a DLL, it won’t unload (or even release) it without restarting Unity. This makes development of the native plugin insanely slow, as in order to make a change and test it I have to change, close unity, build, open unity, test etc. Not a nice dev cycle!

Has anybody hit upon this and found any nice solutions. The best I can think of is to use an approach like this one:

http://blogs.msdn.com/b/jonathanswift/archive/2006/10/03/dynamically-calling-an-unmanaged-dll-from-.net-_2800_c_23002900_.aspx

Loading the DLL dynamically when in the editor. I could even take temporary copies and detect when the source DLL reloaded, giving me a ‘live update’ style system.

However that’d mean defining the function linkage differently depending on whether you were in editor or not, which is a pain!

Any thoughts?

Thanks

-Chris

There is a (Windows only) blog post for a work-around: http://runningdimensions.com/blog/?p=5

–

The thing you want to instantiate is not assigned a value. Just assign it to something. I suppose I should edit my answer.

–

3 Answers

3

There is a (Windows only) blog post for a work-around: http://runningdimensions.com/blog/?p=5

Yeah that was the best solution I could think of. Was hoping somebody might have hit upon an approach that didn't require different code paths for when you were modifying the plugin vs just building the game, but I can fully believe there isn't one.

–

Replying to save answer, good post and good answer.

–

Here is a tool that quite easly allows just that. You don’t have to modify your code, just simply add script to your scene.

Its nearly 2017 and this is still a huge pain. I couldnt find a solution that worked on OSX but I cobbled one together from a few places (sorry, lost track of the original links). Requires more boilerplate than Dllimport but at least it works.

class FunctionLoader
	{
		public static Delegate LoadFunction<T>(string dllPath, string functionName)
		{
			var hModule = dl_native.LoadLibrary(dllPath);
			handles.Add(hModule);
			var functionAddress = dl_native.GetProcAddress(hModule, functionName);
			return Marshal.GetDelegateForFunctionPointer(functionAddress, typeof (T));
		}
			
		// tracking what we loaded for easier cleanup
		static List<IntPtr> handles = new List<IntPtr>();

		public static void FreeLibraries()
		{
			foreach (var ptr in handles)
			{
				Debug.Log("Cleaning up module " + ptr);
				// todo: check if ptr was -1 or something bad like that...
				dl_native.FreeLibrary(ptr);
			}
		}

		////////////////////////////////////////////////////////////

	}


	class dl_native
	{
		
		public static IntPtr LoadLibrary(string fileName) {
			return dlopen(fileName, RTLD_NOW);
		}

		public static void FreeLibrary(IntPtr handle) {
			dlclose(handle);
		}

		public static IntPtr GetProcAddress(IntPtr dllHandle, string name) {
			// clear previous errors if any
			dlerror();
			var res = dlsym(dllHandle, name);
			var errPtr = dlerror();
			if (errPtr != IntPtr.Zero) {
				throw new Exception("dlsym: " + Marshal.PtrToStringAnsi(errPtr));
			}
			return res;
		}

		const int RTLD_NOW = 2;

		[DllImport("libdl.dylib")]
		private static extern IntPtr dlopen(String fileName, int flags);

		[DllImport("libdl.dylib")]
		private static extern IntPtr dlsym(IntPtr handle, String symbol);

		[DllImport("libdl.dylib")]
		private static extern int dlclose(IntPtr handle);

		[DllImport("libdl.dylib")]
		private static extern IntPtr dlerror();

	}

	       static List<string> temp_libs = new List<string>();
	       static string get_unique_library_instance(string orig_path)
		{
			var unique_path = Path.GetTempFileName() + "_" + Path.GetFileName(orig_path);

			File.Copy(orig_path, unique_path);

			Debug.Log("Copied " + orig_path + " to " + unique_path);
			temp_libs.Add(unique_path);

			return unique_path;
			// make sure we clean up later
		}

		void OnApplicationQuit()
		{
			foreach (var p in temp_libs)
			{
				Debug.Log("Cleaning up " + p);
				File.Delete(p);
			}

			FunctionLoader.FreeLibraries();

		}
	
            const string interface_lib = "path_to_your_dylib";

		private delegate void DebugCallback(string message);

		[UnmanagedFunctionPointerAttribute(CallingConvention.Cdecl)]
		private delegate void RegisterDebugCallback_t(DebugCallback cb);
		static RegisterDebugCallback_t RegisterDebugCallback;

		[UnmanagedFunctionPointerAttribute(CallingConvention.Cdecl)]
		private delegate void test_t();
		static test_t test;

		void Awake()
		{
			var libpath = get_unique_library_instance(interface_lib);
			test = (test_t)FunctionLoader.LoadFunction<test_t>(libpath, "test");
			RegisterDebugCallback = (RegisterDebugCallback_t)FunctionLoader.LoadFunction<RegisterDebugCallback_t>(libpath, "RegisterDebugCallback");

			Debug.Log("Loaded functions from " + libpath);
		}

it's incredible that it has to be SO complicated...

–

I'v noticed that, when application quit, you'v free the loaded library. But when i use lsof or the system monitor to view opened files, the loaded library still there. Have you meet this issue? If yes, how did you solved it?

–

In the end you will need to reassign Navigation back to the Button either way, because Navigation is a struct. Otherwise, the change will be lost (the struct is just a copy). Here's a one-liner solution: button.navigation = new Navigation() { mode = Navigation.Mode.None };

–

In the end you will need to reassign Navigation back to the Button either way, because Navigation is a struct. Otherwise, the change will be lost (the struct is just a copy). Here's a one-liner solution: button.navigation = new Navigation() { mode = Navigation.Mode.None };

–