Plugins: Init and Uninit functions note

If you are writing plugins (anyone else doing that? :slight_smile:) there are some important points about the Init and Uninit functions which are overlooked by the current SDK docs.

When implementing DVP_Init/DVP_InitEx/DVP_Uninit in a viewer plugin, and VFS_Init/VFS_Uninit in a virtual filesystem plugin, you need to keep a reference count of how many times Init has been called vs Uninit.
[ul][li]Be prepared for nested pairs of calls to the Init/Uninit functions. That is, Init may be called when you are already initialised.[/li]
[li]Be prepared for more than one thread to use those functions.[/li]
[li]Be prepared to be fully uninitialised and then re-initialised again later.[/li][/ul]In the example code below, each time Init is called it increments a counter. If the counter was at zero before then the real init code is run; otherwise nothing else happens. In turn, when Uninit is called the counter is decreased. If the counter has reached zero again then the real uninit code is run. If another Init call comes in then it will trigger initialisation again as if the plugin was being used for the first time.

The only guarantee about the number of Init/Uninit calls is that they will, eventually, cancel each other out.

The example below uses critical sections to make itself thread-safe since the Init/Uninit calls don't always come from the same thread.

Also note that if your Init function fails then it is your responsibility to clean-up whatever it created. Opus won't call Uninit in that situation (which makes sense as it has no way of knowing that your Uninit function can properly clean-up an Init that aborted half-way through). You can call your own Uninit function (as in the example below) if it's suitably written. Either way, remember that a failed Init should not leave the reference counter increased.

[code]static int s_refCount = 0;
static CRITICAL_SECTION s_cs; // Initialised/Deleted by DllMain

BOOL DVP_Init()
{
CriticalSectionScoper css(&s_cs);

BOOL bInitSuccess = FALSE;

if (0 < s_refCount++)
{
	bInitSuccess = TRUE;
}
else
{
	// ...Your init code here...

	if (!bInitSuccess)
	{
		// If our Uninit function can work out what was allocated
		// by itself, even if Init failed half-way through, then we
		// can simply call it here to clean up our mess.
		DVP_Uninit();
	}
}

return bInitSuccess;

}

void DVP_Uninit(void)
{
CriticalSectionScoper css(&s_cs);

if (0 < s_refCount)
{
	if (0 == --s_refCount)
	{
		// ...Your clean-up code here...
	}
}

}
[/code]

CriticalSectionScoper is not really important, but here it is anyway. It's a tiny class that enters a critical section and automatically releases it when the object goes out of scope:

class CriticalSectionScoper { public: CriticalSectionScoper(CRITICAL_SECTION *pCS) : m_pCS(pCS) { EnterCriticalSection(m_pCS); } /*not virtual*/ ~CriticalSectionScoper() { LeaveCriticalSection(m_pCS); } void Lock() const { EnterCriticalSection(m_pCS); } void Unlock() const { LeaveCriticalSection(m_pCS); } private: // Disallow copying CriticalSectionScoper(const CriticalSectionScoper &rhs); CriticalSectionScoper &operator=(const CriticalSectionScoper &rhs); private: CRITICAL_SECTION *m_pCS; };

Here is the DllMain, again for completeness:

[code]static HMODULE s_hDllModule = NULL;

BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
switch(ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
// If DLL_PROCESS_ATTACH fails it should clean up everything it did
// as DllMain won't be called again after such a failure.
InitializeCriticalSection(&s_cs);
s_hDllModule = reinterpret_cast(hModule);
// DisableThreadLibraryCalls(s_hDllModule); <-- No, do not do this in code using the CRT.
break;
case DLL_PROCESS_DETACH:
DeleteCriticalSection(&s_cs);
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
break;
default:
break;
}

return TRUE;

}
[/code]