C++ Question SDK preparation. How to know if the plugin module is loaded and use it as a condition

fred18

Addon Developer
Addon Developer
Donator
Joined
Feb 2, 2012
Messages
1,667
Reaction score
104
Points
78
Hi Guys,
I have a question which is pretty much for the C++ experts of the forum.

I'm starting to develop the SDK for my Space Network and, since I'm learning step by step this, I ecountered an issue that I don't know how to solve.

First, the good part:

I've created a SpaceNetworkSDK.h file which contains just the follow for the moment:
Code:
#ifndef __SPACENETWORKSDK_H
#define __SPACENETWORKSDK_H

#pragma comment(lib,"SpaceNetwork.lib")

#define DLLEXPORT __declspec(dllexport)
#define DLLIMPORT __declspec(dllimport)


#ifdef SN_API_IMPLEMENTATION
#define SPACENETWORKFUNC DLLEXPORT
#else
#define SPACENETWORKFUNC DLLIMPORT
#endif

	SPACENETWORKFUNC void OrbiterRootOut();

#endif

in my SpaceNetwork class implementation I have:

Code:
SpaceNetwork *SpaceN; // declared here a pointer to a global spacenetwork class instance

SpaceNetwork::SpaceNetwork(HINSTANCE hDLL) : oapi::Module(hDLL){
	//... among the rest
	SpaceN = this; setting the global pointer to the plugin instance, so I can use SpaceN whenever I want
	
}

void  SpaceNetwork::OrbiterRootOut() {
	sprintf(oapiDebugString(), "%s",orbiter_root);
	return;
}

void DLLEXPORT OrbiterRootOut() {     // using this because I want to export single methods, not the whole class
	return SpaceN->OrbiterRootOut();
}

Now, I've tried this with the "official" Deltaglider code and it works like a charm: I just put "OrbiterRootOut()" in the DG's clbkPostStep and everything worked perfectly.

The point is: if I deactivate the SpaceNetwwork Module from the Orbiter Launchpad my poor Deltaglider gets crazy (no meshes on load, no fuel, nothing, clearly some memory issue). Now, of course I want the implementation to be compatible with everyone, so if the module is not loaded I'd like the functions simply not to return anything. How can I do that?

Since the module DLL is not loaded any code implementation in SpaceNetwork class is useless... that's why I need help from somebody expert!

Thanks in advance!
 

martins

Orbiter Founder
Orbiter Founder
Joined
Mar 31, 2008
Messages
2,448
Reaction score
462
Points
83
Website
orbit.medphys.ucl.ac.uk
You could try the GetModuleHandle function.

But it would be very cumbersome to build that check into every vessel code. It might be more elegant to split your project into two DLLs:

  • One DLL is the Orbiter plugin module containing the singleton SpaceNetwork class
  • The other DLL implements the API for communication between individual vessels and the module. This would be linked by every vessel that wants to be able to communicate with the SpaceNetwork.
That second DLL would check if the module is loaded (for example, during a mandatory InitSpaceNetworkConnection call, or in the constructor of a SpaceNetworkInterface class). If successful, API requests would be passed through to the module, otherwise any communication attempt would just raise an error flag.
 

fred18

Addon Developer
Addon Developer
Donator
Joined
Feb 2, 2012
Messages
1,667
Reaction score
104
Points
78
You could try the GetModuleHandle function.

But it would be very cumbersome to build that check into every vessel code. It might be more elegant to split your project into two DLLs:

  • One DLL is the Orbiter plugin module containing the singleton SpaceNetwork class
  • The other DLL implements the API for communication between individual vessels and the module. This would be linked by every vessel that wants to be able to communicate with the SpaceNetwork.
That second DLL would check if the module is loaded (for example, during a mandatory InitSpaceNetworkConnection call, or in the constructor of a SpaceNetworkInterface class). If successful, API requests would be passed through to the module, otherwise any communication attempt would just raise an error flag.

Thank you!

So, basically if the user wants the SpaceNetwork he will simply load the SpaceNetwork module, while if he wants to implement his own communication methods he will have to load also the "SpaceNetworkVesselCom" module in the launchpad, right?

Will this approach work anyway if a vessel with the implementation is passed to somebody that hasn't the spacenetwork addon installed? Because I think I'm missing something: if the SpaceNetworkVesselCom is not installed/loaded wouldn't it be the same situation?
 

martins

Orbiter Founder
Orbiter Founder
Joined
Mar 31, 2008
Messages
2,448
Reaction score
462
Points
83
Website
orbit.medphys.ucl.ac.uk
Will this approach work anyway if a vessel with the implementation is passed to somebody that hasn't the spacenetwork addon installed? Because I think I'm missing something: if the SpaceNetworkVesselCom is not installed/loaded wouldn't it be the same situation?

No, the SpaceNetworkVesselCom isn't defined as an Orbiter plugin, but rather as a dll that is directly linked to the vessel dll. So a vessel addon developer who wants to make use of SpaceNetwork would install SpaceNetwork, build his vessel dll linking to the SpaceNetworkVesselCom dll, and distribute his vessel addon including the SpaceNetworkVesselCom dll (but without the SpaceNetwork dll, which the user may or may not install). Incidentally, SpaceNetworkVesselCom wouldn't have to be implemented as a dll, but could also be a static library.

Of course this opens the gates to the usual version/backward compatibility hell. So the com dll should not only check that the module is present, but also that its version is compatible.
 

fred18

Addon Developer
Addon Developer
Donator
Joined
Feb 2, 2012
Messages
1,667
Reaction score
104
Points
78
No, the SpaceNetworkVesselCom isn't defined as an Orbiter plugin, but rather as a dll that is directly linked to the vessel dll. So a vessel addon developer who wants to make use of SpaceNetwork would install SpaceNetwork, build his vessel dll linking to the SpaceNetworkVesselCom dll, and distribute his vessel addon including the SpaceNetworkVesselCom dll (but without the SpaceNetwork dll, which the user may or may not install). Incidentally, SpaceNetworkVesselCom wouldn't have to be implemented as a dll, but could also be a static library.

Of course this opens the gates to the usual version/backward compatibility hell. So the com dll should not only check that the module is present, but also that its version is compatible.

that's clear now, I'll study on this, thank you very much again! :tiphat:
 

Enjo

Mostly harmless
Addon Developer
Tutorial Publisher
Donator
Joined
Nov 25, 2007
Messages
1,665
Reaction score
13
Points
38
Location
Germany
Website
www.enderspace.de
Preferred Pronouns
Can't you smell my T levels?
It might be more elegant to split your project into two DLLs
Fred, I'm not sure if I understand your requirements completely, but if Martins is right, you might want to have a look of the architecture of ModuleMessaging SDK , where I have exactly, what Martins described.
Some advanced ideas like version checking are implemented in https://www.orbithangar.com/searchid.php?ID=6966.
Also, maybe you could try to link against the second one to spare yourself a lot of redesigning of a wheel.
 

Face

Well-known member
Orbiter Contributor
Addon Developer
Beta Tester
Joined
Mar 18, 2008
Messages
4,403
Reaction score
581
Points
153
Location
Vienna
In your use-case, I would go the late-binding route. Basically it goes like this:
  • Create an SDK lib that contains late-binding and fallback code for every function you have.
  • Create the middle-ware DLL that implements the functions and gets loaded automatically - if present - by the SDK lib.
  • Include the SDK lib in all "user" plugins.

In essence, it is what Dan uses for his sound and UMmu features. If you want, I can give you boiler-plate code.
 

fred18

Addon Developer
Addon Developer
Donator
Joined
Feb 2, 2012
Messages
1,667
Reaction score
104
Points
78
Thanks Enjo, I'll give a good look at it!!! :tiphat:

In essence, it is what Dan uses for his sound and UMmu features. If you want, I can give you boiler-plate code.

I noticed that in orbiter sound sdk the functions are defined without the dll import feature and without being part of a class, they are just there.
like:

Code:
int ConnectToOrbiterSoundDLL(OBJHANDLE Obj);

I don't know how he can do that, the only reason I can think of is that the lib is a static lib, but I don't know if I'm right.
 

Face

Well-known member
Orbiter Contributor
Addon Developer
Beta Tester
Joined
Mar 18, 2008
Messages
4,403
Reaction score
581
Points
153
Location
Vienna
I don't know how he can do that, the only reason I can think of is that the lib is a static lib, but I don't know if I'm right.

The lib contains the late-binding and fallback code. The functions there are just stubs that call the late-bound function from the main DLL. If the late-binding failed, they return some useful default value.

I used this pattern for some shared libs in my projects, too. It works quite well and don't let DLLs that use it crap out with some error when it is not there.

EDIT: Just saw that it is basically what Martin suggested above, just with the static lib approach. I wouldn't call it a communication lib or module, though. It really is just a collection of stubs with some init method that does LoadLibrary() followed by many GetProcAddress(). Just tell them that this is the SDK, include the *.h, include the *.lib, and you have to have SpaceNetwork installed to have all those functions do something. If it is not installed, the functions don't stop Orbiter from loading the user's module, but they also don't do anything useful.
 
Last edited:

fred18

Addon Developer
Addon Developer
Donator
Joined
Feb 2, 2012
Messages
1,667
Reaction score
104
Points
78
Thanks it's quite clear i'll give it a try!

Shall i use also the extern C command in order to make the name of the function unique and traceable by the GetProcAddress function?
 

Face

Well-known member
Orbiter Contributor
Addon Developer
Beta Tester
Joined
Mar 18, 2008
Messages
4,403
Reaction score
581
Points
153
Location
Vienna
Thanks it's quite clear i'll give it a try!

Shall i use also the extern C command in order to make the name of the function unique and traceable by the GetProcAddress function?

Well, I did so in the middle-ware DLL. In principle, you could also directly link to it without the use of the library, so normal DLL export mechanisms should apply.

Example implementation of a GetVersion function in the middle-ware:
Code:
namespace MyLib
{
extern "C"
{
__declspec(dllexport) float __cdecl GetVersion(){return (float)1.0;}
}
}
Example header in the lib:
Code:
namespace MyLib
{
int Init();
float GetVersion();
}
Example library implementation:
Code:
#include "MyLib.h"

typedef float (* FLOATGETTER) (void);

namespace MyLib
{
HMODULE g_Module;
FLOATGETTER g_GetVersion;

int Init()
{
	g_Module=LoadLibrary("Modules\\MyLib.dll");
	if (g_Module==NULL) return -1;	
	g_GetVersion=(FLOATGETTER)GetProcAddress(g_Module, "GetVersion");
	if (GetVersion()<1.0)
	{
		FreeLibrary(g_Module);
		g_GetVersion=(FLOATGETTER)NULL;
		return -2;
	}
	return 0;
}
float GetVersion(){if (!g_GetVersion) return -1; return g_GetVersion();}
}

Of course some exit function would be nice, too.
 

martins

Orbiter Founder
Orbiter Founder
Joined
Mar 31, 2008
Messages
2,448
Reaction score
462
Points
83
Website
orbit.medphys.ucl.ac.uk
One question though. Should it really be LoadLibrary/FreeLibrary? My understanding is that MyLib.dll is an Orbiter module that is activated separately by the user. I guess if it has been activated then LoadLibrary/FreeLibrary is ok, since it will only modify a reference counter. But what if the user hadn't activated MyLib? Wouldn't in that case the vessel load it on its own, even though the user hadn't specifically requested it? That could be bad news, since this would bypass Orbiter's module registration mechanism, which might mean that no module callback functions get called.
 

fred18

Addon Developer
Addon Developer
Donator
Joined
Feb 2, 2012
Messages
1,667
Reaction score
104
Points
78
Maybe a GetModuleHandle check instead of load library could solve this? Just guessing
 

Face

Well-known member
Orbiter Contributor
Addon Developer
Beta Tester
Joined
Mar 18, 2008
Messages
4,403
Reaction score
581
Points
153
Location
Vienna
One question though. Should it really be LoadLibrary/FreeLibrary? My understanding is that MyLib.dll is an Orbiter module that activated separately by the user. I guess if it has been activated then LoadLibrary/FreeLibrary is ok, since it will only modify a reference counter. But what if the user hadn't activated MyLib? Wouldn't in that case the vessel load it on its own, even though the user hadn't specifically requested it? That could be bad news, since this would bypass Orbiter's module registration mechanism, which might mean that no module callback functions get called.

Nothing stops you from coding your middle-ware to only get active on the first callback from Orbiter. If the user module starts up, but the middle-ware DLL is not loaded by Orbiter, it might get loaded into memory. Lacking the activation from the first callback though, it is just as inactive as if it was never loaded to begin with. The user module can't tell the difference.

---------- Post added at 16:21 ---------- Previous post was at 16:14 ----------

Maybe a GetModuleHandle check instead of load library could solve this? Just guessing

Well, in my use-case with the common library, that would not have worked, because it made zero sense to create a separate user-facing module that needs activation in Orbiter's modules tab. If the SpaceNetwork is unusable without e.g. some MFD or custom dialog GUI, it might be better to couple it more tightly to user activation, of course.
 

fred18

Addon Developer
Addon Developer
Donator
Joined
Feb 2, 2012
Messages
1,667
Reaction score
104
Points
78
Nothing stops you from coding your middle-ware to only get active on the first callback from Orbiter. If the user module starts up, but the middle-ware DLL is not loaded by Orbiter, it might get loaded into memory. Lacking the activation from the first callback though, it is just as inactive as if it was never loaded to begin with. The user module can't tell the difference.

---------- Post added at 16:21 ---------- Previous post was at 16:14 ----------



Well, in my use-case with the common library, that would not have worked, because it made zero sense to create a separate user-facing module that needs activation in Orbiter's modules tab. If the SpaceNetwork is unusable without e.g. some MFD or custom dialog GUI, it might be better to couple it more tightly to user activation, of course.

I'm getting a bit lost by this, but I'll shortly give it a run and hopefully it will clear my mind.

Anyway, just to get clear on how does the SpaceNetwork work: it is a plugin, that absolutely needs activation in launchpad to be active. Once active it has a dialog for general control, I think I'll add also an MFD, but all within the same dll, so one activation in the launchpad will give the user the full package.

The SpaceNetwork will work for any vessel with the included set of commands, dialog etc, but I want the users to have the chance to add custom items to their vessels relevant to the SpaceNetwork. For example in a probe coded by a developer an option to send an "I'm alive" message to ground once the solar panels are open, or to send (simluated) measurements of scientific data, or whatever the user might like to do.

To do this I need to create an SDK which will allow the user to do that. That was working in the first example, with the restriction that if the SpaceNetwork module was not activated in the Launchpad then the vessel "blew up". That is not acceptable since users will want to add functionality but not binded to SpaceNetwork actual activation, so I need a way to precheck if the module is actually activated or not.

I found that GetModuleHandle("SpaceNetwork.dll") works quite well, but still I can't use dllimport from a deactivated SpaceNetwork.dll without blewing the vessel. So that's the point where the middle library comes in (shall it be a dll, or just the lib file of it?).

So, I'm thinking of :
- the SpaceNetwork code with the dllexport calls to the relevant functions
- the middle library code with the GetModuleHandle check and all the relevant dllimport
- the middle library header with the "SpaceNetwork API" calls
- the vessel code with the linker pointing also to the Middle lib file and with the header included.

If that works then it's definitely thumbs up, I'll start to code and see :)
 

Face

Well-known member
Orbiter Contributor
Addon Developer
Beta Tester
Joined
Mar 18, 2008
Messages
4,403
Reaction score
581
Points
153
Location
Vienna
So, I'm thinking of :
- the SpaceNetwork code with the dllexport calls to the relevant functions
- the middle library code with the GetModuleHandle check and all the relevant dllimport
- the middle library header with the "SpaceNetwork API" calls
- the vessel code with the linker pointing also to the Middle lib file and with the header included.

That should work (without the dllimport). SpaceNetwork itself is the middle-ware in my terminology, the lib and header of your SDK is where you have the GetModuleHandle and the late-binding with GetProcAddress in a static library. The user vessel code will use that lib and header.

I thought that perhaps user vessels can exchange messages without the need to activate some central GUI, so that functionality without explicit module activation is also possible. If this is not the case, GetModuleHandle is the better approach, of course.
 

fred18

Addon Developer
Addon Developer
Donator
Joined
Feb 2, 2012
Messages
1,667
Reaction score
104
Points
78
all right, this works!

SpaceNetwork code

Code:
void SpaceNetwork::OrbiterRootOut() {
	sprintf(oapiDebugString(), "%s",orbiter_root);
	return;
}

DLLCLBK void SDK_OrbiterRootOut() {
	return SpaceN->OrbiterRootOut();
}

Mid Library Header
Code:
#pragma once

int Init();
void  OrbiterRootOut();

Mid Library Code
Code:
#pragma once

#include "SpaceNetworkSDK.h"
#include <Windows.h>


typedef void (* MYVOID)(void);
MYVOID g_OrbiterRootOut;

int Init(){
	if (GetModuleHandle("SpaceNetwork.dll") != NULL) {
		HMODULE SNModule = GetModuleHandle("SpaceNetwork.dll");
		g_OrbiterRootOut =(MYVOID)GetProcAddress(SNModule, "SDK_OrbiterRootOut");
		return 1;
	}
	return -1;
}
void OrbiterRootOut() {
	if (!g_OrbiterRootOut) { return; }
	return g_OrbiterRootOut();
}

and in DeltaGlider clbkpostStep:
Code:
void DeltaGlider::clbkPostStep (double simt, double simdt, double mjd)
{
	// damage/failure system
	if (bDamageEnabled) TestDamage ();

	ComponentVessel::clbkPostStep (simt, simdt, mjd);
[B]	Init();
	OrbiterRootOut();[/B]
}

There are still things that I don't completely get (like the typedef part) and I don't know if it's better to group the methods inside a class to avoid global variables, but at least it's a strong beginning!!! thanks a lot!!!!
 
Top