1. This site uses cookies. By continuing to use this site, you are agreeing to our use of cookies. Learn More.

Discovering WPF and custom windows in FSX

Discussion in 'Tools programming' started by ziporama, 4/6/07.

  1. ziporama

    ziporama

    Joined:
    28/9/06
    Messages:
    203
    Country:
    unitedstates
    Here's an update on my discoveries after experimenting over the weekend on the subject of WPF and custom windows inside FSX. I'm at a loss as to why there's so little documentation or examples on the subject on how to do this.

    This relates to two threads in this forum, one on WPF content in FS, the other, creating custom windows in FS.

    For WPF to work inside FSX, it will need to be hosted in .NET 3.0, which is really .NET 2.0 + three new .NET libraries (one of which is the Windows Presentation Foundation - WPF).

    It is possible to load a .NET assembly within FSX as a module.dll or as a gauge .dll. The entry points are similar, and this requires a native C++ dll with the proper hooks FS expects, then, using low level COM API functions that come with .NET and are very poorly documented (they are labeled with danger, danger, stay away) to load the CLR manually and the assembly in memory.

    The C++ Interop method (the one using mixed mode and the one typically documented lets you work with both native and managed code using the /clr compiler switch) would be easy, except it crashes FSX on start when the CLR is loaded. It was a no-go for me.

    The low level hooks involve two steps, first, loading the CLR in C++ using COM, second, load the assembly you need once the COM pointer to the CLR is obtained. Locating the assembly is a challenge, because the CLR looks for assemblies in one of two places by default, the GAC and the current executing path. Neither are good places, but fortunately, you can ask the CLR to load a specific dll from path. That works like a champ.

    Next problem: Because WPF windows do no derive from a standard window (it likes to be hosted), WPF content can only exist inside an existing window handle. I've opted to use a standard .NET WindowsForm container, which itself derives from a native OS window underneath. It's quite a bit easier to do the whole thing from within .NET than try to mix/match native and non-native.

    Another requirement is that WPF only works with STA threading, not MTA. The default for .NET threads is MTA, so the first thing to do is to create a STA thread and use that thread to drive the WPF instances inside the FSX process space.

    Onto creating a custom window. There are two theoretical ways I know of doing this.

    Method 1 is the gauge API route, where FS creates a window, you can get a DC to that window and paint a bitmap generated in WPF using bitblt or GDI+. That of course requires a complete gauge. That's a dll too, and part of the init code, you can load the CLR and assembly.

    Method 2 is the module route (dll loaded in dll.xml). All the hooks in .NET are there using either low level overrides or direct API calls. The discussion thus applies to a regular window, or a .NET window.

    Using Spy++, it's fairly easy to see what FSX does under the hood to its windows. From that analysis,

    - must create the window using WS_CHILD as the only style
    - must set the new window's parent manually using SetParent() to the FS98MAIN window handle (using the handle returned by FindWindow()) - need to test if the parent should be FS98MAIN or the client area. The built-in windows FS creates are all parented to FS98MAIN.
    - tested - do NOT use the FS98CHILD classname as the class when the window is created (the other registered classname is FS98FLOAT) - causes a null pointer exception in the wndproc, use your own- set the window styles before the call to CreateWindowEx() is made, to make sure the wiring up of windows is correct. I haven't played with ReCreateHandle().
    - create the window only when the sim is running (ie, not in dialog mode)

    Noted:
    Using SetWindowLong() on an existing window to change the style of an existing window. While the style changes (according to Spy++), the paint events are not fired normally - indicating a wiring problem under the hood. What's interesting is the frame (WM_NCPAINT) messages are fired, so the OS and the application knows the window is there, but not painting correctly.

    In conclusion, I'm at the point where I have .NET 3.0 loaded in FSX, I have a windows form created, but it doesn't paint correctly. I suspect the reason for this is that I didn't add the proper styles to create the window, and I will test this next.

    I've tried the bitmap rendering, and it does work.

    Next challenge is to evaluate if all this is worth it in terms of FPS...

    Cheers,

    Etienne
    Last edited: 5/6/07
  2. ziporama

    ziporama

    Joined:
    28/9/06
    Messages:
    203
    Country:
    unitedstates
    This is an update after a week of testing various combinations. The current "best" option I ended up with is to create a WPF window in the FSX process space using HwndSource, parenting it to the FS98MAIN window, and giving it the WS_TOOLWINDOW | WS_VISIBLE style.

    Through the HwndSourceHook property of HwndSource, it is also possible to filter the message loop and do other interesting things. In this mode, the IKIs are not hooked up by HwndSource, so you must manually hook the keyboard.

    Using a WPF Page control as the RootVisual of the HwndSource, the clock from the WPF tutorial on MSDN displayed correctly animated. I'll get some screenshots of this in action. I ran out of time last night, my apologies.

    I was unable to use WS_CHILD or WS_POPUP in FSX - the window shows up for one frame (first WM_PAINT) and never does redraw. Yet it receives the right messages from FSX, so this is something inside WPF somewhere, or deeper in the rendering layer. I checked the z-order and WS_TOPMOST flags with no change in behavior, so the window was clearly to display on top.

    WS_POPUP is normally used by modeless windows added at runtime for FS9. I simply could not get that to work correctly.

    The next challenge is to make this behave properly in full screen mode. At this point, toggling full screen mode (alt-enter) yields poor results, from works great, or FSX crashes, or either the WPF window to show or the FSX window to show. Reading up on the layering capabilities of DirectX and WPF, it seems that full screen rendering of a DX window does not work with WPF under Windows XP, but is built-in Vista. The article I found has to do with how GDI works with window handles, and the need to paint a black background by the GDI before layering DX content.

    I'm not throwing the towel in yet because I did have some remnant testing code that could also cause this instability.

    There is very little information or documentation available on the precise behavior of WPF when a sibbling window renders using straight DirectX or GDI, GDI+. It's not possible to combine rendering types in the same window, but it does work in windowed mode.

    The general steps to get to my setup:

    0) make sure you have .NET 3.0 loaded if you want to use WPF.

    1) create a native C++ DLL (either a gauge or a module for FSX). Do not use the /clr switch or FSX will crash.

    2) load the CLR using the low level COM API (this is the same API used by the class loader).

    3) load your .NET assembly in the instance of the CLR.

    4) Upon startup, ensure the .NET assembly creates a STA thread before returning from the call. This thread will run all your .NET code. It must be STA (as opposed to the default MTA) because all windows rendering still uses the STA model, and WPF is no different.

    5) Somewhere in your thread, create a message loop by creating a static WPF Application object and calling its Run() method. Make sure the shutdown method is in line with how you want your dialogs to behave. I've set mine to manually shutdown when signaled. The default is when all the windows are closed, which is not what's wanted here.

    6) If you run other threads, such as SimConnect, make sure you communicate with the WPF thread using the Dispatcher.Invoke() calls. While not documented, you can use the Windows Forms MethodInvoker() delegate to cross threads.

    Cheers,

    Etienne
  3. scruffyduck

    scruffyduck Administrator Staff Member FSDevConf team Resource contributor

    Joined:
    17/9/05
    Messages:
    26,108
    Country:
    wales
    Hi Etienne

    Thanks for your work on this - it is very interesting
  4. ziporama

    ziporama

    Joined:
    28/9/06
    Messages:
    203
    Country:
    unitedstates
    Sample code to instantiate the CLR and a .NET library from unmanaged C++ as a module or a gauge (same technique).

    This code borrows heavily from CodeProject articles, credit goes to the various authors this code was "assembled" from.

    // This module is loaded by FSX in the DLL.XML section - compile as a standard win32 dll, make sure you export the entry points in a .DEF file so FSX can find them. DO NOT USE THE /CLR compiler option!!!
    // The first thing it does is load the CLR and the place_assembly_name_here assembly, thus enabling .NET from within FSX.
    // Code borrows heavily from various articles found on codeproject and other sites including msdn, plus my own "sauce"
    // Adaptation for FSX by Etienne Martin, EMCS LLC
    // distributed as sample code, feel free to re-use


    #include <windows.h>
    #include <atlbase.h>
    #include <mscoree.h>
    #include <comutil.h>

    #pragma comment(lib,"mscoree.lib") // link with CLR hosting library

    // Change to match your particular path - this is the default for 2.0/3.0
    #import "C:\\WINDOWS\\Microsoft.NET\\Framework\v2.0.50727\\Mscorlib.tlb" raw_interfaces_only

    using namespace mscorlib;

    #ifdef _MANAGED
    #pragma managed(push, off)
    #endif

    // pointer to the runtime host
    CComPtr<ICorRuntimeHost> pRuntimeHost;
    // pointer to the application domain
    CComPtr<_AppDomain> pDefAppDomain;
    // interface to IDispatch
    CComPtr<IDispatch> pDisp;

    HRESULT StartClrHost(char* szAssemblyNameWithPath, char* szClassNameWithNamespace)
    {
    try
    {
    //Retrieve a pointer to the ICorRuntimeHost interface
    HRESULT hr = CorBindToRuntimeEx(
    NULL, //Specify the version of the runtime that will be loaded.
    L"wks",//Indicate whether the server or workstation build should be loaded.
    //Control whether concurrent or non-concurrent garbage collection
    //Control whether assemblies are loaded as domain-neutral.
    STARTUP_LOADER_SAFEMODE | STARTUP_CONCURRENT_GC,
    CLSID_CorRuntimeHost,
    IID_ICorRuntimeHost,
    //Obtain an interface pointer to ICorRuntimeHost
    (void**)&pRuntimeHost
    );

    if (FAILED(hr)) return hr;

    //Start the CLR
    hr = pRuntimeHost->Start();

    CComPtr<IUnknown> pUnknown;

    //Retrieve the IUnknown default AppDomain
    hr = pRuntimeHost->GetDefaultDomain(&pUnknown);
    if (FAILED(hr)) return hr;

    hr = pUnknown->QueryInterface(&pDefAppDomain.p);
    if (FAILED(hr)) return hr;

    CComPtr<_ObjectHandle> pObjectHandle;


    _bstr_t _bstrAssemblyName(szAssemblyNameWithPath);
    _bstr_t _bstrszClassNameWithNamespace(szClassNameWithNamespace);


    // instantiate the .NET entry dll
    // use the full path of the .NET DLL first
    hr = pDefAppDomain->CreateInstanceFrom(_bstrAssemblyName,
    _bstrszClassNameWithNamespace,
    &pObjectHandle);

    if (FAILED(hr))
    {
    // full path didn't work, look in the current path (where FSX.EXE is), then the GAC if the dll is registered there
    hr = pDefAppDomain->CreateInstance(
    _bstrAssemblyName,
    _bstrszClassNameWithNamespace,
    &pObjectHandle
    );
    }

    if (FAILED(hr)) return hr;

    CComVariant VntUnwrapped;
    hr = pObjectHandle->Unwrap(&VntUnwrapped);
    if (FAILED(hr)) return hr;

    if (VntUnwrapped.vt != VT_DISPATCH && VntUnwrapped.vt != VT_UNKNOWN )
    return E_FAIL;


    pDisp = VntUnwrapped.pdispVal;


    return ERROR_SUCCESS;
    }
    catch(_com_error e)
    {
    //Exception handling.
    }
    }

    // terminate the hosting process
    void StopClrHost()
    {
    if (NULL != pRuntimeHost)
    pRuntimeHost->Stop();

    pRuntimeHost = NULL;
    pDefAppDomain = NULL;
    pDisp = NULL;

    }


    // dispatch to call managed routines in our assembly
    HRESULT CallDispatch(BSTR szMethodName, int iNoOfParams, VARIANT * pvArgs, VARIANT * pvRet)
    {
    if (NULL == pRuntimeHost)
    return ERROR_DLL_INIT_FAILED; // indicate not initialized
    try
    {


    DISPID dispid;

    DISPPARAMS dispparamsArgs = {NULL, NULL, 0, 0};
    dispparamsArgs.cArgs = iNoOfParams;
    dispparamsArgs.rgvarg = pvArgs;
    HRESULT hr;
    hr = pDisp->GetIDsOfNames (
    IID_NULL,
    &szMethodName,
    1,
    LOCALE_SYSTEM_DEFAULT,
    &dispid
    );
    if (FAILED(hr)) return hr;

    //Invoke the method on the Dispatch Interface
    hr = pDisp->Invoke (
    dispid,
    IID_NULL,
    LOCALE_SYSTEM_DEFAULT,
    DISPATCH_METHOD,
    &dispparamsArgs,
    pvRet,
    NULL,
    NULL
    );
    if (FAILED(hr)) return hr;


    return ERROR_SUCCESS;
    }
    catch(_com_error e)
    {
    //Exception handling.
    }
    }



    // called by FSX when module starts
    EXTERN_C __declspec(dllexport) int DLLStart()
    {


    HRESULT hr = StartClrHost(YOUR_ASSEMBLY_NAME,YOUR_ASSEMBLY_CLASS_NAME); // start the CLR runtime and load our assembly
    if (S_OK != hr)
    {
    _RPT1(_CRT_WARN,"Unable to load assembly %s\n", YOUR_ASSEMBLY_NAME);

    StopClrHost(); // didn't load
    }
    else
    {
    _RPT1(_CRT_WARN,"Loaded assembly %s\n", YOUR_ASSEMBLY_NAME);
    hr = CallDispatch(L"DllStart",0,NULL,NULL); // execute DllStart() on the managed side (assumes a public void DllStart() public member in YOUR_ASSEMBLY_CLASS_NAME)
    if (S_OK != hr)
    {
    _RPT0(_CRT_WARN,"Unable to start assembly\n");
    }
    else
    {
    _RPT0(_CRT_WARN,"Assembly started\n");
    }
    SINT32 answer = 0;
    }

    return 0;
    }

    // called by SimConnect when module terminates
    EXTERN_C __declspec(dllexport) int DLLStop()
    {
    HRESULT hr = CallDispatch(L"DllStop",0,NULL,NULL); // execute DllStop() on the managed side (assumes a public void DllStop() public member in YOUR_ASSEMBLY_CLASS_NAME)
    StopClrHost(); // close the CLR runtime

    //SimConnectCleanup();
    return 0;
    }




    /**
    * Entry point of the DLL. WHATEVER YOU DO, THIS IS NOT WHERE TO LOAD THE CLR ... Can't load the CLR inside DllMain()
    * See article on: http://msdn2.microsoft.com/en-us/library/ms173266(VS.80).aspx
    */
    EXTERN_C BOOL WINAPI DllMain(HINSTANCE hInstDLL, DWORD fdwReason, LPVOID lpvReserved)
    {
    //switch (fdwReason)
    //{
    // case DLL_PROCESS_ATTACH:


    // break;
    //
    // case DLL_PROCESS_DETACH:
    // break;

    // }

    return TRUE;
    }


    #ifdef _MANAGED
    #pragma managed(pop)
    #endif
    Last edited: 12/6/07
  5. ziporama

    ziporama

    Joined:
    28/9/06
    Messages:
    203
    Country:
    unitedstates
    What to do on the .NET side ...

    The native code illustrated above will load any assembly it can find. Finding assemblies can be a nasty problem. My code will first look for the specific full path (include the drive letter). If your .NET dll cannot be found, the code will look for it in the host process path (the process running your native DLL, in this case, the path where FSX.EXE is located). The GAC is also checked but it may not be a good idea to place your .NET dll there, especially for debugging purposes.

    Your assembly class needs to minimally include two entry points - matching the FSX DllStart() and DllStop(). I'll give C# examples here since that's what I code in. VB.NET should work exactly the same.

    The declarations should be

    public sealed class MyFSXModule
    {
    public void DllStart()
    {
    // start a STA thread in here
    }

    public void DllStop()
    {
    }
    }

    The entry points are called through the native DLL when FSX loads your module, and tells your module to call it quits.

    My recommended approach is to create a STA thread in DllStart() using a static thread in the class, and abort the thread in DllStop() which will throw a ThreadAbortException in your main thead proc. From there, you can run your cleanup code and nicely exit.

    The choice of STA thread doesn't matter unless you intend to use WPF.

    About SimConnect: you can use the managed version of SimConnect inside your thread. No problem, works like a champ.

    About gauges: the same principle applies to gauge code. The entry points in the native C++ DLL are identical, but you must use a modified version of the gauge.h file to make sure that your code gets initialized. The macros in gauges.h wrap the dll entry points so they must be modified to call your clr start and stop points.

    Hope this helps,

    Etienne
  6. FlapsOut

    FlapsOut

    Joined:
    29/5/07
    Messages:
    75
    Country:
    us-texas
    C++ vs. C#

    I think I'll just echo ScruffyDuck when he said, "Thanks for your work on this - it is very interesting"...

    Took me back to my ancient C++ programming days. :eek:

    But, for the newer guys that are wondering whether to go C# or C++? I think Etienne just put the last nail in Microsoft C++'s coffin! (For me at least) :p

    Learn C# and if you must deal with C++, learn about COM Wrappers.

    COM Wrappers are like the wrappers on a mummy...you don't wanna see the corruption underneath. ;)

    Flaps
  7. MillKa

    MillKa

    Joined:
    29/10/06
    Messages:
    16
    Country:
    germany
    Hi Etienne,

    after messing around with your code above, i finally made some progress after many dead ends ..

    My solution consists of three parts:

    1) MyManagedApp.exe, my managed c# app.
    2) NativeModule.dll, a native c++ glue dll, hosting the CLR NET runtime
    3) FSXWannabe.exe, an altrenate caller of NativeModule.dll

    FSXWannabe.exe is a simple native C++ app. It uses native Win32 API calls to register a windowclass named "FS98MAIN" and then creates an app main window. Then it loads NativeModule.Dll (a C++ dll) and calls "DLLStart ()" in that dll. Then FSXWannabe.exe runs its message loop. After leaving the message loop, it calls "DLLStop ()" in NativeModule.dll. Then FSXWannabe.exe exits.

    As you might have guessed by now, the purpose of FSXWannabe.exe is to be an alternate caller of NativeModule.dll, so that i dont have to use FSX all the time.

    Note, that i did NOT unload the NativeModule.dll. If i do, weird things happen in the c++ runtime library exit code. It looks like the exit code notices that some NET runtime dlls have been loaded in the FSXWannabe.exe process, so it assumes that FSXWannabe's startup code loaded them (as if FSXWannabe.exe were a mixed mode (unmanaged/managed) c++ exe. Instead it is a pure unmanaged exe. So the executed exit code doesnt match the executed startup code. If i omit the dll unload, FSXWannabe (and FSX too) exit clean.

    NativeModule.dll is very similar to your code. In DLLStart i start the CLR NET runtime and then i call NetDLLStart () in MyManagedApp.exe. In DLLStop i call NetDLLStop in MyManagedApp.exe and then i stop the CLR NET runtime.

    MyManagedApp.exe is a standard C# app. I added two functions, NetDLLStart and NetDLLStop. If NativeModule.dll uses the Net 1.x CLR hosting API (like your code does), they look like this:

    class MyApp { // NOT static
    public void NetDLLStart () { .. } // NOT static
    public void NetDLLStart () { .. } // NOT static
    }

    If NativeModule.dll uses the NET 2.0 CLR hosting API instead, they look like this:

    static class Program {
    static public int NetDLLStart (string s) { .. }
    static public int NetDLLStop (string s) { .. }
    }

    With the v2 hosting API, CorBindToRuntimeEx gives you an ICLRuntimeHost interface pointer (v1 gave an ICorRuntimeHost pointer). Via the interface pointer you can call NetDLLStart/Stop using ExecuteInDefaultAppDomain (member of the v2 interface pointer).

    It doesnt seem to be relevant, if v1 or v2 of the CLR hosting API is used.

    In NetDLLStart, i create my managed main form:

    [DllImport ("user32")] public static extern IntPtr FindWindow (string classname, string title);
    static NativeWindow nw = null;
    static MyMainForm mf = null;
    IntPtr fsxw = FindWindow ("FS98MAIN", null);
    if (fsxw != null) { // found FSX main window
    nw = new NativeWindow (); // create Windows.Forms wrapper for fsxw
    nw.AssignHandle (fsxw); // wrap fsxw
    mf = new MyMainForm (); // create my main form
    if (fsxw != null) mf.Show (nw); // use FSX main window as my forms owner
    }

    Using FSX's main window as owner of my form seems to be the important trick, cause that keeps it from vanishing in FSX full screen mode.

    Dont start your own managed main thread. My managed app does not have its own message loop, but instead uses FSX's message loop. I tried to create my own managed main UI thread, but then it doesnt work in FSX full screen mode. I would prefer to have my own UI thread, but it looks like i cant have it, because Windows allows only one UI thread per process.

    I dont know if WPF would work in the managed form, but GDI+ and DirectX do.

    Why did i use the NET 2.0 CLR hosting API ? Because that API allows to take over the low level memory management of the CLR (besides lots of other low level stuff). See IHostMemoryManager in the MSDN library. I implemented my own HostMemoryManager so that MyManagedApp doesnt steal too much memory from FSX. My memory manager forwards all low level memory calls to their corresponding Win32 calls and keeps track of how much memory the CLR is using. If CLR memory usage reaches a certain threshold (e.g. 512 MB), i fake OutOfMemory to the CLR, so that FSX still has 1.5 GB.

    Why dont i simply post my code ? Because there is still some really weird bug. I think it is in my IHostMemoryManager implementation but im not sure. When i do not use my memory manager, everything seems to work fine (except that i would steal too much memory from FSX).

    Maybe i did something wrong with the COM reference stuff or whatever. Thats why i think its wiser, if you (or someone else desperate enough) tries their own implementation first and then we can discuss the behavioural differences .. ;-P

    So whats the weird bug ? Sometimes it crashes. It hasnt crashed yet, when i dont use my memory manager. When it crashes, it crashes in the diagnostic stuff inside the STL iterators, cause i use an STL map to keep track of the allocations (address and size), so that i know how much memory i gave to the CLR. I am pretty sure, that i use STL correctly (the code is way too simple to do it wrong). On the other hand, i am almost equally sure, that the MS STL implementation is ok. If there were a bug in the iterator diagnostic code, i wouldnt be the first to find it (in other words, google didnt found any hints about bugs in the STL).

    So whats that "too simple" code ?
    std::map::iterator i = mymap.find (address);
    if (mymap.end () != i) ...
    Sometimes the iterator diagnostic code complains, that i and mymap.end () dont reference the same container ..

    Anyway, with the hints i gave above, you should get a working managed form that stays in front of FSX even when you switch from FSX windowed mode to full screen mode and back. Maybe you even get WPF running in it.

    I dont know for sure if FSX uses STA or MTA thread apartment mode, but i think FSX uses STA.

    Feel free to ask whatever else you want to know.

    I think i will post my code later, but first i have to clean it up a bit and i want to replace my real app with something simpler.

    Martin
  8. MillKa

    MillKa

    Joined:
    29/10/06
    Messages:
    16
    Country:
    germany
    .. i forgot to mention ..

    when implementing a memory manager for CLR, it is extremely helpful too look into SSCLI aka ROTOR, the MS open source version of CLR.

    It would be really nice to be able to write gauges and other modules in C#. I hope the window issue is solved by now. The memory usage problem should be solvable with a memory manager that avoids my error .. ;-P

    It would be nice, if the managed side could use its own UI thread, but i think thats not possible on XP (only one UI thread per process). Does that limitation still exist in Vista ?

    I see a few possible problems with running managed code inside FSX:
    - sharing the main UI thread (am i right that we have to ?)
    - sharing the stack (how do we know how much stack space is left ?)
    - memory issues (FSX C++ memory allocation rules versus NET GC allocation rules)

    About the stack issue: My managed application uses FSX UI thread as its UI thread, but i try to do pretty much everything else in additional threads in my application to minimize potential problems.

    I would be VERY interested in the opinions of MS Aces about running managed code inside FSX. I think they do some smart things to make the FSX to module/gauge interface somewhat bullet- and idiotproof, but maybe they didnt expect us to do such filthy things as running the CLR inside FSX ?

    I hope that we 3rd party developers can join brains to create an open source interface dll, that allows using managed code inside FSX, with or without hints from MS. Of course it would be better to have some guidelines like "ok, do this if you really have to, but never ever do that" from MS. If we all use the same interface dll to run managed code, life would be simpler for us as well as MS Aces, cause i guess they already have to test FSX with a gazillion addons already ..

    I doubt that an integrated managed interface will magically appear in FSX SP2/DX10, cause its a bit late for dramatic changes like that, but pleeeeze put it on the wishlist for FS 11 ..

    On the other hand, the MS VS C++ team has invented mixed mode (managed and unmanaged) C++ applications and dlls, and im sure they have tested that stuff til it hurts and even longer. Maybe FSX SP2/DX10 could change just that one compiler switch ? (insert begging doggie smilie) .. ;-P

    Martin
    Last edited: 27/8/07
  9. ziporama

    ziporama

    Joined:
    28/9/06
    Messages:
    203
    Country:
    unitedstates
    Martin,

    nice work. I haven't tried the wrapping of the window quite that way as I went lower level (SetWindowsHookEx on FSX's main loop) which would get me very odd behavior. I'll try the NativeWindow wrapper and see how I fare.

    Your idea of using your own memory manager and limit it so that the OOM error doesn't happen is extremely interesting. I'm wondering if there's a startup parameter to the CLR class loader that will limit the total memory used by the default memory manager. I'm guessing probably undocumented if it even exists. SP2 of FSX should also help in this area as, if I read it correctly, it will not shadow video memory as it currently does, using less of the virtual process memory.

    FSX's main thread should be STA - this seems to be a requirement of all UI threads. However, the threads it kicks off may not be. WPF and DirectX require STA (at least with DX9).

    I'm curious to see your 2.x CLR loader and how it differs from the older interface.

    Keep it coming!

    Etienne
  10. ziporama

    ziporama

    Joined:
    28/9/06
    Messages:
    203
    Country:
    unitedstates
    Update:

    The key is definitely to trap the FSX message loop on the same thread that started the custom DLL that hosts the CLR instance. Thanks for pointing that out!

    System.Windows.Forms forms now show correctly in FSX over the UI or the sim window with no flicker. This works in windowed or full screen perfectly.

    I'll rerun my WPF tests to see if overlaid DirectX instances in DX9 work now that the threading issue is out of the way.

    Etienne
  11. ziporama

    ziporama

    Joined:
    28/9/06
    Messages:
    203
    Country:
    unitedstates
    Success!

    Update!

    I was able to use the WPF ElementHost control to place a wpf custom control inside a .NET window inside the FSX process space, and keep FSX stable. Attached is a zipped screenshot from KDEN taken in full screen mode as a demo.

    The first window is a regular .NET window. The second is the one containing the ElementHost displaying a rudimentary WPF control (the expander in this case, borrowed from MSDN's samples).

    More testing to follow, and the key points are that (1) the windows show in full screen mode or windowed mode and (2) they behave normally with no flicker.

    Requirements:
    1) the windows must be parented to FS98MAIN on the same thread that the DLL was loaded by FSX, which is the UI thread.
    2) The CLR must be hosted in a native DLL started by FSX.
    3) CLR memory use can be significant, and can be supposedly managed through the GC.AddMemoryPressure() call. Another memory saver is to flush memory and free the GC after every assembly load - the reason being that every referenced library tends to be loaded by the CLR in memory even when the particular assembly is not used.

    Regards,

    Etienne

    Attached Files:

    Last edited: 20/9/07
  12. gapalp

    gapalp

    Joined:
    8/11/07
    Messages:
    7
    Country:
    unitedstates
    Custom / Integrated FSX Windows: Anymore progress?

    I am really interested in how to create custom integrated windows in FSX. Has anyone made anymore progress on this? Any sample code to share that goes into more detail? I am using C# and can get a custom form to show up on top of FSX and use FSX as the handle. But of course it is a separate window so full screen FSX does not work properly and in window mode every time I click on my form FS pauses since I "switched" to another window (I believe that is a FS setting, but it shows my window is not truly integrated with FSX). It appears some FSX programs, like FSWidgets, are using an integrated window now.

    I have seen some FS9 programs, like FSNavigator, that have integrated windows also. Maybe someone knows how to do it in previous version of FS and can share their thoughts. I understand the theory in this thread but lack the C++/C# skills to implement without seeing more samples.

    Thanks,
    gapalp
  13. ziporama

    ziporama

    Joined:
    28/9/06
    Messages:
    203
    Country:
    unitedstates
    Here are my findings on the subject:

    1) you must use the SAME thread as the UI thread for FSX, which happens, conveniently, to be the thread that calls DllStart() in your module. Windows and DirectX only support a single rendering thread per process. Furthermore, DirectX only supports rendering of the z-order for the windows displayed by the same process. The net effect if you run on a different thread is invisibility when in full screen mode (that's the DirectX z-order at work), or flicker/drawing abnormalities (usually only when windows are overlapped).

    2) To use C# (or any .NET language) in FS, you must instantiate the CLR within the FSX process. This requires another trick on 2Gb machines, which is to manage memory carefully or your will run out that much faster, as FSX already runs out by itself. Adding the CLR to the process' use of memory just makes the situation worse. On 4Gb machines, not a problem, provided that you mark the FSX.exe file as being able to use the large address space, (FSX RTM or SP1), and in the case of FSX Acceleration or later, already done for you. The large address space flag requires and editor that can modify the header of exe files. Note that this is only true if running on a 64 bit O/S as you cannot address all 4Gbs in a 32 bit Operating System due to the way windows utilizes kernel and process mode memory.

    3) Ensure any window you create is on the same thread as the calling thread, and it subclasses the FS98MAIN window only.

    4) FSX knows to load your module only if there is an entry for it in the dll.xml configuration file.

    5) When FS loads your module , it will display a warning the first time your signed module is loaded, or, if your module is not signed, all the time. You must code sign your module. Unsigned modules will result in a security prompt all the time.

    6) No, you do not have to purchase a code signing certificate and spend $300 to $600 a year to get it from the cert vendors out there. First, you can create your own cert using the tools provided in the Windows Platform SDK (download from MSDN and do an msdn search on "signtool"). Second, you can register yourself as a CA if you are an administrator on the local machine, although not mandatory. This is done by placing the cert in your trusted CA store, the easiest thing is simply to right click the signed DLL, go under the certification tab, and register it from there. Don't use the default store, manually specify the trusted store, and no more prompting.

    7) FSX modules have to be 32 bit DLLs created in C++. You cannot load a .NET dll because FSX will not have a clue as to what to do with it. A .NET DLL's structure is not at all the same as a regular native DLL. It has a bootstrap specific to .NET and is nothing but an assembly in disguise with none of the normal entry points expected (ie, DLLMain()).

    8) The code sample above shows the basics of one way to load a CLR within a C++ DLL (applicable to any version of .NET, although under 3.0, you start getting a "deprecated" warning.) Just make sure you do not try to create a CLR instance within
    the DLLMain() entry point - this will either crash FSX, or get you a loader warning error. This is because when DLLMain() is called, the Windows librarian has not necessarily loaded all the other dependencies you need, and it creates many problems especially with COM, and the CLR is nothing but a COM control in disguise :)

    Hope this helps. I can't share any more code unfortunately because some of this is proprietary to my business, but the information above should at least get you going :)

    Regards,

    Etienne
    Last edited: 10/12/07
  14. gapalp

    gapalp

    Joined:
    8/11/07
    Messages:
    7
    Country:
    unitedstates
    Thanks

    Thanks for the review Ziporama. I will dig deeper into your suggestions.

    I have made some good progress. I am able to call a C# dll, which contains my program, from a C++ dll module. This allows the sound and graphics of the main FSX window to continue even when you are interacting with my window. The graphics stutter if my window is moved, but most of the time it would not need to be moved. Also, as you can imagine, I am unable to view my window in full screen. I will have to look into your suggestions further to get this done. For right now my program requirements will be for window mode users of FSX.

    Your earlier suggestions in this post have certainly helped me get to the point I am at now. The key for the method I am using is putting your custom code to call the C# dll in the C++ dll module DLLStart and DLLStop, something not documented in the SDK very well. Also, all the SDK examples are console exe apps, not very practical in my opinion.
  15. ziporama

    ziporama

    Joined:
    28/9/06
    Messages:
    203
    Country:
    unitedstates
    The key to avoiding the window issue is to ensure you create the .NET window on the same thread as the call to DllStart(), and subclassing the FS98MAIN from there.

    Not to worry, this is fairly complex low level stuff :) It only requires a good understanding of win32 programming, how windows uses messages (unchanged since windows 3.1 - that's a flashback), how direct3d rendering works, a sprinkling of COM, custom CLR hosting, the dynamic library loader pitfalls, a pinch of low level interop in .NET, and what some may call the abundance of "shortage" on public information on how FS and SimConnect work under the hood, not to mention managing it all without getting a divorce (if you're in that situation). And it only gets you as far as "hello world" in a window, anything a comp-sci 101 student can do day 1.

    No problems matey, devil's in the details...

    E.
  16. gapalp

    gapalp

    Joined:
    8/11/07
    Messages:
    7
    Country:
    unitedstates
    Update

    Just another update on the progress I have made with my application window in FSX.

    Got my app window to show up in FSX full screen mode finally. I did this by using and adapting the above C++ dll which creates the CLR instance and changing my C# dll to have the DLLStart and DLLStop methods. AS a byproduct, I had to move my Simconnect meenu code to the C# dll, which calls my application when the menu item is clicked in FSX.

    Th only outstanding issue I am having at the moment is when I move my window. In FSX windowed mode when I drag my window, FSX graphics stop responding until I let go of the window. In FSX full screen mode when I drag my window, FSX graphics become choppy and my window does not redraw itself properly and leaves ghosts until I let go of the window.

    My app is "hooked" into FSX since I am using FS98MAIN as the handle for my app form, that is why it shows up in full screen and why I do not lose sound in windowed mode when I am interacting with my app. So, if anyone has any ideas or suggestions on get my window to move without graphics issues, pleas let me know. Maybe I am missing some GDI or DirectX trick I need to be doing.
  17. vodaley

    vodaley

    Joined:
    1/11/06
    Messages:
    7
    Country:
    israel
    How to Debug .NET DLL called from Native DLL

    Hello,

    First, I want to thank you all for a great work you've done.
    I Have successfully built the code from all parts written here and everything works fine.
    The problem is that I can't debug Managed DLL. I've tried to attach it to FSX Process, but no success... Any Ideas?

    Thanks,
    Pavel
  18. ziporama

    ziporama

    Joined:
    28/9/06
    Messages:
    203
    Country:
    unitedstates
    You can debug managed and non-managed by setting the project start to your managed DLL, and enabling debug of native code if you wish. I can't recommend you debug both at the same time as odd things can happen due to stability. Only set a breakpoint in the entry point of the managed DLL and you are good to go.
  19. vodaley

    vodaley

    Joined:
    1/11/06
    Messages:
    7
    Country:
    israel
    Thanks alot!
  20. vodaley

    vodaley

    Joined:
    1/11/06
    Messages:
    7
    Country:
    israel
    Hello,
    I've tried to implement SimConnect managed, Everything looks fine, simconnect object was created and events notifications was registred successfully, I was registred event "simstart".
    While event should be called I'm getting:

    "Managed DebuggingAssistant 'FatalExecutionEngineError' has detected a problem in 'C:\Program Files\Microsoft Games\Microsoft Flight Simulator X\fsx.exe'.
    Additional Information: The runtime has encountered a fatal error. The address of the error was at 0x7a0a067a, on thread 0xe0c. The error code is 0xc0000005. This error may be a bug in the CLR or in the unsafe or non-verifiable portions of user code. Common sources of this bug include user marshaling errors for COM-interop or PInvoke, which may corrupt the stack."


    I've tried to Start a window in diffriend thread but no success...
    any ideas?

    Thanks,
    Pavel

Share This Page