libgit2 / libgit2sharp

Git + .NET = ❤
http://libgit2.github.com
MIT License
3.16k stars 888 forks source link

Question regarding releasing git2.dll resources, and then reloading them. #341

Closed XaeroDegreaz closed 11 years ago

XaeroDegreaz commented 11 years ago

I'm using this library in a Unity project. For those unfamiliar with Unity, when it loads a plugin (git2.dll, in this case) it stays loaded in a write-locked stated until you restart the editor, or make changes to your assembly.

Unity has a nifty feature which basically reloads the assembly when it detects modifications from the outside, however I get random crashes when it tries to do a reload of the git2.dll. It's tricky to pin-point where it's coming from, but I'm sure it has to do with the dll file being locked in some way, and Unity not being able to release it fast enough, causing an Access Violation.

In any case, I've stumbled across LoadLibrary() and FreeLibrary() methods found in kernel32.dll which seem to be a step in the right direction. However, FreeLibrary() takes an IntPtr parameter that references the aforementioned loaded library, and I see nowhere in the NativeMethods class where there is any such reference made (presumably because this is a cross-platform lib, and kernell32.dll is Windows only).

Basically, I'm trying to figure out the best way to load the plugin manually, so that I can keep a reference, and then unload the plugin after select libgit2 calls, so Unity doesn't have a reason to crash when making changes to the assembly after the plugin has been loaded.

Is this even possible without completely hacking deeply into the LibGit2Sharp code, and custom writing methods to call functions on manually loaded library references? Is this even the right word?

I apologize for my ignorance, but I've never actually dealt so closely with programming at this low of a level. If there's any other information that I could provide to explain my intentions further, please let me know.

nulltoken commented 11 years ago

Just so we're on the same page, are you referring to Unity (Game Engine) or Unity (DI container)?

XaeroDegreaz commented 11 years ago

Sorry, Unity the game engine.

I think I've found out how to achieve what I'm trying to do. It's extremely ugly, but it's the only way, that I've found, that give me full control of the dll. Here's a snippet

        /*[DllImport(libgit2)]
        internal static extern int git_repository_open(
            out RepositorySafeHandle repository,
            [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(FilePathMarshaler))] FilePath path);*/

        [DllImport( "kernel32.dll" )]
        public static extern IntPtr LoadLibrary( [In, MarshalAs( UnmanagedType.LPStr )] string dll = "Assets\\Plugins\\git2.dll" );

        [DllImport( "kernel32.dll", CharSet = CharSet.Ansi)]
        public static extern IntPtr GetProcAddress(IntPtr module, string method );

        [DllImport( "kernel32.dll" )]
        public static extern bool FreeLibrary( IntPtr module);

        private delegate int d_git_repository_open( 
            out RepositorySafeHandle repository,
            [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( FilePathMarshaler ) )] FilePath path 
        );
        internal static int git_repository_open( out RepositorySafeHandle repository, string path ) {
            IntPtr module;
            IntPtr addr = StartLoad( out module, "_git_repository_open@8" );

            d_git_repository_open d = (d_git_repository_open)Marshal.GetDelegateForFunctionPointer( addr, typeof( d_git_repository_open ) );

            int result = d( out repository, path );

            FreeLibrary( module );

            return result;
        }

        private static IntPtr StartLoad( out IntPtr module, string method ) {
            module = LoadLibrary();
            return GetProcAddress( module, method );
        }

It pains me terribly to hack a perfectly good API like this, but as long as the same functionality is achieved, and I can be in full control of the dll, then I guess I have to do it this way.

This resolution, interestingly is linked to my question on StackOverflow on which I'll update my answer in a minute.

Regardless, if you know a better way that I can achieve what I'm trying, that would be awesome, and so much less work =)

nulltoken commented 11 years ago

For those unfamiliar with Unity, when it loads a plugin (git2.dll, in this case) it stays loaded in a write-locked stated until you restart the editor, or make changes to your assembly. [...] Unity has a nifty feature which basically reloads the assembly when it detects modifications from the outside, however I get random crashes when it tries to do a reload of the git2.dll. It's tricky to pin-point where it's coming from, but I'm sure it has to do with the dll file being locked in some way, and Unity not being able to release it fast enough, causing an Access Violation.

@joncham Are there any best practices regarding interop and native libraries when working with Unity?

/cc @xpaulbettsx @phkelley

joncham commented 11 years ago

I'm just guessing what is happening here. Unity frequently reloads application domains in the Editor. We do this anytime the user changes a script. We've put a lot of work into correctly tearing down the managed code, but I am guessing the native libraries loaded via pinvokes are not correctly unloaded at appdomain unload. Statics are per domain, so any sort of initialization checks based on static variables would rerun. However, the native library may have already been initialized leading to errors.

XaeroDegreaz commented 11 years ago

@joncham Thanks for the information. I've been somewhat successful with the method of loading and unloading the DLL at will, but I can definitely notice a performance decrease. Also, a couple of the methods used in the NativeMethods.cs class don't seem to be exported for use, as far as dumpbin shows me. That's another subject, and I really hate having to do it this way in the first place.

I guess the real question is, with the way that LibGit2Sharp does it, would it make much of difference if I reduce all of my static variables to nil (I think I've done an OK job already, but there are some leftover, for sure) or is there a sufficient many within the LibGit2Sharp library to cause the sort of crash I'm talking about on its own?

Edit: I've removed all static properties from all of my classes but still get:

git2.dll caused an Access Violation (0xc0000005) in module git2.dll at 0023:6ccdd06b. fairly regularly (I can tell it's gonna happen when the assembly starts compiling it's stiff and the little progress wheel in the bottom right freezes). I guess this is just an unavoidable issue, and I'll just have to stay the course with using my hacked NativeMethods.cs. Took me a couple hours to replace everything, so may as well use it.

Thanks for all of the input.