API Question Getting an MFD instance

Hlynkacg

Aspiring rocket scientist
Addon Developer
Tutorial Publisher
Donator
Joined
Dec 27, 2010
Messages
1,868
Reaction score
4
Points
0
Location
San Diego
I know it's possible query the current mode of a given MFD, but is it possible to get the actual instance of the MFD class using Orbiter's API?
 
From which side? From the vessel, or from the MFD code?
 
From the vessel.
 
I know it's possible query the current mode of a given MFD, but is it possible to get the actual instance of the MFD class using Orbiter's API?

I don't think that it is possible with the API. It is sure possible with hacks, but then the question is: what is the use-case? If you want to use the standard interface* only, it should be not that hard to do. But if you want to use specific modes, you'd have to reference the appropriate headers to have something you can cast the instance to. Knowing the right class, loading the right header, and overcoming versioning issues (new MFD mode class, old header etc.) would then be the hard part IMHO.

*
Code:
void Update (HDC hDC)
void InvalidateDisplay ()
void InvalidateButtons ()
void Title (HDC hDC, const char *title)
HPEN SelectDefaultPen (HDC hDC, DWORD i)
HFONT SelectDefaultFont (HDC hDC, DWORD i)
bool ConsumeKeyBuffered (DWORD key)
bool ConsumeKeyImmediate (char *kstate)
bool ConsumeButton (int bt, int event)
char *ButtonLabel (int bt)
int ButtonMenu (const MFDBUTTONMENU **menu)
void WriteStatus (FILEHANDLE scn)
void ReadStatus (FILEHANDLE scn)
void StoreStatus ()
void RecallStatus ()
 
If you just need that functionality for a specific MFD implementation, and you're writing that yourself, it should be possible to make it work by having a static member in the MFD class keeping track of created instances and vessels they are assigned to.

Otherwise, I don't quite see why you would even want to get an MFD instance inside the vessel. The common interface and universal application is the entire purpose of an MFD, otherwise the functionality should be implemented as a panel element.
 
If you just need that functionality for a specific MFD implementation, and you're writing that yourself, it should be possible to make it work by having a static member in the MFD class keeping track of created instances and vessels they are assigned to.

He already mentioned that he wants to get the instance from the vessel code side, not from the MFD one.
 
You can use ModuleMessagingExt to safely pass simple variables by value, or more complex data structures by reference. Whilst I have only seen MFD to MFD use-cases so far, there's no restriction preventing vessel to MFD direct interaction. PM me if you need more details.
 
He already mentioned that he wants to get the instance from the vessel code side, not from the MFD one.

I know, maybe I was a bit unclear:
If you need to get the instance of a specific implementation you have the code of, you can arrange for that by the MFD tracking created instances and offering a static accessor to retrieve them. Whether from the vessel or from somewhere else wouldn't matter that much at this point.
 
I know, maybe I was a bit unclear:
If you need to get the instance of a specific implementation you have the code of, you can arrange for that by the MFD tracking created instances and offering a static accessor to retrieve them. Whether from the vessel or from somewhere else wouldn't matter that much at this point.

Well, if the code is in its own "soup", you could achieve that easier by sending the instance pointer over to the connected vessel code via clbkGeneric. This way, the vessel code would not have to load the MFD DLL in order to gain access to the static export, and the MFD code would not have to track instances (although that sure is best-practice in order to have persistent data states).

Generally speaking, if you try to inter-connect your own components, there are many ways to do it. If you try to create interfaces to your components, so that other authors can code against them, this is much harder. Then frameworks like ModuleMessaging are better suited, or even late-binding approaches with static libs like DanSteph's Sound SDKs.

Anyway, it all depends on the use-case, so it would be interesting to hear what Hlynkacg has in mind.
 
The idea was to use have a MFD that would display text messages provided by the vessel (or vessels), similar to how UMMU/UCGO's on-screen messaging works.

---------- Post added at 12:16 ---------- Previous post was at 12:15 ----------

How exactly would I go about sending the instance pointer via clbkGeneric?
 
How exactly would I go about sending the instance pointer via clbkGeneric?

Every custom MFD mode should have a constructor that gets the pointer of the linked vessel as argument. You can use this pointer to get the interface version of the vessel, by means of vessel->Version(). If the returned version number is greater than 1 (that means it is at least VESSEL3), you can cast the vessel pointer to VESSEL3. Then you can call e.g. ((VESSEL3 *)vessel)->clbkGeneric(0x1337, 0, (void *)this) to send the instance pointer to the linked vessel. 0x1337 is just an arbitrary constant - chose one that doesn't collide with other addons.

Just note that it is often a bad idea to access the this pointer of a class before it is fully constructed. So while the above procedure might work and look even elegant if done directly in the constructor, it could bring headache if you access the instance in the scope of the vessel's clbkGeneric handler already. I'd advice to store the vessel pointer in the MFD class and do the transfer in the first Update() cycle.

On the vessel code side, you'll have to implement the clbkGeneric-handler, of course. In the body of the implementation, test incoming messages for the magic constant, then cast the context void pointer to your MFD class definition, which you'd have to include in the vessel code project. You only need the header (*.h), not the implementation (*.cpp).

This is only half of the story, though, because you have to take care of the garbage, too. Whenever Orbiter deactivates the custom MFD mode, the instance gets deleted. So you'll have to also send a message on destruction, otherwise an already registered instance pointer on a vessel side would lead to null-pointer exceptions. So it would be good practice to use the stored vessel pointer to send e.g. ((VESSEL3 *)vessel)->clbkGeneric(0x1337, 1, (void *)this) in order to give the vessel a notification to remove the registration again. In the vessel code, you'd do as with registration before, just notice the parameter value "1" instead of "0", thereby removing the internal storage of the instance pointer.

Of course you could also completely circumvent the MFD instance and directly go for vessel-persistent data. It is best practice to store data objects for each vessel (or vessel/MFD combination) inside the MFD code, so user input survives cockpit view switches. With the above described method, nothing stops you from sending this data objects instead of the plain MFD object.
 
It is best practice to store data objects for each vessel (or vessel/MFD combination) inside the MFD code, so user input survives cockpit view switches.

This is why I wanted the MFD instance in the first place, to get at the data objects. I'm not at my dev PC right now, but I'll try the "clbkGeneric" method later tonight and get back to you.
 
Last edited:
This is why I wanted the MFD instance in the first place, to get at the data objects. I'm not at my dev PC right now, but I'll try the "clbkGeneric" method later tonight and get back to you.

Alright.

I forgot to add that you'll have to declare all the methods in the data class - that you want to access across DLL boundaries - as virtual. Otherwise you'd have to export/import them explicitly in the declaration (data class header). If the methods are virtual, you'll have the JMP address in the virtual table of the class layout, so the using code side does not have to know where the function address is.
 
So if I have this right - you have an MFD that needs to manipulate / display / HUD display strings from vessels or other addins, right? Like a Syslog MFD :). It might take a little mod to the ModuleMessagingExt, but it's doable.

In this implementation, you would have a guarded data structure that each string-originating source would create (e.g. with versioning and sizing protections, maybe a valid/not valid boolean, or a format string and some variables, maybe some color hints, etc.). The 'syslog MFD' would connect to each of the data sources and interrogate the data structure and do the screen display.

Every {configurable time - e.g. 2 secs} you could do a new wildcard scan function of MMExt to find new sources, to update your list of things to display.

Should be quite easy.
 
So if I have this right - you have an MFD that needs to manipulate / display / HUD display strings from vessels or other addins, right? Like a Syslog MFD

Pretty much.

I have multiple instances of "gadget" running on 1 or more vessels that I would like to manage via a single MFD.

I'm envisioning something along the lines of...

Code:
// Something has happened that gadget thinks the player should know about.
gadget::event()
{
   I have a message!

   Is there an instance of "gadget mfd" active?

   if (yes)
   {
       add my message to the MFD's message queue.
   }
   else
   {
       do something else.
   }
}
 
Last edited:
The following is a minimal LoggerMFD: http://snoopie.at/face/beta/LoggerMFD.zip
Included are 2010 binaries of LoggerMFD and ShuttleA to test it.

I've tested it with stock ShuttleA code, which I modified to log every keystroke received like so:
Code:
# HG changeset patch
# User Face
# Date 1477597543 -7200
#      Thu Oct 27 21:45:43 2016 +0200
# Node ID 4d780663f29d57eba46aef4a54b0dceb54ee8ca8
# Parent  7cbfabd57c4566b0a30e3049d4b9e6c5bb21cc1f
adding logger to stock ShuttleA code

diff --git a/Orbitersdk/samples/ShuttleA/ShuttleA.cpp b/Orbitersdk/samples/ShuttleA/ShuttleA.cpp
--- a/Orbitersdk/samples/ShuttleA/ShuttleA.cpp
+++ b/Orbitersdk/samples/ShuttleA/ShuttleA.cpp
@@ -74,6 +74,7 @@
     for (i = 0; i < 6; i++) {
         cargo_open[i]=0;    //not opened. not jettisoned
     cargo_arm_status = 0;    //not armed
+    logger=NULL;
     }
 }
 
@@ -1626,6 +1627,10 @@
     if (!down) return 0;       // only process keydown events
     if (Playback()) return 0;  // don't allow manual user input during a playback
 
+    char text[20];
+    sprintf(text, "key %d", key);
+    if (logger) logger->Log(text);
+
     if (KEYMOD_SHIFT (kstate)) {
     } else if (KEYMOD_CONTROL (kstate)) {
         switch (key) {
@@ -2222,6 +2227,13 @@
 return false;
 };
 
+int ShuttleA::clbkGeneric(int msgid, int prm, void *context)
+{
+    if (msgid!=0x1337) return 0;
+    if (prm!=0) return 0;
+    logger=(Logger *)context;
+}
+
 // ==============================================================
 // API callback interface
 // ==============================================================
diff --git a/Orbitersdk/samples/ShuttleA/ShuttleA.h b/Orbitersdk/samples/ShuttleA/ShuttleA.h
--- a/Orbitersdk/samples/ShuttleA/ShuttleA.h
+++ b/Orbitersdk/samples/ShuttleA/ShuttleA.h
@@ -12,6 +12,7 @@
 #define __SHUTTLEA_H
 
 #include "orbitersdk.h"
+#include "Logger.h"
 
 // ==========================================================
 // Some vessel class caps
@@ -135,8 +136,10 @@
     bool clbkLoadVC (int id);
     bool clbkVCRedrawEvent (int id, int event, SURFHANDLE surf);
     bool clbkVCMouseEvent (int id, int event, VECTOR3 &p);
+    int clbkGeneric(int msgid, int prm, void *context);
 
 private:
+    Logger *logger;
     void DefineAnimations ();
     bool ToggleGrapple (int grapple);
     double payload_mass;
In addition to the above modifications, I've only added the minimal Logger.h header from the given source code to the ShuttleA project.
 
Back
Top