Addendum 2: Real “execute_calculator_code” tool
I added another feature in my tool. Now you can enter whatever execute_calculator_code
string, send it to the WASM Module, and see the results in FLOAT64, SINT32 or String format. As usual, you can find the latest version on GitHub. The updated executables (release version) can also be found here.
The UI of the tool looks like this:
You enter an RPN formatted string, press the Send button, and see the results in the Float, Int and String fields. This is very powerful, because execute_calculator_code
accepts all types of variables (SimVars, LVars, Events, etc…), which means that you can now experiment and see how they work.
The implementation is as follows:
- A new command “HW.Exe.[RPN based string]” is added in the WASM Module
- A new Client Area Data “HABI_WASM.Result” is added to send back the results to the SimConnect client
If we look at the signature of execute_calculator_code, we see that it takes a string as parameter, and it returns it values as FLOAT64, SINT32 and STRING.
BOOL execute_calculator_code(
PCSTRINGZ code,
FLOAT64* fvalue,
SINT32* ivalue,
PCSTRINGZ* svalue
);
In the WASM Module, the values are combined in a structure that is sent back through the Client Data Area.
Let’s look at some code:
Changes in WASM Module
First the Client Data Area is initialized:
// file WASM_HABI.cpp
struct Result {
FLOAT64 exeF;
SINT32 exeI;
char exeS[256];
};
...
void RegisterClientDataArea()
{
...
// Create Client Data Area for result of execute_calculator_code
hr = SimConnect_MapClientDataNameToID(g_hSimConnect, CLIENT_DATA_NAME_RESULT, CLIENT_DATA_ID_RESULT);
if (hr != S_OK) {
fprintf(stderr, "%s: Error creating Client Data Area %s. %u", WASM_Name, CLIENT_DATA_NAME_RESULT, hr);
return;
}
SimConnect_CreateClientData(g_hSimConnect, CLIENT_DATA_ID_RESULT, sizeof(Result), SIMCONNECT_CREATE_CLIENT_DATA_FLAG_DEFAULT);
...
// This Data Definition will be used with the RESULT Client Area Data
hr = SimConnect_AddToClientDataDefinition(
g_hSimConnect,
DATA_DEFINITION_ID_RESULT,
0, // Offset
sizeof(Result) // Size
);
...
}
In MyDisptachProc
, the below code is added in the SIMCONNECT_RECV_ID_CLIENT_DATA case to receive the “HW.Exe” command.
// file WASM_HABI.cpp
// "HW.Exe."
if (strncmp(sCmd, CMD_Exe, strlen(CMD_Exe)) == 0)
{
ExecuteCalculatorCode(&sCmd[strlen(CMD_Reg)]);
break;
}
And in the function ExecuteCalculatorCode, the command is executed, and the results are sent back in the Client Data Area.
void ExecuteCalculatorCode(char* sExe)
{
Result exeRes;
exeRes.exeS[0] = '\0';
PCSTRINGZ ps;
execute_calculator_code(sExe, &exeRes.exeF, &exeRes.exeI, &ps);
strncat(exeRes.exeS, ps, 255);
fprintf(stderr, "%s: ExecuteCalulculatorCode: float %f, int %i, string %s", WASM_Name, exeRes.exeF, exeRes.exeI, exeRes.exeS);
HRESULT hr = SimConnect_SetClientData(
g_hSimConnect,
CLIENT_DATA_ID_RESULT,
DATA_DEFINITION_ID_RESULT,
SIMCONNECT_CLIENT_DATA_SET_FLAG_DEFAULT,
0,
sizeof(exeRes),
&exeRes
);
if (hr != S_OK)
{
fprintf(stderr, "%s: Error on Setting Client Data RESULT", WASM_Name);
}
}
Be aware that PCSTRINGZ is a const CHAR *
, which is a pointer to a string. We pass the address of this pointer to execute_calculator_code
. execute_calculator_code
creates the necessary memory depending on the length of the string result, and puts the address of that string in my variable. After that, I copy the string in the exeS
member of the exeRes
structure. Using strncat
guarantees that the max length of 256 characters (including ‘\0’) is not exceeded.
With SetClientData
, the result is put in the Client Data Area and will trigger the SimConnect Client on the receiving side.
Changes in the SimConnect Client
First the Client Data Area is initialized:
// file SimConnectHUB.cs
private void InitializeClientDataAreas()
{
Debug.WriteLine("InitializeClientDataAreas()");
try
{
...
// register Client Data (for RESULT)
_oSimConnect.MapClientDataNameToID(CLIENT_DATA_NAME_RESULT, CLIENT_DATA_ID.RESULT);
_oSimConnect.CreateClientData(CLIENT_DATA_ID.RESULT, (uint)Marshal.SizeOf<Result>(), SIMCONNECT_CREATE_CLIENT_DATA_FLAG.DEFAULT);
_oSimConnect.AddToClientDataDefinition(CLIENTDATA_DEFINITION_ID.RESULT, 0, (uint)Marshal.SizeOf<Result>(), 0, 0);
_oSimConnect.RegisterStruct<SIMCONNECT_RECV_CLIENT_DATA, Result>(CLIENTDATA_DEFINITION_ID.RESULT);
_oSimConnect.RequestClientData(
CLIENT_DATA_ID.RESULT,
CLIENTDATA_REQUEST_ID.RESULT,
CLIENTDATA_DEFINITION_ID.RESULT,
SIMCONNECT_CLIENT_DATA_PERIOD.ON_SET,
SIMCONNECT_CLIENT_DATA_REQUEST_FLAG.DEFAULT,
0, 0, 0);
}
catch (Exception ex)
{
LogResult?.Invoke(this, $"InitializeClientDataAreas Error: {ex.Message}");
}
}
In SimConnect_OnRecvClientData
, an extra branch is added to catch the CLIENTDATA_REQUEST_ID.RESULT
.
// file SimConnectHUB.cs
...
else if (data.dwRequestID == (uint)CLIENTDATA_REQUEST_ID.RESULT)
{
try
{
var exeResult = (Result)(data.dwData[0]);
Debug.WriteLine($"----> Result: float: {exeResult.exeF}, int: {exeResult.exeI}, string: {exeResult.exeS}");
ExeResult?.Invoke(this, exeResult);
}
catch (Exception ex)
{
LogResult?.Invoke(this, $"SimConnect_OnRecvClientData Error: {ex.Message}");
}
}
...
An extra EventHandler ExeResult is used to report the results back to the UI (same way as LogResult). This EventHandler is initialized in the constructor of the form.
// file FormHUB.cs
public FormHUB()
{
InitializeComponent();
_SimConnectHUB.LogResult += OnAddResult;
_SimConnectHUB.ExeResult += OnExeResult;
}
...
private void OnExeResult(object sender, SimConnectHUB.Result ExeResult)
{
textBoxExecCalcCodeFloat.Text = ExeResult.exeF.ToString("0.000");
textBoxExecCalcCodeInt.Text = ExeResult.exeI.ToString();
textBoxExecCalcCodeString.Text = ExeResult.exeS;
}
Everything is now ready to press the Send button. This is just taking the string from the TextBox, and sends it to the WASM Module using the method SendWASMCmd
, now using the “HW.Exe” command.
// file SimConnectHUB.cs
public void ExecuteCalculatorCode(string sExe)
{
SendWASMCmd($"HW.Exe.{sExe}");
}
We can now use this execute_calculator_code addition to experiment with all kinds of variables.
Get and set values of A-Vars:
Get and set values of L-Vars:
Trigger Events:
Getting string values:
(in this case, the Float and Int values are apprantly 0)
Enjoy experimenting!
Goto Addendum 3: A more responsive Connect method