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:
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!
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);
}