Advanced Question getting a pointer to a vessel child-class - possible?

jedidia

shoemaker without legs
Addon Developer
Joined
Mar 19, 2008
Messages
11,319
Reaction score
2,789
Points
203
Location
between the planets
I'm currently trying to make my IMS vessels talk to each other to transfer propellant and consumables. While there are some hacky solutions to pull this off, it would be really nice if I could get a pointer to the actual IMS sub-class of the other vessel.
I looked at dynamic_cast, but that needs the base class to be polymorphic to allow casting from base to derivative. Currently, I'm pretty much wondering if it can be done at all, or if I have to go with my hack...
 
Why does dynamic_cast not work?
 
I'm currently trying to make my IMS vessels talk to each other to transfer propellant and consumables. While there are some hacky solutions to pull this off, it would be really nice if I could get a pointer to the actual IMS sub-class of the other vessel.
I looked at dynamic_cast, but that needs the base class to be polymorphic to allow casting from base to derivative. Currently, I'm pretty much wondering if it can be done at all, or if I have to go with my hack...

I think I don't understand this enough to give a clear answer. Can you elaborate on what you mean with "vessel child-class"?

If I have understood the V2V-via-casting pattern correctly, you have vessel class A and class B, both deriving from either VESSEL2/3 directly, or from a common base class MyVesselBase. Then, in e.g. A, you get the OBJHANDLE to B, confirm that it is indeed of class B, oapiGetVesselInterface() a pointer, and simply cast it to B. You can then access all the values in B's public declaration, given that you've included the header of that class B. Of course the later is clear, because the compiler wouldn't compile without the declaration for B that you used in the cast.

Maybe you are stumbling over the problem that you can't simply call B's public methods, because the linker can't resolve the function entry points. In this case, the easiest way is to declare them virtual, so C++ creates a virtual table in the object header, effectively making the linker resolve the entry points to that table, even without export/import mechanisms.

This is the way I'm doing it in the Ascension Ultra inter-module communication. If you need an example, just drop me a note.

regards,
Face
 
I also did it like that in SSU and some other places, C++ RTTI does work with orbiter as far as I can tell. As long as the function is virtual, you can overload it.
 
Then, in e.g. A, you get the OBJHANDLE to B, confirm that it is indeed of class B, oapiGetVesselInterface() a pointer, and simply cast it to B.

I think that is what I'm trying to do, but I have no expierience with casting whatsoever (well, apart from a bit of explicit c-style casting, but that would seem a bit too unsave here, no?), so this is a bit tricky for me.
For starters, how do I check what class an OBJHANDLE points to? (now, I know how to check wheather it is a vessel, but I still need to know if it is a certain derivative of the vessel class).

And then of course the other problem, actually casting the thing. I tried dynamic cast, and the compiler tells me that a base class must be polymorphic in order to cast from a base class to a derivative (which I would have to do, as I get a VESSEL back from oapiGetVesselInterface(), but in the end need class IMS, which is a derivative of VESSEL). And the VESSEL class is not polymorphic, so how would I cast it into my derivative?
 
You first need to define an abstract vessel interface class between your vessel class and VESSEL3, that you can use for defining the interface functions (virtual functions to be used for interaction).
 
I think that is what I'm trying to do, but I have no expierience with casting whatsoever (well, apart from a bit of explicit c-style casting, but that would seem a bit too unsave here, no?), so this is a bit tricky for me.

My advice would be to actually start with C-style casting. dynamic_cast is just a wrapper around C-style casting that uses the RTTI infrastructure, which is a meta-data system the runtime uses to graft managed information onto unmanaged data objects. For this to work, you'd have to ensure the proper compiler settings are set in all projects. Unfortunately I can't comment on these, as I don't use it in Orbiter, since for me Orbiter's own infrastructure is as good as I need it to be.

For starters, how do I check what class an OBJHANDLE points to? (now, I know how to check wheather it is a vessel, but I still need to know if it is a certain derivative of the vessel class).

Example:
Code:
VESSEL *vessel=oapiGetVesselInterface(objhandle);
if (strcmp(vessel->GetClassName(), "MyIMSClassFileNameForOrbiter")==0)
{
   MyIMSClass *ims=(MyIMSClass *)vessel;
   ims->PublicMethodDeclaredVirtual(whatever);
}
And then of course the other problem, actually casting the thing. I tried dynamic cast, and the compiler tells me that a base class must be polymorphic in order to cast from a base class to a derivative (which I would have to do, as I get a VESSEL back from oapiGetVesselInterface(), but in the end need class IMS, which is a derivative of VESSEL). And the VESSEL class is not polymorphic, so how would I cast it into my derivative?

That's why I prefer plain casting: no cryptic messages :lol: . But then I'm no C++ guru, so I just do what gets me there.

regards,
Face
 
I'm currently trying to make my IMS vessels talk to each other to transfer propellant and consumables. While there are some hacky solutions to pull this off, it would be really nice if I could get a pointer to the actual IMS sub-class of the other vessel.
I looked at dynamic_cast, but that needs the base class to be polymorphic to allow casting from base to derivative. Currently, I'm pretty much wondering if it can be done at all, or if I have to go with my hack...
To get them to "talk to each other", can't you use clbkGeneric? That's what it was made for...
 
Code:
if (strcmp(vessel->GetClassName(), "MyIMSClassFileNameForOrbiter")==0)

Trouble is, I don't even have a static class name (depending on the command module one chooses, the class name varies). But I should be able to get around that by opening the config file and look if it's loading the IMS module... :shifty:

Code:
ims->PublicMethodDeclaredVirtual(whatever);

So only the method I need has to be virtual, not the whole class? well, that'll simplify things a bit...

Thanks a lot for the explanations, I'll see with what kind of calamity I'll end up when I implement it :lol:
 
the class does not need to be virtual but it helps sometimes - but you don't get close to such scenarios that you need it.
 
VESSEL2 class is polymorphic.

You can do for example this:
Code:
VESSEL * vessel = oapiGetVesselInterface (vesselObject);
if (vessel->Version () >= 2 && typeid (*(VESSEL2 *)vessel) == typeid (IMS)) {
    ((IMS *)vessel)->do_something ();
}
Just tested working by comparing if vessel is of ShuttlePB class.
 
Cool. That sample with typeid is going to make stuff a real lot easier, thanks a lot! :thumbup:
 
you can also work like this, is a bit more clean:

Code:
class IMS_VESSEL: public virtual VESSEL3 
{
...
};

class IMS_MODULE1: public virtual IMS_VESSEL {
...
};

In this case, you can test if something uses the interfaces of IMS_VESSEL by:

Code:
IMS_VESSEL* v = dynamic_cast<IMS_VESSEL*>(vessel);
if( v != NULL) 
{
    v->CallThisInterface();
}
 
Code:
if (vessel->Version () >= 2) && typeid (*(VESSEL2 *)vessel) == typeid (IMS))

For some reason, I can't get this to work... I checked the name of the type returned, and for vessel it is always "class Vessel", no matter what vessel is in question (be that an IMS vessel, a DG, or whatever, I just always get "class Vessel").

I guess I'll try Urwumpe's suggestion and see how that works out.
 
Last edited:
For some reason, I can't get this to work... I checked the name of the type returned, and for vessel it is always "class Vessel", no matter what vessel is in question (be that an IMS vessel, a DG, or whatever, I just always get "class Vessel").
Are you sure you're C casting the result of oapiGetVesselInterface into pointer to VESSEL2 class? The `vessel->Version () >= 2` is to check if it can be safely casted.

I just booted into Windows and checked it once again. I get "class VESSEL" only when it's `typeid (*oapiGetVesselInterface (handle)).name ()`. Checking `typeid (*(VESSEL2*)oapiGetVesselInterface (handle)).name ()` returns "class DeltaGlider", "class ShuttlePB", "class ShuttleA" for me. Tested in Visual C++ Express 2008.

After you casted it to (VESSEL2*), you can even do a dynamic cast from it to the IMS class, without even checking the type with typeid, because VESSEL2 is polymorphic, i.e.:
Code:
IMS_VESSEL * v = dynamic_cast<IMS_VESSEL *>((VESSEL2 *)oapiGetInterface (handle));
 
Well, for some reason it never returned true... I had that line pretty much te way you wrote it.

However, I'm doing it with dynamic cast now, which works. There's just one slight problem...
UMMUSDK seems to duplicate VESSEL::Version. I can't call version on a vessel without the linker spitting out errors related to UMMUsdk. So it looks like I can't check the version. May god have mercy on the poor soul that ever tries to attempt to dock a genuine first-generation vessel :shifty:

I think they should be rare enough by now to not be much of a problem.

Just in case anyone's interested in the linker errors:
Code:
UMmuSDK.lib(UMmuSDK.obj) : error LNK2005: "public: int __thiscall VESSEL::Version(void)const " (?Version@VESSEL@@QBEHXZ) already defined in orbiter.lib(Orbiter.exe)
UMmuSDK.lib(universal_mmu.obj) : error LNK2005: "public: int __thiscall VESSEL::Version(void)const " (?Version@VESSEL@@QBEHXZ) already defined in orbiter.lib(Orbiter.exe)
 
I had that line pretty much te way you wrote it.
...Just without the closing parenthesis after "Version () >= 2" and before "&&", which was a copy paste error from shortcutting a couple of ifs into one. :P
 
Ah yes, I did notice that one. Since I had to throw out the version check just to build the thing.
 
Oh great, of course standard config-created vessels are not VESSEL2 :facepalm:

Meaning, currently I get a crash when I dock one of them, and since the modules from which these ships are built are usually config file created, that is not good (extreme understatement).

So I need to get that version checking to run, and I need UMMUsdk in the project. Both together currently don't work at all, as mentioned before. I get the following Linker errors when trying:

Code:
	if (vessel->Version() >= 2)


Code:
UMmuSDK.lib(UMmuSDK.obj) : error LNK2005: "public: int __thiscall VESSEL::Version(void)const " (?Version@VESSEL@@QBEHXZ) already defined in orbiter.lib(Orbiter.exe)
UMmuSDK.lib(universal_mmu.obj) : error LNK2005: "public: int __thiscall VESSEL::Version(void)const " (?Version@VESSEL@@QBEHXZ) already defined in orbiter.lib(Orbiter.exe)

I tried reading up a bit and solving the problem on my own, but the only advice I came up with in the end is "to reverse the loading order of the libraries". Thing is, I don't even know how to do that, as the libs are just added to my project explorer, and I can't seem to adjust their order.

---------- Post added at 06:37 PM ---------- Previous post was at 04:43 PM ----------

I found a short thread on DanStephs forum where someone had a very similar problem. The answer was that orbiter.lib was called twice. Now, if I was any smarter, I would maybe even know what that exactly means and how to avoid it. I tried shifting around the includes in the headers, but al with more or less catastrophic results :shifty:
 
Code:
UMmuSDK.lib(UMmuSDK.obj) : error LNK2005: "public: int __thiscall VESSEL::Version(void)const " (?Version@VESSEL@@QBEHXZ) already defined in orbiter.lib(Orbiter.exe)
UMmuSDK.lib(universal_mmu.obj) : error LNK2005: "public: int __thiscall VESSEL::Version(void)const " (?Version@VESSEL@@QBEHXZ) already defined in orbiter.lib(Orbiter.exe)
Change in the project properties "Configuration Properties > C/C++ > Optimization > Whole Program Optimization" to "No". This works for me. UMmuSDK.lib apparently doesn't like link-time code generation.

And it's a whole way better to move UMmuSDK.lib (similarly OrbiterSoundSDK35.lib, if you use it) to the OrbiterSDK's "lib" folder and include it as additional linker dependency, than include it as a part of your project, as also UMmuSDK.h (similarly OrbiterSoundSDK35.h, MfdSoundSDK35.h for OrbiterSound), which you move to the "include" folder and remove from the project file list (and then you don't use relative paths, but plain file names).
 
Back
Top