Solved

CPropertyPage created in a DLL used on a CPropertySheet in an app

Posted on 2007-11-22
15
1,132 Views
Last Modified: 2013-11-20
Ah hello.

This question is a discussion of that started in http:Q_22977307.html.  I am trying to display CProperyPage derived classes on a cPropertySheet.  The caveat is that the CPropertyPages are created in a DLL, the CPropertySheet is an a calling application.  This question is hopefully going to resolve how to get around the fact that (in short) we cannot use MFC objects across module/thread boundaries.

Alex(itsmeandnobodyelse), please can you elaborate more on your last point:

"Each dll must create the objects and delete its own objects. You then can pass pointers between dll functions or exchange messages but not call member functions."
0
Comment
Question by:mrwad99
  • 8
  • 4
  • 3
15 Comments
 
LVL 86

Expert Comment

by:jkr
Comment Utility
Well, you also can pass and safely delete pointers over module boundaries when all modules are compiled as '/MD, i.e. 'Code Generation: Use Runtime Library 'Multithreaded (Debug) DLL':

"Defines _MT and _DLL so that both multithread- and DLL-specific versions of the run-time routines are selected from the standard .H files. This option also causes the compiler to place the library name MSVCRT.LIB into the .OBJ file.
Applications compiled with this option are statically linked to MSVCRT.LIB. This library provides a layer of code that allows the linker to resolve external references. The actual working code is contained in MSVCRT.DLL, which must be available at run time to applications linked with MSVCRT.LIB"

The effect of this is that all modules use the same version of 'MSVCRT.DLL' where allocation and deletion are performed, thus sharing the same heap and avoiding memory leaks when deleting modules over DLL boundaries.
0
 
LVL 19

Author Comment

by:mrwad99
Comment Utility
Hi jkr!

Thanks for the post.  You are right with that comment, however, as Alex says at http:Q_22977307.html#20334942, that is not the issue, it is to do with the static member variables not being available over the boundaries.  I am currently requesting in that question a tiny bit of clarification from Alex.
0
 
LVL 86

Expert Comment

by:jkr
Comment Utility
Um, how are you exporting/importing your classes? See http://msdn2.microsoft.com/en-us/library/81h27t8c(VS.80).aspx ("Using dllimport and dllexport in C++ Classes"):

dllexport Classes

When you declare a class dllexport, all its member functions and static data members are exported.

Yet I would agree that this is not a good practise.
0
 
LVL 19

Author Comment

by:mrwad99
Comment Utility
OK.  I have one function exported from my DLL:

DLL_SPEC void InitialiseContentManager( CPropertySheet& sheet  )
{
      sheet.AddPage ( new CTestPage1 );
      sheet.AddPage ( new CTestPage2 );
}

The CPropertyPages themselves are not exported, as the point is that the sheet does not know what pages it will have added to it, that depends on the child DLL and what classes it chooses to add to it.

The app loads this function via GetProcAddress and passes in *this, as the app itself is a CPropertySheet.

As I say at http:Q_22977307.html#20334942 I get a crash at GetRuntimeClass() doing this.
0
 
LVL 86

Expert Comment

by:jkr
Comment Utility
What error exactly? (File, line etc.)
0
 
LVL 19

Author Comment

by:mrwad99
Comment Utility
OK.

CTestPropSheetDlg::CTestPropSheetDlg(LPCTSTR pszCaption, CWnd* pParentWnd, UINT iSelectPage)
:CPropertySheet(pszCaption, pParentWnd, iSelectPage)
{
      InitialiseSheet();
}

void CTestPropSheetDlg::InitialiseSheet()
{
      typedef void (*pFunc) ( CPropertySheet& );
      pFunc pInitialiseContentManager;
      CPropertyPage* pPage ( NULL );
      if ( HMODULE hLibrary = LoadLibrary ( _T("TestSubDLL.dll") ) )
      {
            pInitialiseContentManager = ( pFunc ) GetProcAddress ( hLibrary, "InitialiseContentManager" );
            if ( pInitialiseContentManager == NULL )
            {
                  // Function was not found
                  ASSERT ( FALSE );
            }

            pInitialiseContentManager( *this );
            FreeLibrary ( hLibrary );
      }
      else
      {
            // DLL was not found
            ASSERT ( FALSE );
      }
      SetWizardMode();      

      m_psh.dwFlags |= PSH_WIZARD97|PSH_WATERMARK|PSH_HEADER;

      // Step 2: Fix the problem to show images
      m_psh.hInstance = AfxGetInstanceHandle();

}

Unhandled exception at 0x78324a85 (mfc80ud.dll) in TestPropSheet.exe: 0xC0000005: Access violation reading location 0x1001fdc4.

Location: objcore.cpp, line 46.  This is the call to

CRuntimeClass* pClassThis = GetRuntimeClass();

within CObject::IsKindOf(const CRuntimeClass* pClass)

0
 
LVL 19

Author Comment

by:mrwad99
Comment Utility
I think I may have solved the problem.

Currently the only way I can get this to work is if I explicitly export all of my CPropertyPage derived classes from my child DLL, #include their header files in my CPropertySheet class, then create them all in the CPropertySheet itself, adding them via AddPage:

// CTestPage one is declared as __declspec(dllexport)
AddPage ( new CTestPage1 );

Now.

The call to AddPage() succeeds.  The memory for the CTestPage1 object has been allocated in the application module.  So what I now do is *instead of* my child DLL function

a) returning a "new'ed" CTestPage1 object that was created in the child DLL, then calling CPropertySheet::AddPage in the app on the object returned

b) Passing the CPropertySheet to the method in the DLL, create the CTestPage1 object in the DLL and call AddPage on the passed CPropertySheet with this CTestPage1 object

I have my DLL function return a CRuntimeClass:

extern "C" DLL_SPEC CRuntimeClass* InitialiseContentManager()
{
      //AFX_MANAGE_STATE(AfxGetAppModuleState())
      CRuntimeClass* r = RUNTIME_CLASS ( CTestPage1 );
      return r;
}

Then in my application I can:

      typedef CRuntimeClass* (*pFunc) ( CPropertySheet& );
      pFunc pInitialiseContentManager;
      CRuntimeClass* r = NULL;
      if ( HMODULE hLibrary = LoadLibrary ( _T("TestSubDLL.dll") ) )
      {
            pInitialiseContentManager = ( pFunc ) GetProcAddress ( hLibrary, "InitialiseContentManager" );
            if ( pInitialiseContentManager == NULL )
            {
                  // Function was not found
                  ASSERT ( FALSE );
            }

            r = pInitialiseContentManager( *this );
            FreeLibrary ( hLibrary );
      }
      else
      {
            // DLL was not found
            ASSERT ( FALSE );
      }
      if ( r )
      {
            CPropertyPage* pPage = ( CPropertyPage*)r->CreateObject();
            AddPage ( pPage );
      }

thereby allocating the memory within my application, not within my DLL (!)

I am still not sure of this, but it looks to be good.  Can you cast your opinions on this please?
0
Find Ransomware Secrets With All-Source Analysis

Ransomware has become a major concern for organizations; its prevalence has grown due to past successes achieved by threat actors. While each ransomware variant is different, we’ve seen some common tactics and trends used among the authors of the malware.

 
LVL 19

Author Comment

by:mrwad99
Comment Utility
OK that is not working consistently either.  Sometimes the CRuntimeClass returned by the call to pInitialiseContentManager is valid, other times it is just rubbish (??? in the debugger).  Hence

   CPropertyPage* pPage = ( CPropertyPage*)r->CreateObject();

calls

CObject* CRuntimeClass::CreateObject()
{
      ENSURE(this);

      // This line crashes, all the member variables of 'this' are rubbish.
      if (m_pfnCreateObject == NULL)

      //...
}

which crashes as noted.

Is this approach no different to what was s+aid about variables in the DLL's memory space not being accessible in that of the app?  This does not make sense though, since I can say

      // Create instance of exported page
      CTestPage1* pP = new CTestPage1;

      typedef CObject* (PASCAL* _pCreateFunc)();
      _pCreateFunc pCreateFunc;
      pCreateFunc =  pP->GetRuntimeClass()->m_pfnCreateObject;

and surely then pCreateFunc will point at exactly the same function, yet this does not crash!

Please can someone clear this up before I go insane!
0
 
LVL 39

Expert Comment

by:itsmeandnobodyelse
Comment Utility
>>>> I am still not sure of this, but it looks to be good.  
>>>> Can you cast your opinions on this please?
As I told you, a class with static variables safely can only used within one exectuable. You can run the message loop in the application and the frame with the CPropertySheet in the app *but* the only communication between the frame and the app has to be done via (windows) messages *and* not by means of MFC functions like AddPage  which rely on static members like the CRuntime pointer.

Actually that means that you probably can't use the CPropertySheet and CPropertyPage class as both were declared with the DECLARE_DYNAMIC macro beside you would use each of them only in one executable and would overwrite all functionality of both where they were dependent on MFC class pointers rather than on windows messages. That can be achieved as - I told you about - I was responsible for such a system for longer than 5 years. Unfortunately I wasn't responsible for the architecture of that system and have now a different project so that I can't tell you the very details (which might be too much for here anyhow). But as far as I remember we did *not* derive from CPropertyPage and CPropertySheet but directly from CDialog and CMainFrame and added the functionality of 'tabbed' dialogs by not using 'runtime' information but by means of windows messages only.

Regards, Alex
0
 
LVL 19

Author Comment

by:mrwad99
Comment Utility
Alex

Once again I thank you for your help.  I do have more questions about this in general, but I would just like to clarify:

Are you saying that I cannot declare a CPropertySheet class and all of the constituent pages in my DLL, export this class, then simply create and display an instance of it in my application via DoModal() ???  I hope (and feel) this is not the case; I think I cannot add pages to it from within the application.

Please forgive my blatant naiveness on all of this, I simply do not have your experience to know better.

I will close this question after you have commented on the above and then point everyone at another question focussing more on static variables within a DLL.

Thanks again :o)
0
 
LVL 39

Expert Comment

by:itsmeandnobodyelse
Comment Utility
>>>> Are you saying that I cannot declare a CPropertySheet class and all of the constituent pages in my DLL, export this class, then simply create and display an instance of it in my application via DoModal() ???  I hope (and feel) this is not the case; I think I cannot add pages to it from within the application.

Unfortunately, I think that is the consequence of the static CRuntime pointer. I see no way to make it go when the CPropertySheet was created in a different class than the CPropertyPage. You could think about creating creating copies of the property pages (not using the static runtime pointer. The copy could attach to the 'window' rather than to creating a new window. But it is not quite easy to make it run especially if you want to use the member functions of the dll later. IMO, it would be easier to have each class full run in their own executable and only use native 'windows api' to combine them into the sheet. It is not so much to draw a few 'tabs' and switch the dialogs by means of windows messages. But of course it is a different game ...

Alex
0
 
LVL 19

Author Comment

by:mrwad99
Comment Utility
Thank you Alex.

I have just been searching our code base for this kind of situation, and we do have the following:

DLL:
====

class DLL_SPEC CMyPropSheet : public CPropertySheet
{
// ... Usual stuff
}

int CMyPropSheet::DoModal()
{
      // Setup the sheets
      CMyPropSheetDlg1 Page1(this);
      CMyPropSheetDlg2 Page1(this);

      AddPage( &Page1 );
      AddPage( &Page2 );
      
      // No "Apply" button
      m_psh.dwFlags |= PSH_NOAPPLYNOW;
      m_psh.dwFlags &= ~PSH_HASHELP;

      // Now do modal
      return CPropertySheet::DoModal();
}


APPLICATION:
============

#include "MyPropSheet.h"

CMyPropSheet sheet;
sheet.DoModal();

And this has not had any problems.  Sometimes the constructor adds the pages via AddPage, othertimes (like above) it is done in DoModal().  I cannot see any issue with this as all of the work is done in the DLL; no pages are added outside of the DLL...Is this how you interpreted my last comment?

What do you think?

0
 
LVL 39

Accepted Solution

by:
itsmeandnobodyelse earned 500 total points
Comment Utility
>>>>  CMyPropSheetDlg1 Page1(this);
That's seems to create the dialog pages in same dll than the property sheet.

>>>> CMyPropSheet sheet;
The CMyPropSheet is an exported class (I assume). If using it in the application like you did, you have to link the import-export library of dll to the application or you would get 'unresolved externals'. That is not the same case as if you would call an exported dll function like with InitialiseContentManager. For any exported class function the export library contains a wrapper function which fully runs in the context of the calling application. In case of calling an exported dll function by means of function pointers the function runs in the context of the dll executable. In the latter case if you are using the same MFC classes in your app and in your dll both have different static member instances what leads to a crash if ever two different static data were compared  cause the concept relies on the uniqueness of static class data.

Let's see what happens when calling IsKindOf() member function, which is a virtual function of CObject the baseclass of all MFC derived classes. Let's assume the CMyPropertySheet::DoModal() calls CPropertySheetEx::DoModal() which calls CPropertySheetEx::BuildPropPageArray(). Here, for each property page virtually IsKindOf(RUNTIME_CLASS(CPropertyPageEx))  was called. The IsKindOf starts virtually with the most derived class what is CMyPropSheetDlg1 in your sample. It compares the passed pointer of CRuntimeClass with the own static CRuntimeClass pointer. If they match the IsKindOf returns TRUE, if not it calls its own baseclass (it doesn't need to be the direct baseclass, as the baseclass name was passed in the IMPLEMENT_DYNAMIC macro, so - theoretically - you may skip any of the intermediate baseclasses) to make the check at the next level and so on. If finally CObject::IsKindOf has no match, it throws an exception which causes the crash. So, to make it run you definitively cannot use a (baseclass) pointer which was created in a different executable and has stored the static CRuntimeClass pointer from that dll while the CMyPropertySheet::DoModal()  runs in the context of the application and uses the static CRuntimeClass pointer created there.

As not all member functions of CPropertySheet or CPropertyPage are using runtime information, you may be lucky. So, I assume the last code you posted worked as you didn't load the dll dynamically but statically and because the CMyPropertySheet and CMyPropSheetDlg1 were created both in the context of the application. For the same reason MFC can be used from a dll as well as from a static library. But it is a shot from the hip. To know for sure I would need to check link maps and/or check the addresses of the static CRuntimeClass pointers when debugging.

Regards, Alex
0
 
LVL 19

Author Comment

by:mrwad99
Comment Utility
Yes, that code is linked to statically.  And you are correct in the sense that the pages and sheet are created in the same module.  So I guess that is ok then.

I am on a learning curve here, so if you could spare a few minutes to look at http:Q_22982492.html I would really appreciate it.

Thanks once again Alex.
0
 
LVL 39

Expert Comment

by:itsmeandnobodyelse
Comment Utility
I think the problems will rise up when you try to load the dll's dynamically in your app *and* use pointers created in the dll. When mixing these pointers, e. g. by calling AddPage you come into trouble.

You can use dynamically linked dlls only if the communication between the class objects was made via windows messaging only (or some other resources of the OS like events). So, actually you have to make it that each dll/app is *complete* regarding the MFC virtual functions and you can't use virtual calls between dll boundaries but windows messages or windows event handling only.

I am sorry that I can't give you a clear rule what works and what doesn't work. I have evaluated much code of MFC but sometimes it was years ago and I didn't actually evaluate it in detail whether some code will fail in a multi-dll environment or not.  Generally, if you got a MFC class pointer created in a dll, you safely can get it's
windows handle. You then can call ::PostMessage(...) using that handle. But using virtual functions or static members, or using the message map mechanism most likely will fail. It must not fail cause some of the functions were implemented by means of basic Windows API as well. But if they used C++ polmorphism and static class members you were lost.

Regards, Alex
0

Featured Post

Free Trending Threat Insights Every Day

Enhance your security with threat intelligence from the web. Get trending threat insights on hackers, exploits, and suspicious IP addresses delivered to your inbox with our free Cyber Daily.

Join & Write a Comment

Suggested Solutions

Introduction: The undo support, implementing a stack. Continuing from the eigth article about sudoku.   We need a mechanism to keep track of the digits entered so as to implement an undo mechanism.  This should be a ‘Last In First Out’ collec…
Have you tried to learn about Unicode, UTF-8, and multibyte text encoding and all the articles are just too "academic" or too technical? This article aims to make the whole topic easy for just about anyone to understand.
The goal of the tutorial is to teach the user how to use functions in C++. The video will cover how to define functions, how to call functions and how to create functions prototypes. Microsoft Visual C++ 2010 Express will be used as a text editor an…
The viewer will learn how to clear a vector as well as how to detect empty vectors in C++.

772 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

10 Experts available now in Live!

Get 1:1 Help Now