MFC state invalid when DLL called through LoadLibr

2019-04-09 23:20发布

问题:

i'm fighting with MFC and dynamicly linking DLLs with LoadLibrary. It seems that I cannot get the MFC state right when the app calls DLL, and the DLL calls back in the same call. Ultimately, it leads to tons of asserts.

Here is code mock-up of what i'm doing.

  1. The application is just normal, straight from the wizard MFC app. I've got button somewhere and this is the button's handler:

    void callback()
    {
        AFX_MANAGE_STATE(AfxGetStaticModuleState( ));
    
        CDialog1 dlg;
        dlg.DoModal();
    }
    
    typedef void (*TPluginMainFunc)(void*);
    
    void CTheApp1View::OnTestRun1()
    {
            static HMODULE hPluginMFCShared = LoadLibrary( _T("PluginMFCShared") );
            if ( hPluginMFCShared )
            {
                    TPluginMainFunc func = (TPluginMainFunc) GetProcAddress( hPluginMFCShared, "plugin_main" );
                    if ( func )
                    {
                            func(callback);
                    }
            }
    }
    
  2. Then the 'PluginMFCShared' looks like this:

    typedef void (*TFunc)();
    
    extern "C" void GS_EXTERNAL_ENTRY plugin_main(TFunc func)
    {
            AFX_MANAGE_STATE(AfxGetStaticModuleState( ));
    
            func();
    
            CDialog1 dlg;
            dlg.DoModal();
    }
    

So, the idea is that the app (CTheApp1View::OnTestRun1) loads a library and calls a function directly passing in a callback pointer. The library would use that callback to execute something from the app before continuing.

I thought AFX_MANAGE_STATE will take care of the MFC state, but there seem to be something more that needs to be done.

A test project could be found at (make sure TheApp1 project is set to be the start-up project): SystemOfPlugins.zip

Any ideas?

Thanks for any suggestions.

回答1:

Here's another suggestion. In your App variable, add an AFX_MODULE_STATE* variable named m_pModuleState, and initialize it at the end of the InitInstance funciton,

m_pModuleState = AfxGetModuleState();

Modify your callback function to set the application state before opening the dialog, and then set back the original state before exiting the function

void callback()
{
    //Get the original state
    AFX_MODULE_STATE* pOriginalState = AfxGetModuleState();

    //Set the mfc state
    AfxSetModuleState(((CTheApp1App*)&theApp)->m_pModuleState);

    //Do stuff here
    CDialog1 dlg;
    dlg.DoModal();

    //Set the mfc state back to its original state
    AfxSetModuleState(pOriginalState);
}

And keep your plugin as it was in your example

extern "C" void GS_EXTERNAL_ENTRY plugin_main(TFunc func)
{
    AFX_MANAGE_STATE(AfxGetStaticModuleState( ));

    func();
    CDialog1 dlg;
    dlg.DoModal();
}

This way, you would call AFX_MANAGE_STATE in your plugins, but when some of the plugin make a call to the callback function, you make sure to set the app's state so it can find the good dialog resources and execute state-specific functions



回答2:

I looked at your code, and I got it working by modifiying 2 functions :

in pluginMFCShared.cpp, I called AFX_MANAGE_STATE after the call to func()

extern "C" void GS_EXTERNAL_ENTRY plugin_main(TFunc func)
{

    func();

    AFX_MANAGE_STATE(AfxGetStaticModuleState( ));
    CDialog1 dlg;
    dlg.DoModal();
}

In theapp1view.cpp, I removed the AFX_MANAGE_STATE

void callback()
{
    CDialog1 dlg;
    dlg.DoModal();
}

Now, the two dialogs pops one after another



回答3:

Are you building the dll with the _LIB preprocessor flag? If so, check if you really should - the whole 'MFC dll' concept is antiquated, there is no reason to use it anymore. Then, forget about all the AFX_MANAGE_STATE stuff. In your dll, store the HMODULE of the dll that is passed to DllMain, and use ::AfxSetResourceHandle() to the correct value before each use of a CDialog or similar. Wrap it in a smart pointer-like class that sets the correct resource handle and resets it to the old one (= the main app's one, usually 0x4000...) when the object goes out of scope.

For all purposes where you can pass a resource handle directly (LoadString etc), you don't even have to touch the global handle.

Much easier to work, and much more transparent. The resource handle is the only one that is relevant for MFC state in MFC versions since VS6 anyway.