The online racing simulator
DirectX output, one common D3D8.dll for all addons (technical discussion)
hello developers,

due to several requests for DirectX output I want to initiate a project for one common D3D8.dll for all addon developers. Participants of the following discussion should have a basic understanding of the wrapper dll approach and knowledge about DirectX programming would be helpful. A tutorial of the proxy concept can be found on mikoweb.eu

Introduction

Even if the wrapper dll approach is very easy to understand and to implement, it has one significant drawback. As every addon developer will write his own D3D8.dll for his purpose and copy it into the LFS folder, it may lead in overwriting the D3D8.dll of other addons.

Objective

The goal is one common D3D8.dll for all addons. Ideally it would only do the wrapping and would not have any functionality. On request of the addon it would load an additional addon specific dll with all needed funtionality.

Problems

We need to discuss several questions:
  • We need some kind of interprocess communication to instruct the D3D8.dll to load/unload the addon specific part. What kind to use (pipe, loopback, shared memory...)?
  • What functions does the addon specific dll needs to export?
  • How to handle a "dll only" addon (example: Drift Gauge and G-Meter by Racer_S2)? There is no 2nd process that could instruct the dll to load additional functionality.
with kind regards

Sören
Quote from Soeren Scharf :Even if the wrapper dll approach is very easy to understand and to implement, it has one significant drawback. As every addon developer will write his own D3D8.dll for his purpose and copy it into the LFS folder, it may lead in overwriting the D3D8.dll of other addons.

You should be able to 'stack' the dll's, by instead of loading the real d3d8.dll you just load the dll of another 'proxy' until the dll at the end loads the real one. I dont know if it works with that gpp proxy dll though since I dont use that myself.

That's not to say some common interface would be a bad idea, it just might be difficult to design it in such a way it isn't too restrictive.
Can't you create a proxy DLL with a configuration file which loads other proxy dlls?
I fear that it would approach the framework-syndrome that I love to hate. Don't spend time creating frameworks unless you're actually going to use it yourself; if you do use it yourself then it will naturally become a good framework if it gets adopted by others.

Look at LFSLapper. It's considered the defacto standard for certain things. It may not be the best solution, but I think it could be argued that other than the various versions of tweak it's probably the most widely used, alterable "mod".

Make it and they will come.
Quote from Kegetys :You should be able to 'stack' the dll's, by instead of loading the real d3d8.dll you just load the dll of another 'proxy' until the dll at the end loads the real one. I dont know if it works with that gpp proxy dll though since I dont use that myself.

That's not to say some common interface would be a bad idea, it just might be difficult to design it in such a way it isn't too restrictive.

Brilliant idea, stacking has the huge advantage, that existing D3D8.dll's do not need to be modified, they just need to be renamed to a unique name. Of course we will not get a huge chain of several addons, but with one common dll and one custom dll it will do the job. (see attachment as example).
Indeed this soultion is more simple because this common proxy dll does not need to wrap the whole D3D8 interface, but only the DirectD3Create8 function, where it decides which dll to load. Also we will not loose performance.
But there is also one limitation: The addon needs to be active during startup of LFS. Once all Dlls are loaded, the 'stack' can not be modified. So loading/unloading the custom dll at runtime would not be possible. If we decide, that this is not really a handicap, then I would prefer this as the solution.
During this weekend I will prepare a source code suggestion.

Quote from wheel4hummer :Can't you create a proxy DLL with a configuration file which loads other proxy dlls?

this is exactly what is written above. Configuration file or whatever else to use is still subject to discuss. But I would agree that an ini file would be the most plausible decision.

kind regards
Sören
Attached images
d3d8stack.gif
This is exactly what I was going to suggest at one point.

I haven't done much work with programming (some c++ with DX9sdk and currently learning Turbo pascal)
so I can't help much other than to suggest that you use a unit system.

Its the same thing as you posted above, but I'll right it out in simple terms for referance. (so I understand it as well)

D3D8.DLL is simply a loader file that runs all the normal functions and uses a calling list (say units.ini) to call the other DLLs for each mod
(for example the motion blur DLL) and run the functions from them.

Only problem I can see, is loading more than one. You'd need to re-code and re-compile each DLL to work as a unit instead of a d3d8.dll file.

The way I see it, you can't use the original DLLs for the mods no matter how you code the loader because many of them use the same functions for different things. (not all people code the same way)
here is the first draft of the source code and a compiled version in the attachment for testing purposes. I have for now implemented the ini file option.

BTW: I must admit, that in opposit to this my first idea was much more complicated. I thougth about an approach, where custom Dlls could be loaded and unloaded at runtime.

The source of the current solution is quite small and simple. There is only one unpleasant issue still there, I have also seen this in the source of Drift Gauge and in the tutorial sources from mikoweb. So I have to assume more dlls derived from this will have this issue too. Me must not call FreeLibrary inside the DllMain function (the MSDN says so). But for now it works. Nevertheless I would like to know where would be the appropriate location to call FreeLibrary, maybe in the Release function of the D3D8 interface?

@DragonCommando: Turbo Pascal, jeah, good language with a rigorous syntax check, very good to avoid a lot of unnecessary bugs. I loved this language, but it is a long time ago, that I have used it.
Oh, and believe: the attached dll will work with every addon D3D8.dll, they do not need to be recompiled. I have tested this with one of my own modded D3D8 wrapper Dlls.

regards
Sören


#include <windows.h>


// we do not take care of the type of the pointer
#define IDirect3D8 void


HINSTANCE g_hNextDll;
void LoadNextDll(void);


BOOL APIENTRY DllMain(HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
g_hNextDll = NULL;
break;

case DLL_PROCESS_DETACH:
if (g_hNextDll)
{
FreeLibrary(g_hNextDll);
g_hNextDll = NULL;
}
break;
}

return TRUE;
}


// Exported function (export declared in the .def file)
IDirect3D8* WINAPI Direct3DCreate8(UINT SDKVersion)
{
if (!g_hNextDll) LoadNextDll();

// Hooking IDirect3D Object from Original Library
typedef IDirect3D8 *(WINAPI* D3D8_Type)(UINT SDKVersion);
D3D8_Type D3DCreate8_fn = (D3D8_Type)GetProcAddress(g_hNextDll, "Direct3DCreate8");
if (!D3DCreate8_fn)
{
ExitProcess(0); // exit the hard way
}

return D3DCreate8_fn(SDKVersion);
}


void LoadNextDll(void)
{
char path2dll[MAX_PATH];
char path2ini[MAX_PATH];

if (GetModuleFileName(NULL, path2ini, sizeof(path2ini)))
{
// get the path of the LFS folder
while ((path2ini[strlen(path2ini) - 1] != '\\') && (strlen(path2ini) > 0))
path2ini[strlen(path2ini) - 1] = 0;

strcat(path2ini, "D3D8.ini");
GetPrivateProfileString("D3D8Wrapper", "ProxyDll", "",
path2dll, sizeof(path2dll), path2ini);

if (strlen(path2dll) > 0)
{
g_hNextDll = LoadLibrary(path2dll);
if (g_hNextDll)
return;
}
}

// no ini file there or loading the custom dll failed, so let's load the systems d3d8.dll

GetSystemDirectory(path2dll, MAX_PATH);
strcat(path2dll, "\\d3d8.dll");
g_hNextDll = LoadLibrary(path2dll);

if (!g_hNextDll)
{
ExitProcess(0); // exit the hard way
}
}

Attached files
commonD3D8.zip - 11.4 KB - 197 views
Oh, thats cool.

I sort of understand how it works by looking at the code.
But does it let you run more than one mod at a time? You didn't make that clear, and I lack the experiance in C++ to see it in the code.

This probably isn't something I should be sticking my nose in since I'm such a noob, but I'm just curious since I had an idea like this a while back, but don't have the skill in programming to do it yet.
Quote from Soeren Scharf :this is exactly what is written above.

No, it really is NOT... A chain of proxy dlls is different then a dll that loads all other dlls.

My idea:

Proxy dll 1
/ | \
/ | \
/ | \
Proxy dll 3 | \
| Proxy dll 5
Proxy dll 4


wheel4hummer's model is the best way to do it since proxy dll's 3, 4 and 5 don't need to be coded in any special way. Is it a lot tougher to do it his way?
some comments to the suggestion of wheel4hummer.

1) We would have to make a decision if:
  • we want compatibility, so all existing proxy dlls remain working without code changes, or
  • we want to load more than one proxy dll, this will of course require code changes
2) The picture by wheel4hummer is not complete (assuming that there will be no code changes as stated by Peptis). Every of the existing proxy dlls is designed to load the systems D3D8.dll, so the complete picture is:

Proxy dll 1
/ | \
/ | \
Proxy dll 3 | \
/ | Proxy dll 5
/ Proxy dll 4 \
D3D8.dll | \
| D3D8.dll
D3D8.dll

So if the application calls the IDirect3DDevice8::Present function of Proxy dll 1, then this dll will call the IDirect3DDevice8::Present functions of Proxy dll 3, 4 and 5. Every of these dlls will paint some stuff and then call the original IDirect3DDevice8::Present function of the system. You see: every call of the IDirect3DDevice8::Present function from the application will result in 3 calls of the original IDirect3DDevice8::Present function. Therefore the the proxy dlls would require code changes.

3) I do not see the point with more than one proxy dll. In most cases LFS addons are insim addons. Since there is only one insim application possible at one moment, only one insim application could be active anyway.

kind regards
Sören
Quote from Soeren Scharf :
3) I do not see the point with more than one proxy dll. In most cases LFS addons are insim addons. Since there is only one insim application possible at one moment, only one insim application could be active anyway.

A post from Scawen.
http://www.lfsforum.net/showthread.php?p=394977#post394977
We will get TCP instead of UDP for most packets. Also more then one InSim port.
And that in patch X

EDIT:
I´m not complitly sure if more then one InSim connection will come in patch X because Scawen only affirmed the InSim rewrite.

FGED GREDG RDFGDR GSFDG