C++ Question Static calls between 2 classes in one library

Abdullah Radwan

Addon Developer
Addon Developer
Joined
Aug 20, 2017
Messages
314
Reaction score
284
Points
78
Location
Cairo
Hello,

I want to make the system in the attachments.

The custom cargoes inherit the custom cargo interface. The custom cargo interface will make static calls to add the cargoes to the main API.

This how it's done in code:

Custom Cargo Vessel:
Code:
class Custom: public VESSEL4, public CustomCargo {
public:
	Custom(OBJHANDLE hVessel, int flightmodel) : VESSEL4(hVessel, flightmodel), CustomCargo(hVessel, false) {};
	~Custom();

	ATTACHMENTHANDLE GetCargoAttachmentHandle() override;
	bool IsUnpacked() override;
	void CargoGrappled() override;
	void CargoReleased() override;
	bool PackCargo() override;
	bool UnpackCargo() override;
};

Custom Cargo Interface:
Code:
CustomCargo.h:

class __declspec(dllexport) CustomCargo
{
public:
	CustomCargo(OBJHANDLE handle, bool unpackable);

	virtual ATTACHMENTHANDLE GetCargoAttachmentHandle() = 0;

	virtual bool IsUnpacked() = 0;

	virtual void CargoGrappled() = 0;

	virtual void CargoReleased() = 0;

	virtual bool PackCargo() = 0;

	virtual bool UnpackCargo() = 0;

	virtual ~CustomCargo();

private:
	friend class UCSO_API;

	OBJHANDLE handle;
	bool unpackable;
};

================================

CustomCargo.cpp:

#include "CustomCargo.h"
#include "UCSO_API.h"

CustomCargo::CustomCargo(OBJHANDLE handle, bool unpackable)
{
	this->handle = handle;
	this->unpackable = unpackable;

	UCSO_API::AddCustomCargo(this);
}

CustomCargo::~CustomCargo() { UCSO_API::DeleteCustomCargo(this); }

Main Cargo API:
Code:
UCSO_API.h:

class UCSO_API
{
private:
	friend class CustomCargo;
	static void AddCustomCargo(CustomCargo* cargo);
	static void DeleteCustomCargo(CustomCargo* cargo);
	static std::vector<CustomCargo*> customCargoes;
};

================================

UCSO_API.cpp:

#include "UCSO_API.h"

std::vector<CustomCargo*> UCSO_API::customCargoes;

void UCSO_API::AddCustomCargo(CustomCargo* cargo)
{
	customCargoes.push_back(cargo);
}

void UCSO_API::DeleteCustomCargo(CustomCargo* cargo)
{
	std::vector<CustomCargo*>::iterator it = find(customCargoes.begin(), customCargoes.end(), cargo);
	if (it != customCargoes.end()) customCargoes.erase(it);
}

The main cargo API and custom cargo interface are all in one project and compiled into one .lib file.

The code works until the custom cargo interface calls the static add function. The problem is the method is never called actually. So the custom cargo interface constructor is executed correctly, however, the main cargo API AddCustomCargo method isn't actually called, and the custom cargo isn't added to the customCargoes vector.

I am trying to use the design @Enjo used in his HUD Drawer SDK.
 

Attachments

  • Cargo Interface.png
    Cargo Interface.png
    55.8 KB · Views: 11
Last edited:

Face

Well-known member
Orbiter Contributor
Addon Developer
Beta Tester
Joined
Mar 18, 2008
Messages
4,390
Reaction score
577
Points
153
Location
Vienna
The code works until the custom cargo interface calls the static add function. The problem is the method is never called actually. So the custom cargo interface constructor is executed correctly, however, the main cargo API AddCustomCargo method isn't actually called, and the custom cargo isn't added to the customCargoes vector.

How do you know this? Stepped through debugging until the static function call, then nothing happens?

Multiple inheritance in C++ always was kind of a crutch, I wouldn't be surprised if something strange happens in the constructor chain. Perhaps "this" is returning an unexpected type, since the CustomCargo ctor is called after the VESSEL4 ctor?
 

Abdullah Radwan

Addon Developer
Addon Developer
Joined
Aug 20, 2017
Messages
314
Reaction score
284
Points
78
Location
Cairo
How do you know this? Stepped through debugging until the static function call, then nothing happens?

Multiple inheritance in C++ always was kind of a crutch, I wouldn't be surprised if something strange happens in the constructor chain. Perhaps "this" is returning an unexpected type, since the CustomCargo ctor is called after the VESSEL4 ctor?

Yes. It reaches up to the custom cargo interface constructor then nothing happens. The static function isn't called, although the line is actually executed.

This way is the same way used in Enjo's HUD Drawer SDK. I don't know why it doesn't work for me.

I highly doubt "this" is returning unexpected type, since the application would crash if this happens, but it continues normally without crashing. It looks like the static function call went lost!
 

Face

Well-known member
Orbiter Contributor
Addon Developer
Beta Tester
Joined
Mar 18, 2008
Messages
4,390
Reaction score
577
Points
153
Location
Vienna
Yes. It reaches up to the custom cargo interface constructor then nothing happens. The static function isn't called, although the line is actually executed.

This way is the same way used in Enjo's HUD Drawer SDK. I don't know why it doesn't work for me.

I highly doubt "this" is returning unexpected type, since the application would crash if this happens, but it continues normally without crashing. It looks like the static function call went lost!

Well, calls don't go lost as much as an unexpected type would crash the app, so there obviously is something strange going on.

So just to be clear: you have one DLL (the custom "vessel") which references another DLL (the custom cargo "interface" and API implementation). You followed the code from the vessel DLL to the API DLL with the debugger, and despite the call being executed with "step into" feature, you are not taken to the static function in the debugger. I can only suggest to look at the disassembly, then.
 

jangofett287

Heat shield 'tester'
Joined
Oct 14, 2010
Messages
1,150
Reaction score
13
Points
53
Just to sanity check, are you debugging with optimizations enabled?
 

Abdullah Radwan

Addon Developer
Addon Developer
Joined
Aug 20, 2017
Messages
314
Reaction score
284
Points
78
Location
Cairo
I think I have understood it now.

See the diagram in the attachments.

It looks like the custom cargo gets an instance of the .lib file, and the custom vessel gets its own instance.

When the custom cargo calls, the call reaches to the main API in its instance, which is different from the custom vessel main API. This way, the call never arrives at the custom vessel main API (which I want).

This answer on Stackoverflow confirms my conclusion.

How can this problem be solved? By making the library dynamic? I am not really sure as my experience in C++ in such topis is not that great.

I want to make a universal .lib file so the same file (not copies of it) is used in all custom vessels and cargoes. Is such thing possible?
 

Attachments

  • Static library.png
    Static library.png
    114.9 KB · Views: 9

Face

Well-known member
Orbiter Contributor
Addon Developer
Beta Tester
Joined
Mar 18, 2008
Messages
4,390
Reaction score
577
Points
153
Location
Vienna
I want to make a universal .lib file so the same file (not copies of it) is used in all custom vessels and cargoes. Is such thing possible?

Usually you solve this architecture with a lib and a shared DLL. In the lib, you define the classes and/or static function signatures. But instead of full implementations, you make stubs that forward the calls to a late-bound DLL. This way people can include the lib in their code and get the class hierarchy compiled into their binaries, but the actual implementation with static data-structures remains in the single DLL runtime instance.
The late-binding makes the use of the shared DLL optional, i.e. if users implement a vessel that uses your platform, it will work - though with reduced functionality - without your platform installed, like e.g. XRSound.

For this you should get yourself familiar with LoadLibrary() and GetProcAddress(), as it lays at the heart of most late-binding techniques. I'd also suggest to drop the multiple inheritance and ctor housekeeping. Instead, use the factory pattern to get a cargo object for the vessel. This would make release management a bit easier for you and your user-base.
 

Abdullah Radwan

Addon Developer
Addon Developer
Joined
Aug 20, 2017
Messages
314
Reaction score
284
Points
78
Location
Cairo
Usually you solve this architecture with a lib and a shared DLL. In the lib, you define the classes and/or static function signatures. But instead of full implementations, you make stubs that forward the calls to a late-bound DLL. This way people can include the lib in their code and get the class hierarchy compiled into their binaries, but the actual implementation with static data-structures remains in the single DLL runtime instance.
The late-binding makes the use of the shared DLL optional, i.e. if users implement a vessel that uses your platform, it will work - though with reduced functionality - without your platform installed, like e.g. XRSound.

For this you should get yourself familiar with LoadLibrary() and GetProcAddress(), as it lays at the heart of most late-binding techniques. I'd also suggest to drop the multiple inheritance and ctor housekeeping. Instead, use the factory pattern to get a cargo object for the vessel. This would make release management a bit easier for you and your user-base.

I understood your idea, but how can it be applied with DLLs? See this Microsoft page.
Variable Scope

Variables that are declared as global in a DLL source code file are treated as global variables by the compiler and linker, but each process that loads a given DLL gets its own copy of that DLL's global variables. The scope of static variables is limited to the block in which the static variables are declared. As a result, each process has its own instance of the DLL global and static variables by default.

If every process will get its own instance of the DLL global and static variables, how can my idea be applied?
 

Face

Well-known member
Orbiter Contributor
Addon Developer
Beta Tester
Joined
Mar 18, 2008
Messages
4,390
Reaction score
577
Points
153
Location
Vienna
If every process will get its own instance of the DLL global and static variables, how can my idea be applied?

There is only one process: orbiter.exe (or orbiter_ng.exe). Therefore, once one custom cargo DLL late-bound the common cargo DLL, there will be one set of global variables for that DLL. Later custom cargo DLLs that also late-bind the common cargo DLL will get the very same set.
 

Abdullah Radwan

Addon Developer
Addon Developer
Joined
Aug 20, 2017
Messages
314
Reaction score
284
Points
78
Location
Cairo
There is only one process: orbiter.exe (or orbiter_ng.exe). Therefore, once one custom cargo DLL late-bound the common cargo DLL, there will be one set of global variables for that DLL. Later custom cargo DLLs that also late-bind the common cargo DLL will get the very same set.

Oh, I thought that each time you load the DLL a new process is created. I understand it now. So you get the same DLL for every LoadLibrary in the running Orbiter instance.

I tried it and it worked without problems :thumbup: Thank you so much for your help!
 

Linguofreak

Well-known member
Joined
May 10, 2008
Messages
5,017
Reaction score
1,254
Points
188
Location
Dallas, TX
Oh, I thought that each time you load the DLL a new process is created.

No, that's really the difference between a DLL and an executable: An executable is loaded when a process is created and provides the main program for that process. A DLL is a file that contains helper code for other programs, and is loaded into the address space of each program that needs it. It is never launched as the initial program for a process.
 
Top