Dev Update: Choosing and Linking DLLs

On the MSFS 2020 forum site I noticed an interesting discussion in the SDK/SimConnect area, about which SimConnect DLL to use for cross-simulator development. Now I was kind of assuming I would be able to choose one as well but, just like these guys, had to conclude there are issues when you try to use the Prepar3D SimConnect library for MSFS or vice versa. So the discussion also went on about trying to link your application with both, which is not easy, especially if you want to use a managed DLL. (This discussion was about the managed SimConnect API) Microsoft puts everything in the “Microsoft.FlightSimulator” namespace, while Lockheed Martin uses “LockheedMartin.Prepar3D“. If you want to support both, you cannot even use the fact that they have classes and enums with the same layout, and have to resort the that old rule that states you can solve any problem with an extra level of indirection.

For CsSimConnect I am lucky that I decided to skip the Managed SimConnect DLL, because the static libraries don’t use namespaces, as C doesn’t support those. This also makes the CsSimConnectInterOp DLL unmanaged, which means you can (have to) defer linking it to runtime. For example, as someone already noticed, if the InterOp DLL is not in the current working directory, the first attempt to call SimConnect.Connect(), which again calls CsConnect() in the InterOp DLL, will fail. If I had used a managed DLL, it would have failed to start the program, because the linkage is forced at startup.

What I have since tested and implemented is the following: the InterOp DLL is linked against a specific static library, and I used the Visual Studio Configuration Manager to create configurations for three simulators and two “modes”: The simulators are Prepar3D v4 and v5, and MSFS 2020, the modes are “Debug” and “Release”. Not only does the build use different environment variables to find the SimConnect header file and static libraries (“P3D45_SDK“, “P3D51_SDK“, and “MSFS_SDK“), it also uses different target directories. (“P3Dv4“, “P3Dv5“, and “MSFS“) When you first use the “SimConnect” object, most likely to do a “SimConnect.Instance.Connect()“, it will check a static property named “InterOpType“:

        private static void LoadInterOpLibrary(string path)
        {
            try
            {
                var hExe = LoadLibrary(path);
                if (hExe == IntPtr.Zero)
                {
                    log.Fatal("Unable to load '{0}'", path);
                }
            }
            catch (Exception e) {
                log.Error("Exception caught in LoadInterOpLibrary('{0}'): {1}", path, e.Message);
            }
        }

        private SimConnect()
        {
            log.Info("Loading InterOp DLL for '{0}'.", InterOpType.ToString());
            if (InterOpType == FlightSimType.Unknown)
            {
                log.Fatal("Target InterOp type not set!");
            }
            else if (InterOpType == FlightSimType.Prepar3Dv4)
            {
                LoadInterOpLibrary("P3Dv4\\CsSimConnectInterOp.dll");
            }
            else if (InterOpType == FlightSimType.Prepar3Dv5)
            {
                LoadInterOpLibrary("P3Dv5\\CsSimConnectInterOp.dll");
            }
            else if (InterOpType == FlightSimType.MSFS2020)
            {
                LoadInterOpLibrary("MSFS\\CsSimConnectInterOp.dll");
            }
            else
            {
                log.Fatal("Unknown FlightSimType '{0}'", InterOpType.ToString());
            }

            UseAutoConnect = false;
            Info = new("CsSimConnect");
            OnOpen += SetConnectedSim;
            OnConnect += InvokeConnectionStateChanged;
            OnDisconnect += ResetErrorCallbacks;
            OnDisconnect += InvokeConnectionStateChanged;
        }

The result is that the application could throw up a dialog box asking for the simulator, or set if from a stored property or registry value, and the correct DLL will be loaded. Then when the first call goes to the InterOp DLL, it will find a matching DLL to be already loaded and linked, and use that.

Related change: CLI builds

A related issue was caused by now having three different build configurations for the InterOp DLL, while the main library is still single. The changes I did (and CLI tooling I installed) messed up something in the configuration (or just made a problem explicit) that makes it impossible for me to build the InterOp DLL from the commandline with the “dotnet” command, while “msbuild” works. I’ll create three different “Solution” files to solve this: One that only contains the two C# projects, one that only contains the C++ project, and one with everything. From the commandline you then use the split solutions, while the Visual Studio IDE can do everything together.

Unrelated changes

While working on variable strings (the “SIMCONNECT_DATATYPE_STRINGV” type) I cleaned up the message copy code so it uses the total message size instead of the (possibly not matching) sum of the sizes of the definitions. Undocumented was that variable strings will be aligned on 4-byte boundaries, which is reasonable but not always necessary. Also I noticed that variable strings are not supported with the “onlyWhenChanged” flag, so be careful when you use that to save space.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s