Archived

This topic is now archived and is closed to further replies.

SilentReaper

Classes in a DLL / plugins

Recommended Posts

Hello all. I am working on a game project where I''d like to exclusively use C++ and classes. One of the requirements of this project is that it must be easy to upgrade/patch the game components and add things to the game (such as new weapons, new characters, or new AI behaviors). To this end, each major component of the game will have a sub-directory where I''d like to read in and load *.DLL. I have read some of the prior posts on how to use C++ in DLL''s and how to access those classes in the hosting process, however these posts have all pretty much been what I consider "early bound". The classes are known and defined at compile time. That won''t necessarily be the case with my game. If a new DLL is dropped in a directory, its code should run. I was planning on making a basic parent class with at least 3 pure virtual functions: - init: set up any variables necessary, possibly pass in a "this" pointer from one of my master classes so that the class can access other game objects - process: called in the appropriate game logic section (a weapon class would be processed during the weapons stage of the game loop) - shutdown: release all allocated memory objects and handles. I don''t believe this type of scenario was answered in any prior posts (if so, someone please link me there), and I can''t seem to find any GOOD resource on Google. Is this possible? Could someone help me out with a short snippet? Doesn''t even have to be to my above specs as long as I learn the requirements for creating the DLL and how to load in the DLL into my game process.

Share this post


Link to post
Share on other sites
What you are after is called a "pluggable factory", searching for that on Google will return you loads of articles. Indeed one of them is right here at GameDev.

[edited by - MonkeyChuff on August 14, 2002 11:08:17 AM]

Share this post


Link to post
Share on other sites
Sweet! I will look for that. You are not, by any chance, referring to COM are you? I would like to steer clear of that since I have heard it is very slow. I know COM has things called class factories, and that is the only reason I ask.

[edited by - SilentReaper on August 14, 2002 11:13:15 AM]

Share this post


Link to post
Share on other sites
No, not at all, factory''s are a common design pattern, it just so happens that COM uses the factory pattern quite extensively.
I haven''t found COM to be slow, most of DirectX uses COM, so if you are going down that route then it''s going to be in there somewhere.

Share this post


Link to post
Share on other sites
Its a peice of cake. Crystal space does a good job of explaining how its done: http://crystal.sourceforge.net/docs/online/manual/cs_139.php

If you want a more specific example:

iTest.h:
class iTest
{
public:
virtual int Init(void* stuff)=0;
virtual int Process()=0;
virtual int Shutdown()=0;
};


Test.h:
class Test: public iTest
{
public:
int Init(void* stuff);
int Process();
int Shutdown();
};



Test.cpp:

int Test::Init(void* stuff)
{
//do something
return 0;
}
int Test:: Process()
{
//do somthing
return 0;
}
int Test::Shutdown()
{
//do somthing
return 0;
}

//this is called the class factory
__declspec(dllexport) Test* ExportPlugin()
{
return new Test;
}




main.cpp:

#include "itest.h"
#include <windows.h> //yuck!
HINSTANCE handle;
typedef void* (*FuncPtr)();


int main()
{
FuncPtr funcpointer;
handle=LoadLibrary("test.dll");
funcpointer=GetProcAddress(handle,"ExportPlugin");
iTest* tester=funcpointer();
tester->Init(junk);
tester->Process();
tester->Shutdown();
FreeLibrary(handle);
return 0;
}


test.h and test.cpp are compiled into test.dll
main.cpp is compiled into the main program, both use itest.h

basically what you have to do, is search through all the dlls in your games 'plugin' folder and call GetProcAddress to get the pointer to the ExportPlugin function, if you get a null returned then that dll isnt one of your plugins, if a !null pointer is returned then call the function the pointer points to and store the returned pointer as a pointer to base plugin class...

'ExportPlugin' isnt a special function name, you can name it whatever you want...

a couple things to watch out for if you use MSVC...

In order to export a function with a specific name(MSVC likes to give functions semirandom names) you have to give it a specific option or use a .def file. not hard to do. You can check the actuall name that is exported by generating a .map file(its an option) and looking in there.

make sure the function calling convention in the dll class factory and the function pointer match otherwise you get nasty errors

ummm i think thats it, hope it helps!

[edited by - warpexplorer on August 14, 2002 5:33:04 PM]

Share this post


Link to post
Share on other sites
Thank you to you both for your excellent resources! warpexplorer: I assume that if I derive from Test that iTest will still work? I assume that it would and the reason for it would be the vtable would have the proper location for my member functions?

Thanks again!

Share this post


Link to post
Share on other sites
I tried something very similar to that and got errors, which I think come from the fact that new uses some memory allocation function (malloc?), which uses global pointers (buckets, if anyone understands). The dll had it''s own copies of these variables, which meant there were two separate memory allocation functions each trying to allocate the same memory.

(I''m guessing this because when mallocing large (1mb - 256bytes) blocks, one from the dll and one from the main, the pointers were different by a number not divisible by 1meg. If you know malloc, this is wrong. Also, nonsensical pointer errors abounded)


Constructors are able to be exported themselves from the DLL, without the need for an encapsulating function like the one below.

If you use MSVC 6 or more, create a new project of type "Win32 dynamic link library", and chose "A dll that exports some symbols". It gives examples of how to create dlls "well" (the microsoft way). It shows how to export variables, functions and constructors.

Also, dlls shouldn''t call library functions unless you know what you''re doing. A simple call to printf() will not print anything when inside a dll, you have to import the printf function (or an encapsulating function) from the main program. I assume this doesn''t work for the same reason that I assume malloc doesn''t.

(These are just my findings, correct me if I''m wrong)

Share this post


Link to post
Share on other sites
Krylloan,

well, im not sure about new using malloc.
but my above example works perfectly on my machine as long as the call convention is the same.

The examples microsoft give are for ''statically'' linked dlls (very oxymoron), they MUST be (and are) loaded when the program starts and are unloaded when it exits. Which doesnt work for what SilentReaper wants.

all the library functions that I''ve tried work in my dlls (SDL, OpenGL, stdio, ect.)
printf works great, all i have to do is #include <stdio.h>

I use MSVC 6 so I dont think its a compiler problem, are you sure you are using the same calling convention?

Share this post


Link to post
Share on other sites
Sorry about reviving an old post, but I want to clear some things up that some people have said (I don't want people to stumble on this post in the future and learn some misinformation).

quote:
original post by warpexplorer
The examples microsoft give are for 'statically' linked dlls (very oxymoron)...



There is no such thing as a 'statically' linked DLL. A DLL is a dynamically linked library . A LIB is a statically linked library (which is not what Krylloan was referring to, anyways). LIB files are not oxymoronic and can be quite useful (as well as being faster than DLLs because they are compiled into the EXE). However, DLL's are generally more useful because they are not built into the EXE.

Also, I'm not sure what Krylloan was trying to say in the first half of his last post, but it seemed mostly wrong (no offense). The keyword 'new' does not use malloc() to allocate its memory, and their is nothing wrong with creating and returning a pointer to an object inside of a DLL function to whatever called the function. As long as the calling program deletes the pointer, everything will be fine.

-Mike

[edited by - doctorsixstring on October 14, 2002 12:31:02 PM]

Share this post


Link to post
Share on other sites
My thread lives!

Actually, statically linked DLLs do exist and are quite common. The win32 API, most of which consisting of functions that make calls to KERNEL32.DLL and USER32.DLL, use this. In these types of DLLs, during link time, the functions that are called within these DLLs are resolved. When the program runs, one of the initial things it does is to "automatically" load these dependancies, looking in the current directory or the %PATH% environment variable. If it does not find the DLL, the program will fail to run, and a dialog box will appear saying something to the effect of "Missing component" and give the filename of the DLL it expected to find. Programmatically, you don't even have a chance to handle this error condition, and your program breaks. This is also what I meant by "early bound" (I believe I used the term out of context so that's why I've always quoted it) in my original thread, and that would not work for my design since I wouldn't know the DLL names up front.

This differs from runtime DLL linking in the sense that it is the program's responsibility to manually load the DLL and resolve a function pointer before any code in that DLL is accessed. This also gives the ability to handle error conditions and your program can be graceful.

EDIT: Also, addressing Krylloan's, problem, I believe he was possibly running into issues of calling some non thread-safe code. I believe some of the standard ANSI C API (the string functions maybe?) are especially notorious for not being thread-safe.

[edited by - SilentReaper on October 14, 2002 1:18:20 PM]

Share this post


Link to post
Share on other sites
With msvc6 you can create dll or static lib. In both cases the project spits out .lib file which contains library function names. In the static case you include .lib into your project but your main program must also inform the linker about libs the static library is using as well as those libs your main program is using. In dynamic library case your program still includes the .lib file but any libraries the dll is using are specifically listed in the dll project when it is compiled and linked, thus your main program doesn''t need to link to libraries which the dll is using. Also, the linking of dll is broken into two one using the LoadLibrary() call in which case you need to specify unmangled library function names thru the .def file and in second case you don''t use LoadLibrary() instead you let main program to load all the libraries it''s using or lib functions at startup thus you include the .lib file into your main program. The static library is linked in during compilation as it doesn''t include .dll file at all.

Also, I''m pretty sure the global new() is implemented using malloc the same way you implement local new() for your own objects if you need to. Also, something funky is going on with dll. I have memory leaks after I converted to dll from static lib, no code change just movement to dll and recompiled the app - boom instant memory leaks whereas before the debug showed zero leaks. I''m using STL(multi-threaded option). Also, something interesting for those who want to inherit from d3dx lib inside a dll. You can do it but you''ll get warnings when using your new dll in your main program. The warning tells that exporting an object that inherits from non-exportable base class is a no-no. However everything works, interestingly I would imagine the 3d objects in the d3dx lib are also flagged as exportable so I''m not sure why the warnings show. Just some weird quirks going on w.r.t. dlls. On another topic I discovered ms specific _base() function that allows saving object using pointers to file then load them into memory and remaps the offsets accordingly. Haven''t played with it but this might be one solution to dreaded "can''t save pointer to file" subject. You can do it by recording the offset yourself as ints and save those to the file instead, then when reading back convert the offsets into pointers, I will have to do something like this, thankfully stl helps with this so not sure if I''ll go auto with _base() or do it manually.

Couple of my dll notes:

DLLMain:rocessAttach/Detach should not call LoadLibrary() or any other functions that call LoadLibrary() themselves. Only simple init. should be done in DLLMain().

Hinstance that''s passed to DLLMain() is dll''s starting address NOT main app''s Hinstance. Window creation requires app''s hinstance instead of DLL''s module handle.

The hinstance of modal dlg box can be that of the DLL module handle, hwndParent param can be set to NULL.

Get()/Peek() win32 functions can be called from within DLL they''ll examine main app''s msq queue.

All memory DLL allocates is owned by the main app. Same goes for windows, files, etc. All data and functions are mapped into the address space of the main app upon startup of the app. Global data in DLL is also global in the main app.

These are just few I wrote down from msdn. Not sure about the last one as by the weird memory leaks I''ve encountered go against this "all data is mapped into addy space of main app" thingy unless STL somehow breaks this as I had zero new()s in my main program when I ported it to dll. So STL allocated all the heap memory w/o my help.

Share this post


Link to post
Share on other sites