• Which the release of FS2020 we see an explosition of activity on the forun and of course we are very happy to see this. But having all questions about FS2020 in one forum becomes a bit messy. So therefore we would like to ask you all to use the following guidelines when posting your questions:

    • Tag FS2020 specific questions with the MSFS2020 tag.
    • Questions about making 3D assets can be posted in the 3D asset design forum. Either post them in the subforum of the modelling tool you use or in the general forum if they are general.
    • Questions about aircraft design can be posted in the Aircraft design forum
    • Questions about airport design can be posted in the FS2020 airport design forum. Once airport development tools have been updated for FS2020 you can post tool speciifc questions in the subforums of those tools as well of course.
    • Questions about terrain design can be posted in the FS2020 terrain design forum.
    • Questions about SimConnect can be posted in the SimConnect forum.

    Any other question that is not specific to an aspect of development or tool can be posted in the General chat forum.

    By following these guidelines we make sure that the forums remain easy to read for everybody and also that the right people can find your post to answer it.

P3D v5 Trying to read LVAR with gauges.h

Messages
6
Country
germany
Hello,
I'm pretty inexperienced in C++ and SimConnect. So I tried to do a small first step and read a local var. With SPAD.neXt I found easily a local variable that I would like to read. But I didn't got that far.
I've read that it needs a gauge to access local vars. The problem: my console app won't compile.

This post: https://www.fsdeveloper.com/forum/t...the-outside-without-gauge.439157/#post-762356 has a lvar_interface example by ddawson. I tried to copy it:
dllmain.cpp
C++:
#include "pch.h"


#include "P3DDLLTry.h"

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

PPANELS Panels = NULL;

void FSAPI    DLLStart(void)
{
    if (NULL != Panels)
    {
        ImportTable.PANELSentry.fnptr = (PPANELS)Panels;
    }
}

void FSAPI    DLLStop(void)
{
}

GAUGESIMPORT    ImportTable =
{
    { 0x0000000F, (PPANELS)NULL },
    { 0x00000000, NULL }
};

GAUGESLINKAGE    Linkage =
{
    0x00000013,
    DLLStart,
    DLLStop,
    0,
    0,
    FS9LINK_VERSION,
    {
        0
    }
};
P3DDLLTry.h
C++:
#pragma once

#include "gauges.h"

double GetLVarByName(PCSTRINGZ lvname);
double GetLVarByID(ID lvID);

P3DDLLTry.cpp
C++:
#include "pch.h"
#include "P3DDLLTry.h"

double GetLVarByName(PCSTRINGZ lvname)
{
    //checks for a valid string
    if (lvname == 0) return 0.;
    if (strlen(lvname) == 0) return 0.;
    ID i = check_named_variable(lvname);
    return get_named_variable_value(i);
}

double GetLVarByID(ID lvID)
{
    return get_named_variable_value(lvID);
}
This works... I get a P3DDLLTry.dll and a P3DDLLTry.lib

My console app simply does:
C++:
#include <iostream>
#include <P3DDLLTry.h>

int main()
{
    std::cout << "Hello World!\n" << std::endl;

    std::cout << GetLVarByName("TEST");
}
Additional changes to my console project:
1. Properties -> VC++ Directories -> Include Directories (path to source files)
2. Properties -> VC++ Directories -> Library Directories (path to lib file)
3. Properties -> Linker -> Input -> Additional Dependencies -> P3DDLLTry.lib

Everything looks good until I click on compile:
Severity Code Description Project File Line Suppression State
Error LNK2019 unresolved external symbol "double __cdecl GetLVarByName(char const *)" (?GetLVarByName@@YANPEBD@Z) referenced in function main P3DDLLConsoleTry [...]\P3DDLLTry\P3DDLLConsoleTry\P3DDLLConsoleTry.obj 1
Error LNK1120 1 unresolved externals P3DDLLConsoleTry [...]\P3DDLLTry\x64\Debug\P3DDLLConsoleTry.exe 1

I thought that it might be my incompetence in C++ so I followed a very basic c++ DLL guide and it was working directly in the same project without doing a lot of things different (I didn't include the gauges.h). So I was able to call my custom function and std::cout it...

For sure some things like "FS9LINK_VERSION" look very strange but it shouldn't be a problem to compile it (in my opinion). It was also not possible for me to find any new lvar example. In general I think its horrible to find anything about LVARs in P3D. Maybe I'm searching the wrong way but it's really frustrating at the moment.

Maybe someone can help me.

Thanks a lot
 
You didn't include the P3DDLLTry.cpp file... and that's not going to make your console app work reading LVARs. To use "check_named_variable" you have to have access to it's function address in the sim's gauge support code. You'll need the GAUGESIMPORT and GAUGESLINKAGE structs defined. I don't know if adding them to a console will allow it to connect to the sim as if it's a gauge or not.
 
You didn't include the P3DDLLTry.cpp file... and that's not going to make your console app work reading LVARs. To use "check_named_variable" you have to have access to it's function address in the sim's gauge support code. You'll need the GAUGESIMPORT and GAUGESLINKAGE structs defined. I don't know if adding them to a console will allow it to connect to the sim as if it's a gauge or not.
Got it compiled. With your tip I changed #include <P3DDLLTry.h> to "P3DDLLTry.h" after adding the dllmain.cpp, P3DDLLTry.cpp & P3DDLLTry.h to the project via Add -> Existing Item. But I'm now pretty sure that the DLL isn't used because I can simply remove my P3DDLLTry.lib entry without any error (seems to simply compile the files now). I had not to do that with my test DLL application:
1686231502006.png

(I know, nice naming) There is simply the DLLTry.h included (external dependencies). The rest is done by the DLLTry.dll (as I understand)

Also I'm getting an exception (which is logical):
Exception thrown: read access violation.
ImportTable.PANELSentry.fnptr was nullptr.

What do you mean by GAUGESIMPORT needs to be defiened? So, I understand it but it should be set by sim when DLLStart function is executed (dllmain.cpp). But for that I would need to use the DLL.

Maybe I'm understanding something wrong but I understand it like this:
1686231040921.png

Or is it wrong?

I'm also still not understanding why the console app is not compiling without the cpp files and why it can't use my DLL.

Thank you for your help
 
Ok... if you're trying to access the P3DDLLTry.dll from within your console app... well, you didn't do anything I would do to accomplish that.

1. P3DDLLTry.dll absolutely must be physically loaded (linked). It's a DLL (Dynamically Linked LIbrary), not a library.
2. You have to declare a typedef for the function you're trying to use GetLVarByName in your console app's code.
3. You must request the address for said function AFTER you load the DLL.
4. Only once you've physically loaded the DLL itself and retrieved the function's address in memory can you use it.
5 . The DLL will only have access to gauge panel functions of the sim (get_named_variable_value) IF it is loaded by the sim itself.
 
Ok... if you're trying to access the P3DDLLTry.dll from within your console app... well, you didn't do anything I would do to accomplish that.

1. P3DDLLTry.dll absolutely must be physically loaded (linked). It's a DLL (Dynamically Linked LIbrary), not a library.
2. You have to declare a typedef for the function you're trying to use GetLVarByName in your console app's code.
3. You must request the address for said function AFTER you load the DLL.
4. Only once you've physically loaded the DLL itself and retrieved the function's address in memory can you use it.
5 . The DLL will only have access to gauge panel functions of the sim (get_named_variable_value) IF it is loaded by the sim itself.
Thanks for your answer.

I'm not sure if I understand you correctly but:
1. I think thats what I tried in my first post. And as I said I already loaded a DLL in C++ in a small test application and I don't understand why it is compiling my test app and not the LVAR one.
2. Could you give me an example? I don't know a lot about C++, something like this?:
C++:
typedef double GetLVARByName();
3. Ouf what? Shouldn't be the DLL already loaded before the first line if I linked it correctly?
4. Okay
5. Yes, the DLL would be loaded via a DLL.xml link like this:
XML:
<Launch.Addon>
    <Name>lvar interface</Name>
    <Disabled>False</Disabled>
    <ManualLoad>FALSE</ManualLoad>
    <Path>.\modules\P3DDLLTry.dll</Path>
</Launch.Addon>

Thanks for your help and sorry, I'm inexperienced in C and SimConnect. I can't find any guide to do those things which makes it really hard to get into this stuff
 
I'm not trying to be an *(censored)*... but I don't teach programming. My personal patience to teach is slightly above that of Voldemort's patience with muggles. Very slightly. You need to find information on how to write C/C++... understand how the language works, etc. Did you utilize a call to LoadLibrary to load the DLL or did you try to statically link the DLL into your console app? Do you understand the differences? I do not think using a static link to the DLL will work correctly at all as when the dynamic load happens with Prepar3D it goes into the DLL's memory footprint and modifies some data (populates the addresses for the panel functions).

Static means just that... it's static/unchanging.
 
I'm not trying to be an *(censored)*... but I don't teach programming. My personal patience to teach is slightly above that of Voldemort's patience with muggles. Very slightly. You need to find information on how to write C/C++... understand how the language works, etc. Did you utilize a call to LoadLibrary to load the DLL or did you try to statically link the DLL into your console app? Do you understand the differences? I do not think using a static link to the DLL will work correctly at all as when the dynamic load happens with Prepar3D it goes into the DLL's memory footprint and modifies some data (populates the addresses for the panel functions).

Static means just that... it's static/unchanging.
Ahhhh, you mean using LoadLibrary... Didn't know about that because dynamic linking can also be done by using lib files but that makes sense now :) .

I've read a bit about it and came to this solution:
P3DDLLTry.cpp
C++:
#include "pch.h"
#include "P3DDLLTry.h"
extern "C" {
    _declspec (dllexport) double GetLVarByName(PCSTRINGZ lvname)
    {
        //checks for a valid string
        if (lvname == 0) return 0.;
        if (strlen(lvname) == 0) return 0.;
        ID i = check_named_variable(lvname);
        return get_named_variable_value(i);
    }

    _declspec (dllexport) double GetLVarByID(ID lvID)
    {
        return get_named_variable_value(lvID);
    }
}

P3DDLLTry.h
C++:
#include "gauges.h"

class P3DDLLTry {
public:
    _declspec (dllexport) double GetLVarByName(PCSTRINGZ lvname);
public:
    _declspec (dllexport) double GetLVarByID(ID lvID);
};

P3DDLLConsoleTry.cpp:
C++:
typedef double(__stdcall* f_GetLVarByName)(PCSTRINGZ lvname);

int main()
{
    std::cout << "Hello World!\n" << std::endl;

    HINSTANCE hGetProcIDDLL = LoadLibrary("C:\\Program Files\\Lockheed Martin\\Prepar3D v5\\modules\\P3DDLLTry.dll"); // DLL is loaded by P3D via DLL.XML entry
    if (!hGetProcIDDLL) {
        std::cout << "COULD NOT LOAD DLL";
        return EXIT_FAILURE;
    }

    f_GetLVarByName GetLVarByName = (f_GetLVarByName)GetProcAddress(hGetProcIDDLL, "GetLVarByName");
    if (!GetLVarByName) {
        std::cout << "COULD NOT LOCATE FUNCTION";
        return EXIT_FAILURE;
    }

    std::cout << GetLVarByName("APU_EGT");
   
}


Aaaaaaand it's not working :( . But it's a big progress because it loads the DLL and finds the function pointer :) . The problem should be now more simConnect specific:
ImportTable.PANELSentry.fnptr is null. And thats now the part I don't understand. So it seems that the DLLStart function is not called by P3D or "Panels" is null. (tested while P3D was running)
dllmain.cpp:
C++:
void FSAPI    DLLStart(void)
{
    if (NULL != Panels)
    {
        ImportTable.PANELSentry.fnptr = (PPANELS)Panels;
    }
}

void FSAPI    DLLStop(void)
{
}
(tried also to export these functions and GAUGESLINKAGE & GAUGESIMPORT & Panels but it didn't work)

As I said it would be simple if there would be a small example or explanation but I can't find anything so I'm now again in a situation where I don't know what to do and where the problem could be... Also I don't understand when "Panels" would be set (default NULL) (can P3D access this var?) but this example DLL is the only one I have.

But again thanks for you help I got the function executed now... A step in the right direction. Maybe if you know something about that next problem it would be nice to hear from you
 
Yeah... I'll be honest... your approach makes zero sense to me. I don't think it's possible to do what you're trying to do... and I'm pretty certain changing the original DLL code from Mr. Dawson isn't necessary.

The GAUGESLINKAGE and GAUGESIMPORT are only touched by the sim when they are defined within a DLL.

A side note... of zero value... but... the line "if (NULL != Panels)" doesn't describe what you're actually doing in that line. When reading it, it implies you're checking to see if NULL is not equal to Panels... but technically, you're testing to see if Panels is not equal to NULL. Readability wise... those two should be reversed... so it should read "if (Panels != NULL)" because you're actually testing the value of Panels... not NULL. Hope that makes sense.
 
Yeah... I'll be honest... your approach makes zero sense to me. I don't think it's possible to do what you're trying to do... and I'm pretty certain changing the original DLL code from Mr. Dawson isn't necessary.

The GAUGESLINKAGE and GAUGESIMPORT are only touched by the sim when they are defined within a DLL.

A side note... of zero value... but... the line "if (NULL != Panels)" doesn't describe what you're actually doing in that line. When reading it, it implies you're checking to see if NULL is not equal to Panels... but technically, you're testing to see if Panels is not equal to NULL. Readability wise... those two should be reversed... so it should read "if (Panels != NULL)" because you're actually testing the value of Panels... not NULL. Hope that makes sense.
Do you mean my general approach doesn't make sense or only the export of functions. Without the export I'm not able to access it via GetProcAddress. But if my general approach is not the correct way then please tell it to me.

I combined now everything (in more or less one file, not the best solution but the easiest for testing).
I understood now that the lvar_interface example is handling exports via a .def file
lvar_interface example:
Code:
LIBRARY    "lvar_interface"
EXPORTS
DLLStart
DLLStop
GetLVarByName
GetLVarByID
SetLVarByName
SetLVarByID
GetLVarID
CreateLVar
Panels DATA

So I did that too for my DLL and it worked directly:
Code:
LIBRARY "P3DDLLTry"
EXPORTS
DLLStart
DLLStop
GetLVarByName
GetLVarByID
Panels DATA
TESTINT

Tried now the following and that might be interesting:
C++:
// dllmain.cpp : Defines the entry point for the DLL application.
#include "pch.h"

#include "gauges.h"

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

PPANELS* Panels = NULL;
int TESTINT = 1;

double GetLVarByName(PCSTRINGZ lvname)
{
    //checks for a valid string
    if (lvname == 0) return 0.;
    if (strlen(lvname) == 0) return 0.;
    ID i = check_named_variable(lvname);
    return get_named_variable_value(i);
}

double GetLVarByID(ID lvID)
{
    return get_named_variable_value(lvID);
}


void FSAPI    DLLStart(void)
{
    TESTINT = 2;
    ImportTable.PANELSentry.fnptr = (PPANELS)Panels;
}

void FSAPI    DLLStop(void)
{
}

GAUGESIMPORT    ImportTable =
{
    { 0x0000000F, (PPANELS)NULL },
    { 0x00000000, NULL }
};

GAUGESLINKAGE    Linkage =
{
    0x00000013,
    DLLStart,
    DLLStop,
    0,
    0,
    FS9LINK_VERSION,
    {
        0
    }
};

If my console app then executes the GetLVarByName function, TESTINT is still 1. So DLLStart doesn't look like it was executed. If I force a DLLStart (debug), TESTINT is set to 2 but ImportTable.fnptr stays null because Panels is null (logical)... So P3D is not "starting" correctly the DLL (I think so).
Added also extra parameters to DLL.XML (found them in this example https://www.prepar3d.com/SDKv4/sdk/...verview.html#Running the Cabin Comfort Sample):
XML:
<Launch.Addon>
    <Name>P3DDLLTry</Name>
    <Disabled>False</Disabled>
    <ManualLoad>FALSE</ManualLoad>
    <Path>modules\P3DDLLTry.dll</Path>
    <DllStartName>DLLStart</DllStartName>
    <DllStopName>DLLStop</DllStopName>
</Launch.Addon>
But still no success. Very strange. Any idea?
 
Back
Top