Log in

View Full Version : Compatibility with C++ DLLs


Lizardsoft
09-08-2003, 01:51 PM
My current plugin system requires that all plugins uses a DLL that contains in it a special C++ object that allows the passing of parameters/return values. The DLL uses normal implicit linking and the class is exported using dllexport.

How compatible is this between different compilers? The dll was compiled using VC++ 6 and works with .NET fine. Will people who wish to write a plugin using Borland's compiler be able to use this exported C++ class fine, or is this a special extension that isn't widely supported? I'm pretty confused on this matter and after googling for a while I still don't have a definite answer. I can change how I handle matters and still end up with a class at the end but it would be a pain to do and I'd rather keep the system simple and elegant if it will work for 99.99% of Windows developers.

To summarize: which compilers are capable of linking to dlls that export C++ classes, and is the format for this exporting standardized?

patrox
09-08-2003, 02:13 PM
as far as i used DLL between VC and CW i had no problems. I only had problems with static libraries ( the 2 compilers are not compatible ).

if you do have something like

CsuperStuff *myStuff = NULL ;

myStuff = (CsuperStuff *) DllFunc_GiveMeAnInstanceOfStuff( ) ;

that will work just fine.
Just make sure you free the myStuff in the DLL memory space though. ( DllFunc_DestroyInstanceOfStuff( myStuff ) )

Hope that helps ( i'm not a specialist in DLLs though )
pat.

Nexis
09-08-2003, 04:21 PM
If you're making C dlls, you have nothing to worry about. However, with C++ the dlls are incompatible between microsoft and borland compilers. If you want more info on this just do a google search on it but basically microsoft uses the COFF format and borland uses a OMF format. The easiest way to get around this problem would be to port it to C.

I've written wrappers to get around the problem before but it's a pain.

Really though, unless your game has a windows GUI inteferace borland is just a bad IDE for making games.

Nexis
09-08-2003, 04:25 PM
Actually though, if you have both compilers then it's not really an issue since you could just make a dll for each of them.

Lizardsoft
09-08-2003, 04:52 PM
Making two DLLs wouldn't work very well for this, I would sooner take the wrapper approach. Would defining a class all in a header file that is used by the main application and a plugin written in Borland work? Say I have a header file, PluginData.h, which defines an object that gets created by the main application, and a pointer to it is passed to the plugin. Will the Borland plugin be able to work with this object correctly? I would assume yes since C structs work fine but assumptions are never a good thing. The class would then just be a wrapper with a bunch of inline functions that call C functions in the actual dll. Not very elegant but worth it if it means any C++ programmer can write a plugin.

This isn't a game actually, it's the program in my sig. The plugins are there to avoid a hard-coded restriction on what sorts of data the bar can display and what sorts of tasks it can perform. The plugin system works really nice right now and I'm very pleased with it. I just need to do whatever it takes to make sure any reasonable Windows C++ compiler can be used to create the plugins.

Thanks for the information, unless there's a problem with the wrapper approach that's what I'll be using.

Siebharinn
09-08-2003, 06:06 PM
You could also implement your class as a COM object, then it would be compatible between any compiler/tool on Windows.

Lizardsoft
09-08-2003, 06:44 PM
I tried the COM approach today and before writing 15 lines of code I realized it's just how much I would be going against the simplicity goal. If the SDK were to expand significantly and there was demand for a COM wrapper that would certainly not be out of the question. COM is nice for larger things, but I can't picture throwing that acronym at casually-curious programmers when telling them how to get parameters for their plugin functions. After seeing how beginner's DirectX books/articles treat the COM aspect of DX, I can't help but get the feeling it would scare away people who would otherwise be capable of writing perfectly good plugins.

CustomBar works like this. It's a bar where at the basic level, you can place control's and instruct them to execute plugin functions when things happen. For example, you can put down a button, give it a picture of 2 stripes, and instruct it to call the Pause function in the AudioPlayer plugin when it is pressed. There, you have a pause button for Winamp. This is all wrapped up with the higher concept of prefabs and an editor which lets you place them down. The normal user opens up the bar in the editor and places down things like "Winamp Controller" or "Control Panel Menu" instead of scripting individual buttons. However, the more adventureous users can create their own prefabs and even their own plugins and post them for download :)

Anyway, I'm hoping that a combination of programmer-friendly SDK, source code to all standard plugins, and the possibility of creating something very useful very quickly will encourage users to write plugins for CustomBar.

Matthijs Hollemans
09-09-2003, 07:40 AM
C++ compilers use the same kind of linker that C compilers do. This made sense back when C++ was first born because C++ compilers once generated C code.

Unfortunately, using the C linker introduces a problem: how do you represent the names of classes and class members as linker symbols?

The answer is 'name mangling.' The compiler takes your identifier, for example int MyClass::MyFunction(int param), and turns it into something like this: ?MyFunction@MyClass@@QAEHH@Z .

The problem with name mangling is that most compilers have a different way of doing this. So the symbol names that the target compiler generates may be different from the ones in your DLL.

How much of a problem this is with the various Windows compilers, I do not know. But it is definitely something to be aware of.

See also: http://www.kegel.com/mangle.html

WreckerOne
09-09-2003, 08:32 AM
There are lots of apps that try to do the class in a DLL thing without COm and fail miserably. The plugins have to be recompiled each time a new version comes out. COM is so easy, all you have to do is implement three functions. You don't even have to use CoCreateInstance or whatever, just put a C function in your plugin DLL called CreateMyPluginObject.

"and is the format for this exporting standardized?"

COM

programmer_ted
09-09-2003, 06:36 PM
Hmm. I'd say either two DLLs or COM are your two options. Two DLLs could be more confusing than COM, really. If you want, you could make a quick tutorial for writing a plugin, and have a footnote that explains a little about COM. For instance:

To create a plugin you need to do this, this, this and this<footnote icon>.

--- At bottom of page ---
<footnote icon> CustomBar uses COM for plugins. COM is useful for <little COM description>. You can find out more about COM at http://msdn.microsoft.com/library/en-us/com/htm/comportal_3qn9.asp

Just an idea.

bernie
09-09-2003, 07:03 PM
Hmm.., funny I am currently battling with a similar problem, too.

I am porting my all codebase back to the new gcc - well, the forked branch returns home :) - and while it compiles fully, the data communication between the .dlls not completely correct. It produces some nasty access violations.

I think it could be some minor problem regarding the binding but it's been bugging me for few days now. Heck, after seeing the directx based code compiling I really want see this stuff to the end. I'd really like to see this win32 abomination merge back to the repository after a 5 years fork.

Lizardsoft
09-09-2003, 10:16 PM
Okay, I'll bite. First off, I want to re-iterate that my only goal/problem with regards to C++ classes is being able to pass as an argument to a DLL function a C++ object that the DLL function can then use. The code for the C++ object is currently stored in a seperate DLL and all instances of it are created and destroyed by the main CustomBar module. Plugin functions are only expected to call functions in this object, they do not have to create instances of it. What's the simplest way to accomplish this in COM? I imagine the plugin DLLs will still have to worry about interfaces if they want to access features that are added later on.

In the meantime, here's the crazy scheme I came up with. There is a DLL called datalib.dll. It's a C dll which creates instances of data and manipulates/deletes it based on the handle that is passed to it when its functions are called. For example, pretend CustomBar makes the following datalib.dll calls:


DataLib_CreateObject( 0x0555555 );
DataLib_CreateObject( 0x0333333 );

DataLib_SetStr( 0x0555555, "Hello and " );
DataLib_SetStr( 0x0333333, "Goodbye" );

DebugOutputString( DataLib_GetStr( 0x0555555 ) );
DebugOutputString( DataLib_GetStr( 0x0333333 ) );

DataLib_DestroyObject( 0x0333333 );
DataLib_DestroyObject( 0x0555555 );


As you may have guessed, the expected output is Hello and Goodbye, which is achieved by first retrieving the string value associated with 0x0555555 (an arbitrary but unique number), and then retrieving the value associated with 0x0333333. This basically emulates a primitive object.

Now for the next crazy step. I write a C++ class contained entirely in a .h file and containing NO member data. It's just a list of functions, like so:


CData
{
CData() { DataLib_CreateObject( this ); }
~CData() { DataLib_DestroyObject( this ); }

void SetStr( const char *SomeString )
{
DataLib_SetStr( this, SomeString );
}

const char *GetStr()
{
DataLib_GetStr( this );
}
}


Note that this class is not exported in any way. Also note that it has no data and that it simply translates each member function call to the appropriate function in datalib.dll Each plugin DLL includes this header file and this translation code is therefore generated within each dll. The class's pointer is what is passed to DataLib as a unique handle to identify the data associated with the class. Plugin functions take this pointer as an argument when they are called by CustomBar, and all they do is call these wrapper functions in what they think is a CData object but in reality is just a wrapper for data inside datalib.dll. For example, a simplified version of a plugin function in a plugin dll is:


int SomeFunction( CData *Arguments, CData *ReturnValue )
{
ReturnVal->SetStr( "59" );
}


The plugin functions actually take two more parameters but those are not important with regards to this. The real DataLib class is also more complex and is actually called UNIVAL, which is my own type-safe varient-style type created specifically to work around the problems of passing dynamically sized data between multliple modules that might resize it (eg. a call to SetStr can result in memory alllocation, which is why this code ultimately rests in datalib.dll).

As far as the plugin writer knows, he is simply working with a CData object. All this hiding behind dlls and wrappers is only visible by the need for the plugin to link with datalib.dll (which is a reasonably expectation). So.... would this approach work or am I overlooking some important issues? This theoritically has the possibility of being easily extendable. Backwards compatibility could only be broken by removing existing functions.

Siebharinn
09-10-2003, 04:04 AM
I'm confused now. Is the plug-in writer making calls into datalib.dll? So you have CustomBar module, a datalib module and a plug-in, right? CustomBar loads the plug-in and datalib, and the plug-in uses the functions in datalib?

You might want to consider just setting up a function pointer interface (which is essentially all that COM is).

CustomBar loads the plug-in, and calls the well-defined function "InitPlugIn" (or whatever). As a parameter, it passes the function interface, which looks something like:

struct Functions {
void (*CreateObject)(int);
void (*SetString)(int, const char*);
};

The plug-in keeps a reference to the interface and whenever it needs to use a function, it simply calls it:
funcptr->CreateObject(0x111111);

The only caveat is that InitPlugIn can't be name-mangled.
But this removes the need for datalib completely, which if I understand you correctly, you're only using as intermediary anyway.

Siebharinn
09-10-2003, 04:16 AM
One additional plug for COM: if you set it up correctly, your plug-in writers aren't limited to C++, any COMpatible (heh) tool can be used. Between VB and Delphi, that expands your potential plug-in developer base tremendously.

Lizardsoft
09-10-2003, 10:24 AM
DataLib currently exists strictly for the purpose of implementing a very special object I wrote for passing arguments and receiving results from plugin functions. This allows each plugin function to take the same arguments (two pointers to this special object, one for a parameter array, and one for return value). This special object allows a value to be stored as an int, float, string, etc. This object also remembers what type is stored, so if you try to store an int but retrieve a string it fails (there are conversion functions as well but lets not get into that). This allows plugins to instruct CustomBar what arguments they expect, and leave CustomBar to make sure the parameter array contains objects with values set as the expected type.

Now before this makes sense, I better point out that CustomBar directly doesn't call any plugin functions except three special predefined ones that all plugins must support (GetPluginInfo, Initialize, Deinitialize). All other calls it makes are a result of being instructed to do so by the script. Bars and the prefabs that they are made of are created using my TDSettings settings language, which is an ini-style way of specifying settings (it goes way beyond what ini files do though). Here's an example of a button that opens up the command prompt:


[@INC]
{Button}

XPos = 0
YPos = 1
Width = 18
Height = 18
HorzAlignType = "relative"
HorzAlign = "right"
Gfx = "Button.Normal"

ExecProc = "System.Execute( 'open', 'C:\\WINNT\\system32"
"\\cmd.exe', '', 'C:\\WINNT\\system32\\\\', 'NORMAL' );"

Glyph = "&C:\\WINNT\\system32\\cmd.exe"
ToolTip = "Command Prompt"


Mind you there's an editor that allows all this to be done without mucking around in settings files. Those are available for those people who wish to make their own prefabs (their own scripts basically). The plugins come into play with the xxxProc group of settings. In the case above, the code defined in ExecProc is executed whenever the button is clicked. System is one of the plugins that comes with CustomBar, and Execute is a plugin function. Note how I said before a plugin tells CustomBar the parameter type it expects. This is important since it's actually user-alterable scripts that specify the arguments passed to a plugin function. CustomBar has to check the types passed and convert them as necessary to make sure it is passing the plugin information in the type that it expects. In the above example only strings are passed, but integers and floating point numbers can be as well.

Now, the reason that this datalib thing is in its own dll is so both plugins and custombar can assign strings to datalib objects and not worry about the fact that CustomBar is responsible for all datalib object creation and destruction. Basically, the dll wraps memory allocation to get around the limitation that memory allocated in a module must be freed by the same module (ie. if a plugin were to directly allocate a string to return its result, custombar would crash if it tried to delete it).

Siebharinn - I don't know much about VB's and Delphi's abilities to generate DLL. I'm particularly concerned about VB since VB programs require all sorts of other stuff to be installed. Would these languages be capable of producing a dll that is around 50KB and can give performance similiar to one written in C++? Plugin functions are very small and the information ones like getting winamp song title are expected to be capable of being called every second without causing a system performance drop. I'm willing to investigate allowing plugins in other languages if it's indeed feasible to create CustomBar plugins in them.

Also, just to show what I mean about plugin functions being small, here's the source of SystemInfo.UsedMem, a plugin function that returns the same value as the task manager memory in use display:


/*
----------------------------------------------------------------------
| Obtains the amount of used memory and reports it back. See
| ConvertBytesToStr for units that data can be returned as.
|
| P1 - unit to convert bytes to. (string)
---------------------------------------------------------------------- */

SYSTEMINFO_API int UsedMem( P_UNIVAL *Params, P_UNIVAL
*Result, PLUGIN_CALLINFO *CallInfo, void *Reserved )
{
MEMORYSTATUS MemStats;

MemStats.dwLength = sizeof(MEMORYSTATUSEX);
GlobalMemoryStatus( &MemStats );

Result->SetInt( (int) ConvertBytesTo(
((MemStats.dwTotalPageFile - MemStats.dwAvailPageFile) > 0) ?
MemStats.dwTotalPageFile - MemStats.dwAvailPageFile :
MemStats.dwTotalPageFile, MemStats.dwTotalPhys,
Params[0].GetConstStr() ) );
return 1;
}


As you can see, it's mostly a wrapper for Win32 functionality, with the additional feature of allowing the data to be returned as something other than bytes (eg kb or mb). This is also why I want the plugin system to be as developer-friendly as possible, you don't actually need to know much beyond basic C (and a little bit of C++). I have a friend with very little C++ experience and almost no Win32 experience who was able to write a plugin that gets the system up time without a problem. Of course, advanced developers are free to write some more complicated stuff, I'm just saying you don't need a rocket science degree to create something useful with this stuff :)

patrox
09-10-2003, 10:46 AM
Originally posted by Lizardsoft
The code for the C++ object is currently stored in a seperate DLL and all instances of it are created and destroyed by the main CustomBar module.
.


I probably missing something very basic here, but why don't you create and delete the instances in the DLL instead of the customBar module ? ( with 2 C functions in the DLL , plugNew and plugDelete for example )

pat.

Lizardsoft
09-10-2003, 11:08 AM
I'm not sure if that would work or not, but I'm not seeing how that solves any problems.