0

I have a C# program that loads an unmanaged C++ DLL with LoadLibrary and later unloads it with FreeLibrary. The application has been functioning normally and I'm able to load, unload, and file-level replace the DLL at will from within the C# application.

For some (new) reason, now when I FreeLibrary the DLL, it successfully unloads from C# (I get a positive return value from FreeLibrary), but the *.dll file is still locked by the file-system and I'm unable to update it newer versions (which was the whole reason for using dynamic loading).

The application uses Boost ASIO with Beast websockets, and the recent change I made was to switch to SSL. I figure there's something wrong with how I'm invoking or closing the SSL connection that's keeping the DLL locked by the filesystem. But since FreeLibrary returns success (and subsequent calls to FreeLibrary hang), I am unsure why the DLL file would still be locked.

Here's how I load the DLL in C# (snippets):

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern IntPtr LoadLibrary(string libname);

    [DllImport("kernel32.dll")]
    private static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName);

    [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
    private static extern bool FreeLibrary(IntPtr hModule);

    IntPtr Handle;

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    private delegate void Initdb();
    Initdb InitdbFunction;

Main

    Handle = LoadLibrary("Perks.dll");
    if (Handle == IntPtr.Zero) {
        int errorCode = Marshal.GetLastWin32Error();
        throw new Exception(string.Format("Failed to load library (ErrorCode: {0})", errorCode));
    }
    IntPtr funcaddr;

    funcaddr = GetProcAddress(Handle, "Initdb");
    InitdbFunction = Marshal.GetDelegateForFunctionPointer(funcaddr, typeof(Initdb)) as Initdb;

Unload function

    if (FreeLibrary(Handle))
    {
        Console.WriteLine("FreeLibrary true");
    }
    else
    {
        Console.WriteLine("FreeLibrary false");
    }
    Handle = IntPtr.Zero;

My new implementation of Boost ASIO and Beast websockets in the unmanaged DLL (which is probably causing the problem somehow) is here C++ (snippets)

    std::string                     wsm_SERVER_URL;
    boost::asio::io_service         wsm_ios;
    boost::asio::io_service::work   wsm_work{ wsm_ios };
    boost::asio::ip::tcp::resolver  wsm_r{ wsm_ios };
    boost::asio::ip::tcp::socket    wsm_sock{ wsm_ios };
    std::unique_ptr<beast::websocket::stream< boost::asio::ssl::stream<boost::asio::ip::tcp::socket>>> wsm_client;
    boost::asio::ssl::context wsm_ctx{ boost::asio::ssl::context::sslv23 };

Initialize

    wsm_client = std::make_unique<beast::websocket::stream<boost::asio::ssl::stream<boost::asio::ip::tcp::socket>>>(wsm_ios, wsm_ctx);

Use

    wsm_client = std::make_unique<beast::websocket::stream<boost::asio::ssl::stream<boost::asio::ip::tcp::socket>>>(wsm_ios, wsm_ctx);
    boost::asio::ip::tcp::resolver::iterator iter = wsm_r.resolve(boost::asio::ip::tcp::resolver::query{ wsm_SERVER_URL, "https" });
    const boost::asio::ip::tcp::endpoint EP = iter->endpoint();
    boost::system::error_code EC;
    wsm_client->next_layer().next_layer().connect(EP, EC);
    wsm_client->next_layer().set_verify_mode(boost::asio::ssl::verify_none);
    wsm_client->next_layer().handshake(boost::asio::ssl::stream_base::client);
    wsm_client->handshake(wsm_SERVER_URL, srv_url);

Deactivate (called in C++ just before C# does the FreeLibrary portion)

wsm_client->next_layer().next_layer().close();
wsm_client->next_layer().next_layer().cancel();
wsm_client->close(beast::websocket::close_code::normal);
wsm_client.reset();
wsm_ios.stop();
GenerationTech
  • 79
  • 1
  • 11
  • Try calling FreeLibrary until it fails. true means it just decremented ref count, but as long as it's above zero, dll won't be unloaded. – Jan Feb 06 '18 at 09:11
  • Also there are ways how dll unload can be prevented (e.g. if the dll installs hooks or if GetModuleHandleEx is called with GET_MODULE_HANDLE_EX_FLAG_PIN flag) - see https://stackoverflow.com/questions/48631560/freelibrary-returns-true-indefinitely for similar case – Jan Feb 06 '18 at 09:12
  • I eventually did find the root cause of this problem. The issue is using the shared dynamically-linked SSL library. Even though I release memory my DLL uses for interface to SSL library, the OS keeps a portion of my DLL locked in memory due to the way the SSL library initializes itself when called. From what I remember with my research, there's no good existing way to correct this other than statically linking the SSL source in my DLL, which is what I planned to do to mitigate this issue until I found a better solution, but I tabled the whole idea and just used a non-SSL connection for now. – GenerationTech Feb 07 '18 at 12:24
  • Thanks for responding back - interresting finding – Jan Feb 07 '18 at 13:11

0 Answers0