app_context - ryzom/ryzomcore GitHub Wiki


title: Application Contexts description: published: true date: 2023-03-01T05:17:18.944Z tags: editor: markdown dateCreated: 2022-03-07T10:56:36.315Z

This piece of documentation is quite old. Please help review it! {.is-warning}

The below explanation is largely wrong, at least on the Windows part. The issue is that under Windows we build NeL as static libraries, and then create multiple DLLs (as plugins) and executables. Each with their own static copy of the NeL libraries. Meaning, each shared library has their own static copy of the NeL library. Under Linux we are either building completely static executables, or building NeL entirely as dynamic libraries. The way NeL mixes static and dynamic builds for plugins is really a big no-no. {.is-danger}

Having discussed the pure NeL library system in the section above it is now important to discuss the application context system and safe singletons. Applicaiton contexts are a way for objects to traverse address spaces via relocation. Most Unix systems such as Linux use Position Indenendent Code (PIC) as a method to ensure that an application and all of its shared libraries are operating in the same address space. In overly simple terms when Linux loads a shared library it automatically relocates the symbols address spaces.

Windows systems do not implement PIC and so each shared library (DLL) operates in an independent address space. To illustrate this imagine you have a singleton called CFoo and it has two methods, one called addFoo and the other called printFoos. This obscenely simple singleton will allow you add strings to a list and then print them off. The problem with Windows (and other platforms that do not support PIC) comes up when the application and a library or two libraries use the same singleton - the end up with different images of world based upon their address and execution space. Below is an example of the kind of behavior you would exhibit on a Windows-based system:

// main.cpp
int main()
{
	// assume we did the library loading work, minus a context.
	CBar *bar = getBarInstance();

	// Now add some foo strings.
	CFoo::instance().addFoo("main1");

	// Initialize our bar library
	bar->initFoos();

	// Print out our list of foos.
	CFoo::instance().printFoos();

	// The above produces: main1

	// Now ask the bar library to do the same.
	bar->printFoos();

	// The above produces: bar1, bar2
}

// bar.h
class CBar {
public:
	void initFoos();
	void printFoos();
}

// bar.cpp
void CBar::initFoos()
{
	CFoo::instance().addFoo("bar1");
	CFoo::instance().addFoo("bar2");
}

void CBar::printFoos()
{
	CFoo::instance().printFoos();
}

Notice that the CBar library outputs something differently than the application itself? To overcome this the NeL developers created the NeL Context system and the concept of safe singletons.

Application and Library Contexts

Applications created using the NeL network services macros automatically have an application context but all other applications may need to explicitly create one. All you need to do is create a new instance. You can also check using the INelContext interface if you want to avoid creating a context unless necessary. Pure NeL libraries based on the INelLibrary interface will also receive a copy of the loading application's context.

int main()
{
	CApplicationContext *myContext;
	// Check to see if one was created.
	// This would be important if you had other code before this.
	if(!INelContext::isContextInitialised())
		// If one does not exist, create a new one.
		myContext = new CApplicationContext();
	else
		// If one does exist, retrieve its instance.
		myContext = INelContext::getInstance();
}

Safe Singletons

Safe singletons are singletons that are aware of the NeL context and can be used reliably in multiple libraries as well as the application. The NeL framework provides a couple useful macros to ease the creation of safe singletons: NLMISC_SAFE_SINGLETON_DECL, NLMISC_SAFE_SINGLETON_DECL_PTR and NLMISC_SAFE_SINGLETON_IMPL. The difference between NLMISC_SAFE_SINGLETON_DECL and NLMISC_SAFE_SINGLETON_DECL_PTR is that the former provides a reference to the singleton object and the latter provides a pointer to the singleton object. References are preferred as they tend to be a safer option to the pointer variation.

Using these macros is pretty simple. When your your class definition, simply declare the DECL-variant you wish to implement in the private scope of your class definition. Then in the source where you define your concrete implementations of methods of your class use the NLMISC_SAFE_SINGLETON_IMPL macro.

// string_getter.h
class CStringGetter
{
	NLMISC_SAFE_SINGLETON_DECL(CStringGetter)
	std:string m_string;
public:
	void setString(std::string str);
	std::string getString();
};

// string_getter.cpp
NLMISC_SAFE_SINGLETON_IMPL(CStringGetter)

void CStringGetter::setString(std::string str)
{
	m_string = str;
}

std::string CStringGetter::getString()
{
	return m_string;
}


// main.cpp
int main()
{
	CStringGetter::getInstance().setString("somestring");
	nlinfo("set the string to: %s", CStringGetter::getInstance().getString().c_str());
}

Source

⚠️ **GitHub.com Fallback** ⚠️