Solved

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

Posted on 2007-11-22
15
1,167 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
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
  • 8
  • 4
  • 3
15 Comments
 
LVL 86

Expert Comment

by:jkr
ID: 20335595
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
ID: 20335603
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
ID: 20335624
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
Technology Partners: We Want Your Opinion!

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

 
LVL 19

Author Comment

by:mrwad99
ID: 20335635
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
ID: 20335779
What error exactly? (File, line etc.)
0
 
LVL 19

Author Comment

by:mrwad99
ID: 20337974
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
ID: 20338378
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
 
LVL 19

Author Comment

by:mrwad99
ID: 20338515
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
ID: 20338963
>>>> 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
ID: 20348951
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
ID: 20349017
>>>> 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
ID: 20349140
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
ID: 20349991
>>>>  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
ID: 20350294
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
ID: 20350703
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 Tool: Subnet Calculator

The subnet calculator helps you design networks by taking an IP address and network mask and returning information such as network, broadcast address, and host range.

One of a set of tools we're offering as a way of saying thank you for being a part of the community.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Suggested Solutions

Title # Comments Views Activity
How to convert MFC::CString to UTF8 wchar_t* 10 449
wordappend challenge 8 246
Best book to learn C++ 4 96
JQuery serialize and unserialize 8 265
Introduction: Database storage, where is the exe actually on the disc? Playing a game selected randomly (how to generate random numbers).  Error trapping with try..catch to help the code run even if something goes wrong. Continuing from the seveā€¦
Exception Handling is in the core of any application that is able to dignify its name. In this article, I'll guide you through the process of writing a DRY (Don't Repeat Yourself) Exception Handling mechanism, using Aspect Oriented Programming.
The viewer will learn how to user default arguments when defining functions. This method of defining functions will be contrasted with the non-default-argument of defining functions.
The viewer will learn how to clear a vector as well as how to detect empty vectors in C++.

737 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