0

I have this code:

// ------- My dialog header file ----------
class CMexifierDlg : public CDialogEx {
public:
    unsigned int static CMexifierDlg::doit(LPVOID pParam); // this is the thread
    CMexifierDlg* pMexifierDlg=this;
    int x, // used to initialize the dialog AND by thread
    y,   //   set by dialog,  used to pass info to thread
    z    // used by MULTIPLE thread functions, but NOT by dialog
};

// ------- My CPP file -----
void CMexifierDlg::OnClickedCheck1() {
    CMexifierDlg* myparm; myparm = pMexifierDlg;
    AfxBeginThread(doit, (LPVOID)myparm); 
        // thread terminates itself when job is done
}
// -----  my thread function
unsigned int   CMexifierDlg::doit(LPVOID pParam) {
    // this is where the actual work happens
    CMexifierDlg* D_ptr=(CMexifierDlg*)pParam;
    int y = x;  // doesn’t compile
    int y = D_ptr->x;   // fails at runtime
    // now we make many calls (as part of thread)
    // to other functions that need D_ptr
    return 0;   // thread completed successfully
}

The header file has MANY variables, some of which are needed ONLY by my main (and only) dialog, some of which are shared by the dialog and the ONE thread I create from the dialog when a button is pressed and some that are used ONLY by many functions called within the thread. Once the thread starts, it runs to completion before ANY other actions will be allowed in the dialog, except exiting the whole program. However, once the thread is done, it can be restarted with NEW data from the dialog. Can someone SHOW me what I have wrong that will make this work? I am getting lost with understanding HOW to pass the correct thing to my thread. One thing I tried is to MOVE my thread-only var definitions to the .CPP file as global data and that SEEMS to be OK (but is it a proper thing to do)?

IInspectable
  • 46,945
  • 8
  • 85
  • 181
DocDJ
  • 31
  • 5
  • I have moved ALL the non-member variables to the top of my .CPP file and the code builds correctly, but it still dies at the first statement in the thread, that tries to use the parameter (after it is assigned to D_ptr). – DocDJ Apr 20 '23 at 18:58
  • Could you please [edit] and format this mess properly? – Jabberwocky Apr 21 '23 at 07:53
  • You can call `AfxBeginThread(doit, this);` to pass a pointer to your dialog to the thread function. That pointer isn't going to know when it stops being a valid pointer, and by consequence neither does your thread function. You'll have to arrange for additional communication. – IInspectable Apr 21 '23 at 13:34
  • I'm not sure if `AfxBeginThread()` is really happy to call a class function. You might want to double-check this. – thomiel Apr 21 '23 at 14:33
  • @thomiel `doit()` is a `static` class member. It has the same properties as a free function, with the only exception being that the call syntax needs to name the class type. Modulo the missing [calling convention](https://learn.microsoft.com/en-us/cpp/cpp/calling-conventions) declarator this is perfectly valid. – IInspectable Apr 22 '23 at 10:27
  • @IInspectable You do have an interesting definition of `static`. Yes, the calling syntax is what I had in mind. It just feels wrong to pass `this` to a function that should usually be able to access the instance `this` by default with no need to have it passed as argument. This is just bad style and I'd emphasize my recommendation to declare the worker outside of the class to make it clear that it solely depends on the class instance pointed passed as argument via `AfxBeginThread()`. – thomiel Apr 22 '23 at 12:41
  • @thomiel This isn't *"my"* definition. It's the [definition](https://en.cppreference.com/w/cpp/language/static) established by the C++ programming language. If you find that *"interesting"*, you're probably best advised to brush up on your end. Do you have anything of substance to contribute? – IInspectable Apr 22 '23 at 13:04
  • @IInspectable ah, sorry. I was confused by the unformatted code mess above before I sanitized it (don't know why code highlighting still doesn't work). And your confusion about `static` from the preceeding question still echoed in my mind. – thomiel Apr 22 '23 at 15:02
  • You get syntax highlighting if you ask for it, nicely. – IInspectable Apr 22 '23 at 15:08

2 Answers2

0

You obviously can't do

int y= x;   // doesn’t compile

as your thread function is static and has no class instance member variables.

Your

int y= D_ptr->x;    // fails at runtime

Looks OK. I've created a similar code in my test project and it works fine.

Your issue ("fails at runtime") must be elsewhere.

UPDATE:

Here is that ASSERT from MFC\wincore.cpp line 970:

void CWnd::AssertValid() const
{
    ...
    ASSERT((CWnd*)p == this);   // must be us

    // Note: if either of the above asserts fire and you are
    // writing a multithreaded application, it is likely that
    // you have passed a C++ object from one thread to another
    // and have used that object in a way that was not intended.
    // (only simple inline wrapper functions should be used)
    //
    // In general, CWnd objects should be passed by HWND from
    // one thread to another.  The receiving thread can wrap
    // the HWND with a CWnd object by using CWnd::FromHandle.
    //
    // It is dangerous to pass C++ objects from one thread to
    // another, unless the objects are designed to be used in
    // such a manner.

Please note

...and have used that object in a way that was not intended.

The code you posted does NOT use that object in a "bad" way.

As I suggested, the issue is elsewhere. But, as you said, you do need to pass a window handle to the thread, if you want to use it as a window.

Vlad Feinstein
  • 10,960
  • 1
  • 12
  • 27
  • It throws an exception at the failure point, displaying a breakpoint (which I did not set). When I look at the values in the VS debugger, the pointer is valid and d_ptr->y has the correct value (in my real code, it is a string that ONLY comes from the main dialog). If I click on "ignore", the program proceeds and eventually ends properly, (although it skipped some steps - have to see what I forced it to bypass for testing). – DocDJ Apr 20 '23 at 20:42
  • In my example, all 3 vars are in the header. MY real code now has y in the top of my .cpp file. does that make a difference? – DocDJ Apr 20 '23 at 22:04
  • Although a breakpoint exception is raised, if I click on "ignore", it proceeds properly to the end, running the thread and all the processes it starts. If I go to VS>Debug, the option delete all breakpoints is greyed out, so there is SOME kind of runtime problem with that first assignment using D_ptr. Also, I noticed that the functions in the thread MUST put D_ptr-> in front of calls to MessageBox! Strange. – DocDJ Apr 21 '23 at 14:36
  • @DocDJ Re: `It throws an exception at the failure point` - could you please post the code of that failed exception, including the file name? I am still saying that the code you posted shouldn't cause any exceptions... – Vlad Feinstein Apr 21 '23 at 23:35
  • The code is in my previously posted example except that x, y and z are all globals in the .cpp file. The VS debugger pops a window saying debug assertion failed in MFC\wincore.cpp line 970, bu the VS debug window shows all is good. Seems I need to pass a Handle, not a pointer. – DocDJ Apr 22 '23 at 13:15
  • My thread is now working "properly", using handles as required for members and pointers for referenced to data shared between the thread and the main dialog. I knew what I needed to do, but had to do a LOT of searching to find the SYNTAX and statement combinations to make both data and functions work. **Thanks to everyone for their suggestions.** MFC is NOT my favorite programming paradigm, but it DOES get the job done without having to fiddle with the message-loop directly. – DocDJ Apr 24 '23 at 16:25
0

One little code review in advance:

Don't initialize pMexifierDlg in the class declaration. A better place would be to do it in the constructor:

CMexifierDlg* m_pMexifierDlg;  // also prefix members with m_ or something to make clear in the code that it's a class member

void CMexifierDlg::CMexifierDlg()
{
    m_pMexifierDlg = this;
}

But I'd omit the m_pMexifierDlg step completely because this is omnipresent in member function code anyway. Just have it like this:

AfxBeginThread(doit, (LPVOID)this);

So you also can remove the unnecessary myparm assignment.

Having said this, I think it's desirable to have the worker thread running as a separate entity outside the class. For example, the worker won't exit if you close or destroy the dialog. It's always a good idea to separate gui code from your logic code and even if you have one dialog and only one thread at a time that is associated with it somehow, you might run into pitfalls if you do gui things directly from the worker thread. Manipulating atomic variables like ints might work but use CMutex for more complex things like CString. But I really urge you to have a clear protocol for communication between the two threads, as already mentioned in your recent question.

If you want to have the worker thread in a class, create a separate class in addition to CMexifierDlg.

I recommend you have a closer look into instantiating C++ classes, lifetime of the this pointer, and win32/MFC gui thread <-> worker thread communication.

thomiel
  • 2,467
  • 22
  • 37
  • Thasnks to everyone for their help. I got it all working passing "this" to the thread as pParam + using this code inside all thread functions: varaccess = (CMexifierDlg*)pParam; // use this for accesing dialog data members // use following (with pointer notation, for accessing dialog member functions memberaccess = (CMexifierDlg*) CWnd::FromHandle(varaccess->m_hWnd); The thread works asynchronously with the dialog, as it should. – DocDJ Apr 25 '23 at 14:06