Documentation for the tagged format is referenced but not given

Version: 1.3.1 (SDK version)

Frequency: Consistently

Severity: High

Marketplace package name: N/A

Context: Core SDK Documentation

Similar MSFS 2020 issue: No issue, but same for 2020

Bug description: The Documentation for SimConnect_RequestDataOnSimObject(), in the section on the Flags parameter, refers to the SIMCONNECT_RECV_SIMOBJECT_DATA struct documentation for “more details” on the tagged format. However, the section on that structure does not contain any documentation on the subject.

I know this is a very old issue, because I checked the FSX SDK documentation and this section is more or less a verbatim copy of that, just as the “TaggedData” Sample. My assumption is currently that, rather than int, the Id field in that example code should have been DWORD, as that is what SimConnect_AddToDataDefinition uses. They will normally work out to the same, but for correctness sake I’d prefer the type that makes its size explicit. (so yes, int32_t should even be better, but that would be a major rework of SimConnect.h) What is uncertain in both cases (tagged as well as untagged) is if SIMCONNECT_DATATYPE_STRINGV values (just zero-terminated UTF-8) are padded to the next 32-bit boundary. Haven’t found a STRINGV using sample in the “Samples”.

Note to helpful commenters: Please refrain from “if it works, there’s your answer.” Being correct and well-documented helps prevent bugs, memory corruption, and abuse of undefined behavior.

Note to Asobo folks: I’m willing to provide text/diffs/PRs, but I need to know the correct answer for that.

Repro steps: Visit the documentation.

Attachments: N/A

Private attachments: N/A

1 Like

Note from self: In CsSimConnect I actually re-align at a 4-byte boundary after STRINGV values, which “DWORD dwData” kind of hints at, so probably that is the alignment?

Hello @BenkeiBuddy

What variable are you trying to get using SIMCONNECT_DATATYPE_STRINGV type?
AFAIK, only fixed size strings can be requested this way.

We’ll add some details in the documentation indeed.

Regards,
Sylvain

What variable are you trying to get using SIMCONNECT_DATATYPE_STRINGV type? AFAIK, only fixed size strings can be requested this way.

Interesting, this is the first time I hear about that restriction. I have to admit not having used STRINGV that often, but the SIMCONNECT_DATATYPE page just states “Specifies a variable length string.”

Ok, now I’m really getting out my magnifying glass:

  • In the remarks at the bottom of SIMCONNECT_DATATYPE it states that “The three structures in the list of datatypes…” while there are 5.

  • For SimConnect_AddToDataDefinition(), parameter “UnitsName”, it states:

    For strings and structures enter “NULL” for this parameter.

    MSFS 2024 will return an exception if you do that; an empty string should be used instead. Wouldn’t nullptr be more appropriate?

  • for structure SIMCONNECT_RECV_SIMOBJECT_DATA , field dwDefineCount, it states:

    The number of 8-byte elements in the dwData array.

    This would let me conclude that the “data” part of the message is (dwDefineCount * 8) bytes long. In fact the length can only be determined from the cbData parameter and dwDefineCount is (probably) the number of datums in the message. (Makes most sense given the name)

Darn, need to redo some stuff before I can finally start recording part 3 of the SimConnect tutorial.

Cheers,
Bert

Ok, a small test with initially 4, then 6 SimVars, tells me:

  1. STRINGV is accepted and “just works”. As far as I can see it indeed aligns at a 4-byte boundary for strings not coming out at multiples of 4 bytes, although I did receive more bytes at the end of the message than needed. Maybe the message is allocated using the known max length of the strings. Getting “too much data” is not an issue if you stop when you have all your SimVars extracted.
  2. dwDefineCount” went up from 4 to 6 when I added two extra SimVars, so it looks like the name is correct and not the documentation for it.

@FlyingRaccoon can you verify?

I am surprised by that.
When using AddToDataDefinition on simvar Title for example, I’ll get a SIMCONNECT_EXCEPTION_DEFINITION_ERROR exception when using SIMCONNECT_DATATYPE_STRINGV as opposed to SIMCONNECT_DATATYPE_STRING128.
What SIMCONNECT_PERIOD are you using when doing the SimConnect_RequestDataOnSimObject call?

When using SIMCONNECT_DATA_REQUEST_FLAG_TAGGED, only the variables that have changed will be sent so dwDefineCount reflects that.

Regards,
Sylvain

This is my test:

// My First SimConnect App


#pragma warning(push, 3)
#include <Windows.h>
#include <SimConnect.h>
#pragma warning(pop)

#include <iostream>


constexpr DWORD DataReq{ 1 };
constexpr DWORD AircraftData{ 1 };

enum class Datum : DWORD {
	NoId = 0,
	Title,
	IsUser,
	AtcId,
	AtcModel,
	AircraftAGL,
	Altitude
};

static HANDLE hSimConnect{ nullptr };
static bool connected{ false };


static void handleException(const SIMCONNECT_RECV_EXCEPTION& msg) {
	const SIMCONNECT_EXCEPTION exc{ static_cast<SIMCONNECT_EXCEPTION>(msg.dwException) };
	printf("Received an exception type %d:\n", (int)exc);
	if (msg.dwSendID != SIMCONNECT_RECV_EXCEPTION::UNKNOWN_SENDID) {
		printf("- Related to a message with SendID %d.\n", (int)msg.dwSendID);
	}
	if (msg.dwIndex != SIMCONNECT_RECV_EXCEPTION::UNKNOWN_INDEX) {
		printf("- Regarding parameter %d.\n", (int)msg.dwIndex);
	}
}

static void handle_messages(HANDLE hEvent)
{
	while (connected && (::WaitForSingleObject(hEvent, INFINITE) == WAIT_OBJECT_0)) {
		SIMCONNECT_RECV* pData{ nullptr };
		DWORD cbData{ 0 };
		HRESULT hr{ S_OK };

		while (SUCCEEDED(hr = SimConnect_GetNextDispatch(hSimConnect, &pData, &cbData))) {
			switch (pData->dwID) {
			case SIMCONNECT_RECV_ID_EXCEPTION:
				handleException(*((SIMCONNECT_RECV_EXCEPTION*)pData));
				break;

			case SIMCONNECT_RECV_ID_OPEN:
			{
				SIMCONNECT_RECV_OPEN* pOpen{ (SIMCONNECT_RECV_OPEN*)pData };


				printf("Connected to '%s' version %d.%d (build %d.%d)\n",
					pOpen->szApplicationName,
					pOpen->dwApplicationVersionMajor,
					pOpen->dwApplicationVersionMinor,
					pOpen->dwApplicationBuildMajor,
					pOpen->dwApplicationBuildMinor);
				printf("  using SimConnect version %d.%d (build %d.%d)\n",
					pOpen->dwSimConnectVersionMajor,
					pOpen->dwSimConnectVersionMinor,
					pOpen->dwSimConnectBuildMajor,
					pOpen->dwSimConnectBuildMinor);
			}
				break;

			case SIMCONNECT_RECV_ID_QUIT:
			{
				printf("Simulator is shutting down.\n");
				connected = false;
			}
				break;

			case SIMCONNECT_RECV_ID_SIMOBJECT_DATA:
			{
				const SIMCONNECT_RECV_SIMOBJECT_DATA * msg{(const SIMCONNECT_RECV_SIMOBJECT_DATA*)pData};

				unsigned dataSize{ cbData - (4 * 7) };
				printf("Received SimObject data for request %d, object %d, defineId %d, entry %d out of %d, %d times 8 bytes of data, remaining message size %d bytes.\n",
					   msg->dwRequestID, msg->dwObjectID, msg->dwDefineID, msg->dwentrynumber, msg->dwoutof, msg->dwDefineCount, dataSize);
				if ((msg->dwFlags & SIMCONNECT_DATA_REQUEST_FLAG_CHANGED) != 0) {
					printf("  - Data is sent due to a change.\n");
				}
				if ((msg->dwFlags & SIMCONNECT_DATA_REQUEST_FLAG_TAGGED) != 0) {
					printf("  - Data is in the TAGGED format.\n");
				}
				printf("Raw data:\n\n");
				unsigned count{ 0 };
				uint8_t* data = (uint8_t*)(&(msg->dwData));
				while (count < dataSize) {
					if ((count % 16) == 0) {
						printf("0x%04x ", count);
					}
					printf(" 0x%02x", data[count]);
					count += 1;
					if ((count % 16) == 0) {
						printf("  ");
						for (unsigned p = count - 16; p < count; p++) {
							printf("%c", ((data[p] < 0x20) || (data[p] > 0x7f)) ? '.' : ((char)(data[p])));
						}
						printf("\n");
					}
				}
				if ((count % 16) != 0) {
					while ((count % 16) != 0) {
						printf("     ");
						count++;
					}
					printf(" ");
					for (unsigned p = count - 16; p < dataSize; p++) {
						printf("%c", ((data[p] < 0x20) || (data[p] > 0x7f)) ? '.' : ((char)(data[p])));
					}
					printf("\n\n");
				}
				else {
					printf("\n");
				}

				if (msg->dwRequestID != DataReq) {
					printf("Ignoring data, not our request.\n\n");
				}
				else if (msg->dwDefineID != AircraftData) {
					printf("Ignoring data, not AircraftData.\n\n");
				}
				else if ((msg->dwFlags & SIMCONNECT_DATA_REQUEST_FLAG_TAGGED) != 0) {
					const uint8_t* ptr{ reinterpret_cast<const uint8_t*>(&(msg->dwData)) };
					size_t i = 0;
					while (i < dataSize) {
						Datum id = static_cast<Datum>(*reinterpret_cast<const DWORD*>(&(ptr[i])));
						//if (id == Datum::NoId) {
						//	break;
						//}
						i += sizeof(DWORD);

						switch (id) {
						case Datum::Title:
						{
							const char* s{ reinterpret_cast<const char*>(&(ptr[i])) };
							printf("Aircraft title is '%s'.\n", s);
							i += strlen(s) + 1;
							if ((i % sizeof(DWORD)) != 0) {
								i += (4 - (i % sizeof(DWORD)));
							}
						}
							break;

						case Datum::IsUser:
							printf("This %s the user's aircraft.\n", (ptr[i] ? "IS" : "ISN'T"));
							i += sizeof(DWORD);
							break;

						case Datum::AtcId:
						{
							const char* s{ reinterpret_cast<const char*>(&(ptr[i])) };
							printf("Aircraft ATC Id is '%s'.\n", s);
							i += strlen(s) + 1;
							if ((i % sizeof(DWORD)) != 0) {
								i += (4 - (i % sizeof(DWORD)));
							}
						}
							break;

						case Datum::AtcModel:
						{
							const char* s{ reinterpret_cast<const char*>(&(ptr[i])) };
							printf("Aircraft ATC Model is '%s'.\n", s);
							i += strlen(s) + 1;
							if ((i % sizeof(DWORD)) != 0) {
								i += (4 - (i % sizeof(DWORD)));
							}
						}
						break;

						case Datum::AircraftAGL:
						{
							int32_t aAGL = *reinterpret_cast<const uint32_t*>(&(ptr[i]));
							printf("Aircraft is %d feet above ground level.\n", aAGL);
							i += sizeof(int32_t);
						}
							break;

						case Datum::Altitude:
						{
							int32_t alt = *reinterpret_cast<const uint32_t*>(&(ptr[i]));
							if (alt == 0) {
								printf("Aircraft is at sea level.\n");
							}
							else if (alt > 0) {
								printf("Aircraft is %d feet above sea level.\n", alt);
							}
							else {
								printf("Aircraft is %d feet below sea level.\n", -alt);
							}
							i += sizeof(int32_t);
						}
						break;
						}
					}
					if (i < dataSize) {
						printf("Skipping %d unused byte(s).\n", int(dataSize - i));
					}
				}
				else {
					printf("Data and stuff...\n\n");
				}
			}
				break;

			default:
				printf("Ignoring message of type %d (length %d bytes)\n", pData->dwID, pData->dwSize);
				break;
			}
		}
		if (connected) {
			Sleep(100);
		}
	}
}


static int testConnect()
{
	HANDLE hEventHandle{ ::CreateEvent(NULL, FALSE, FALSE, NULL) };
	if (hEventHandle == NULL) {
		printf("Failed to create a Windows Event!\n");
		return 1;
	}

	HRESULT hr = SimConnect_Open(&hSimConnect, "My First SimConnect App", nullptr, 0, hEventHandle, 0);

	if (SUCCEEDED(hr)) {
		std::cout << "Successfully connected to MSFS.\n";

		SimConnect_AddToDataDefinition(hSimConnect, AircraftData, "TITLE", "", SIMCONNECT_DATATYPE_STRINGV, 0, static_cast<DWORD>(Datum::Title));
		SimConnect_AddToDataDefinition(hSimConnect, AircraftData, "is user sim", "Bool", SIMCONNECT_DATATYPE_INT32, 0, static_cast<DWORD>(Datum::IsUser));
		SimConnect_AddToDataDefinition(hSimConnect, AircraftData, "atc id", "", SIMCONNECT_DATATYPE_STRINGV, 0, static_cast<DWORD>(Datum::AtcId));
		SimConnect_AddToDataDefinition(hSimConnect, AircraftData, "atc model", "", SIMCONNECT_DATATYPE_STRINGV, 0, static_cast<DWORD>(Datum::AtcModel));
		SimConnect_AddToDataDefinition(hSimConnect, AircraftData, "aircraft agl", "feet", SIMCONNECT_DATATYPE_INT32, 0, static_cast<DWORD>(Datum::AircraftAGL));
		SimConnect_AddToDataDefinition(hSimConnect, AircraftData, "plane altitude", "feet", SIMCONNECT_DATATYPE_INT32, 0, static_cast<DWORD>(Datum::Altitude));

		SimConnect_RequestDataOnSimObject(hSimConnect, DataReq, AircraftData, SIMCONNECT_OBJECT_ID_USER_AIRCRAFT, SIMCONNECT_PERIOD_ONCE, SIMCONNECT_DATA_REQUEST_FLAG_TAGGED);

		connected = true;
		handle_messages(hEventHandle);

		hr = SimConnect_Close(hSimConnect);

		std::cout << "Disconnected from MSFS.\n";
	}
	else {
		std::cerr << "Failed to connect to MSFS!\n";
	}
	return SUCCEEDED(hr);
}


int main([[maybe_unused]] int argc, [[maybe_unused]] const char* argv[])
{
	std::cout << "Welcome to my first SimConnect app.\n";

	return testConnect();
}

Hello @BenkeiBuddy

It turns out the request on a string with type SIMCONNECT_DATATYPE_STRINGV only works when period is SIMCONNECT_PERIOD_ONCE.
The same request with periods SIMCONNECT_PERIOD_VISUAL_FRAME/SIM_FRAME/SECOND will trigger a SIMCONNECT_EXCEPTION_DEFINITION_ERROR.
I will have this reviewed as well.

Edit: this is in fact documented, I just never noticed
SimConnect_RequestDataOnSimObject

Regards,
Sylvain

1 Like