Jump to content
  • Advertisement
Sign in to follow this  
NumberXaero

Singleton use between App and inside DLL

This topic is 5060 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hey, I have log manager class (singleton) whose instance is created in the main app (W32) at startup (allocated to static pointer, with reference returned for use). Now, I need to use that same log manager inside a DLL which the app uses so that logging goes to the same place. Basically I need the data inside that managers instance to be same across app and DLL. Should I pass a specific log to the class in the DLL, pass the actual log manager instance to the class in the DLL, or is there some other way this should be done? Im guessing im gonna have to change to classes interface for the DLL to accept an additional pointer to handle logging. Any thoughts?

Share this post


Link to post
Share on other sites
Advertisement
For the most robust solution, you'll want to switch to some sort of interfacing scheme between modules. Sort of like COM, only stripped down to the bare essentials.

Class types that are shared among modules should declare an interface class, which is a type with only public pure virtual functions. Since C++ doesn't have built-in support for interfaces, it's possible to add regular virtual functions and data members, but that should be kept to a minimum. Next, you write the actual implementations, that derive from the interface. When communicating with other modules, you pass around pointers to the interface type. It becomes the responsibility of the implementing module to manage the creation, destruction, and lifetime of the object. This should all sound familiar if you have some basic COM knowledge. A decent article on the subject exists here that presents the basics of these interfacing concepts.

Lastly, I'm not so sure it's a good idea to have the log class implemented in the executable and then passed to the DLL. This means that the DLL becomes dependent on every EXE that links it to provide a log class implementation. Usually you want to minimum these types of dependencies. The log class is hopefully designed to be reusable, as such it makes sense to put it in a DLL so that it can more easily be that. Even if you only ever plan on having one EXE link the DLL, it makes more sense from a design standpoint as well.

Share this post


Link to post
Share on other sites
Thanks for the suggestion. I currently have the DLL in question inplemented using an abstract base class. The .EXE uses the interface of this DLL class. Now the log manager is a singleton which is currently in a static lib (maybe it shouldnt be?) my problem is making use of this same singleton instance in both the .EXE and .DLL
so that both the exe and dll can control creation/destruction of these logs.
Currently my DLLs class interface contains a Initialize(..., Log*) argument. But if Im understanding your second paragraph, you suggest that this log manager should also be implemented using an interface and then the interface should be passed to the interface of my DLL class (which requires the log manager)? If that is the case, If Im going to be changing my DLL class interface to accept this new log manager interface, why not just pass the log manager instance instead? Whether I "pass around pointers to the interface type" or the actual log manager (or just a log) itself Im still changing my original DLL interface to accept some new parameter, be it a log manager interface or log manager? Or have I miss understood something?
Glancing at the article, it looks very similar to how I have my DLL class setup, but what Im kinda confused about is, isnt the DLL going to be dependant on the exe that links it no matter what, as long as the DLL class interface requires parameter input the exe is going to be expected to provide that info isnt it?
In which case could you not provide NULL in place of a pointer to a log manager indicating no logging is needed? Thanks for any help.

Share this post


Link to post
Share on other sites
Quote:
Original post by NumberXaero
Hey, I have log manager class (singleton) whose instance is created in the main app (W32) at startup (allocated to static pointer, with reference returned for use). Now, I need to use that same log manager inside a DLL which the app uses so that logging goes to the same place. Basically I need the data inside that managers instance to be same across app and DLL. Should I pass a specific log to the class in the DLL, pass the actual log manager instance to the class in the DLL, or is there some other way this should be done? Im guessing im gonna have to change to classes interface for the DLL to accept an additional pointer to handle logging. Any thoughts?


For two way communication between an .exe and .dll I would have to suggest not using runtime linking. With that said here is some tips about how I would do this:

To get memory from a .dll and use it in an exe, you must use some void** pointer. In the .dll you must have a function that uses this pointer-pointer to allocate memory as well as deallocates it - ie.

void Allocate( abstract_class** memory)
{
*memory = new dervived_class;
}

void Deallocate( abstract_class** memory)
{
delete *memory;
*memory = 0;
}


Now that is for using variables of a .dll in a .exe as runtime. To do two way communication, I would suggest using a similar method, but passing the abstract_class** memory to the .dll so it can use it as well. I hope this makes some sense - if not I'll try to explain a little more.

Share this post


Link to post
Share on other sites
Yes that is exactly how I am currently getting an interface to the DLL class for use in the exe. I export (using your example) Allocate/Deallocate and get pointers to those functions using GetProcAddress for use in the exe.
Now you suggest, doing the reverse of that and setting a (global?) variable inside the DLL so that the class in the DLL can make use of it if it chooses to? To avoid having the DLL class interface dependant on some sort of logging claass? If so, why must the class Im passing back to the DLL be abstract?
Im using an abstract class in the exe so that in case the DLL implementation needs to change, its a matter of updating the DLL, while not changing the exe and having to recompile it. I guess whats messing me up is
Quote:
I would suggest using a similar method, but passing the abstract_class** memory to the .dll so it can use it as well.

I cant see the benefit of using an abstract log mangager class rather then the actual log manager when passing to the DLL. Thanks.

Share this post


Link to post
Share on other sites
Quote:
Original post by NumberXaero
Thanks for the suggestion. I currently have the DLL in question inplemented using an abstract base class. The .EXE uses the interface of this DLL class. Now the log manager is a singleton which is currently in a static lib (maybe it shouldnt be?) my problem is making use of this same singleton instance in both the .EXE and .DLL
so that both the exe and dll can control creation/destruction of these logs.

Having the implementation in a static library isn't a problem, however now you have a compile-time dependency. Whenever you change the log class you have to compile every module that links it, rather than just recompiling the single module if it were a DLL. In all cases though, you want to avoid having an instance of the log class created in the library. This is because every module that links the library will have it's own internal singleton, and you will have multiple log classes. However, since a singleton is the obvious solution for a log managing class, a DLL would work better. The singleton is only created once, in that DLL, and since all modules loaded into the same process have access to the same virtual address space, you can easily pass around a pointer to that single log class and have it work everywhere. If you're intent on keeping it in a library, then the log class can't be a singleton, and you'll have to manage the instance in another way.

Quote:
Currently my DLLs class interface contains a Initialize(..., Log*) argument. But if Im understanding your second paragraph, you suggest that this log manager should also be implemented using an interface and then the interface should be passed to the interface of my DLL class (which requires the log manager)? If that is the case, If Im going to be changing my DLL class interface to accept this new log manager interface, why not just pass the log manager instance instead? Whether I "pass around pointers to the interface type" or the actual log manager (or just a log) itself Im still changing my original DLL interface to accept some new parameter, be it a log manager interface or log manager? Or have I miss understood something?

The idea here is that you can change the implementation of the underlying log manager without having to recompile or break existing modules that use the log manager. This also means you don't change the interface. Thus, if your interface remains the same, you don't have to recompile the clients that uses these interfaces. If you were to change Log over and over, than every single module that relied on that definition would have to be recompiled! Instead, you have ILog that never changes, and Log that can only change within a single module. You pass around pointers to the interface, however instantiate the actual log instances within their implementing module.

If you ever reach the point where you absolutely must modify an interface, what you do is create a new interface (so that the old one still works) and then write your log manager to satisfy both interfaces. Thus applications that use the old interface work without modification, as well as new applications that use the new interface. And if you want to upgrade a few of the old interface in addition to adding new stuff, then you have the old functions act as wrappers around the new ones. Now the old interface takes advantage of new implementation, and the new interface gives you this updated implementation plus extra. The only managerial work you have to do is have a global list of interface identifiers, so that modules can actually specify which interface they're requesting. I guess if you want, you can also setup global class identifiers so that modules can request specific implementations as well. This is what COM does with their IID and CLSID (interface and class IDs). However I'm not a big fan of letting client modules specify the implementation, since if your design is truly modular it shouldn't matter. But for special cases where the implementations are platform dependent say, or usage requirements dictate that certain faster or efficient algorithms must be used, it's a good idea. It all depends what your requirements are.

Quote:
Glancing at the article, it looks very similar to how I have my DLL class setup, but what Im kinda confused about is, isnt the DLL going to be dependant on the exe that links it no matter what, as long as the DLL class interface requires parameter input the exe is going to be expected to provide that info isnt it?
In which case could you not provide NULL in place of a pointer to a log manager indicating no logging is needed? Thanks for any help.

Eh, I don't exactly agree with the article [smile] It just demonstrated what I was getting at with the interfacing, and if that's similar to what you have then you're on the right track. This is what I do: each module implements an interface called IModule. Each module exports a function called GetModuleInterface. When you call this function from a DLL, you pass it a IModule pointer-pointer (excuse my lingo), and it gets set to the module interface. So this is how the EXE gets at the DLL module handle. IModule contains a function called QueryInterface, which is how everything is passed between modules. The calling module (in this case the EXE) has the option of setting a parent pointer on the child module (DLL). This informs the child module of the parent module, and implicitly serves to tell the child if the parent is an interface provider.

This all begs the question, if the system is so similar to COM then why not use COM off the bat? The answer is you could, but it can be a pain in the ass to work with and you probably won't end up using all the special features it provides anyway. This is just a simple yet elegant solution for small to medium-sized engines and applications.

[Edited by - Zipster on January 7, 2005 11:58:31 PM]

Share this post


Link to post
Share on other sites
If your sill aware of this thread (little late replying, had somewhere to go), I think I see your reasoning now, you wouldnt happen to have a quick resource (link, etc) to give me a better idea of how IModule and QueryInterface would be setup would you? Thanks for your help.

Share this post


Link to post
Share on other sites
Although this specific design is something I cooked up on my own, the overall pattern is quite common. It simply has to be modified to suit your needs. I built on top of the article I linked by abstracting the module as well, so that even the interaction between system modules used a standard interface. Here's the basic design I use:

IInterface.h

class IInterface
{
public:
// Simply return an identifying string
virtual const char* GetVersionString() const = 0;
};

#define ADDER_INTERFACE "AdderInterface001"
#define MULTIPLIER_INTERFACE "MultiplierInterface001"

IModule.h

class IModule : public IInterface
{
public:
// Initialize internal interfaces
virtual bool InitializeInterfaces() = 0;
// Uninitialize internal interfaces
virtual void UninitializeInterfaces() = 0;
// Set the parent module
virtual void SetParentModule(IModule*) = 0;
// Query interfaces
virtual IInterface* QueryInterfaces(const char*) const = 0;
};

ExampleInterfaces.h

class BinaryIntegerOperation : public IInterface
{
public:
virtual int DoOperation(int,int) = 0;
};

class Adder : public BinaryIntegerOperation
{
public:
virtual const char* GetVersionString() const
{
return "Adder v1.0";
}
virtual int DoOperation(int a, int b)
{
return a + b;
}
};

class Multiplier : public BinaryIntegerOperation
{
public:
virtual const char* GetVersionString() const
{
return "Multiplier v1.0";
}
virtual int DoOperation(int a, int b)
{
return a * b;
}
};

ServerModule.h

/* "server" in reference to being a provider of services */
class ServerModule : public IModule
{
public:
virtual const char* GetVersionString() const
{
return "ServerModule v1.0";
}
virtual bool InitializeInterfaces()
{
m_Adder = new Adder;
m_Multiplier = new Multiplier;
return true;
}
virtual void UninitializeInterfaces()
{
delete m_Adder;
delete m_Multiplier;
}
virtual void SetParentModule(IModule* parentModule)
{
m_Parent = parentModule;
}
virtual IInterface* QueryInterface(const char* interface) const
{
if(!strcmp(interface, ADDER_INTERFACE))
{
return m_Adder;
}
else if(!strcmp(interface, MULTIPLIER_INTERFACE))
{
return m_Multiplier;
}
return 0;
}

private:
IModule* m_Parent;
Adder* m_Adder;
Multiplier* m_Multiplier;
};

ClientModule.h

/* "client" in reference to being a consumer of services */
class ClientModule : public IModule
{
public:
virtual const char* GetVersionString() const
{
return "ClientModule v1.0";
}
virtual bool InitializeInterfaces()
{ /* not a provider */ }
virtual void UninitializeInterfaces()
{ /* not a provider */ }
virtual void SetParentModule(IModule* parentModule)
{
m_Parent = parentModule;
}
virtual IInterface* QueryInterface(const char* interface) const
{
if(!strcmp(interface, ADDER_INTERFACE))
{
if(m_Parent)
return m_Parent->QueryInterface(ADDER_INTERFACE);
}
else if(!strcmp(interface, MULTIPLIER_INTERFACE))
{
if(m_Parent)
return m_Parent->QueryInterface(MULTIPLIER_INTERFACE);
}
return 0;
}

private:
IModule* m_Parent;
Adder* m_Adder;
Multiplier* m_Multiplier;
};

ExampleApplication.h

/* Example application manages server/client modules */
class ExampleApplicatoin : public IModule
{
public:
virtual const char* GetVersionString() const
{
return "ExampleApplication v1.0";
}
virtual bool InitializeInterfaces()
{
HMODULE hServer = LoadLibrary("ServerModule.dll");
HMODULE hClient = LoadLibrary("ClientModule.dll");
getModuleInterface = GetProcAddress(hServer, "GetModuleInterface");
getModuleInterface(&m_Server);
getModuleInterface = GetProcAddress(hClient, "GetModuleInterface");
getModuleInterface(&m_Client);
if(m_Server) m_Server->SetParentModule(this);
if(m_Client) m_Client->SetParentModule(this);
return m_Server && m_Client;
}
virtual void UninitializeInterfaces()
{ }
virtual void SetParentModule(IModule*)
{ /* we are the parent */ }
virtual IInterface* QueryInterface(const char* interface) const
{
return m_Server->QueryInterface(interface);
}
private:
IModule* m_Server;
IModule* m_Client;
};

It can take some getting used to, as it's a bit more complicated than I may have made it sound. Each module controls the interfaces it implements underneath. When these internal interfaces request another interface from the module, the module forwards the request the parent module, which either implements the interface itself, or knows how to route the request further. I use an engine module which acts as the parent to all the subsystems. It implements a few interfaces itself and also acts as the primary router for interface requests from subsystems. The example chain for the code I gave looks like this:

ExampleApplication
/ /
ServerModule ClientModule
/ /
Adder Multiplier

Although usually you'll have another layer between the application and the module structure (akin to the engine module I use) that handles routing, so that the application only has to know about that one singe engine module.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!