Link to home
Start Free TrialLog in
Avatar of Dj_Fx8
Dj_Fx8

asked on

DLL Callback

Hi

I am using a DLL which has a callback function. Now as the callback has to be static I can't access the other class members, so as in other callbacks I used I had got a pointer to my class in the callback function as follows
CfvWorkBench* CFunctions::GetWorkBenchWnd()
{
  CMainFrame* pMainFrm = (CMainFrame*)AfxGetMainWnd() ;
  CSplitterWnd* pSplitter = &pMainFrm->m_wndSplitter;
  ASSERT_POINTER(pSplitter, CSplitterWnd);
  CfvWorkBench *pWorkBenchView = (CfvWorkBench*)pSplitter->GetPane(1,1);  //FAILURE HERE
  ASSERT_POINTER(pWorkBenchView, CfvWorkBench);
  return pWorkBenchView;
}
The problem is when I try this with the DLL callback I get a debug assert failure on the line marked,  this traces  from the Wincore.cpp at the following code
  // should also be in the permanent or temporary handle map
void CWnd::AssertValid() const
  ........
  .......
  CHandleMap* pMap = afxMapHWND();
  ASSERT(pMap != NULL);

Now I think this is somehow due to the callback being in its own thread, is this right and if so how do I work arround it, if not anyone any ideas on this. Note the CFunctions::GetWorkBenchWnd() function is used in other places in my code and works ok.

Now the other way I looked at this was
The DLL has a function to set up the callback like this
HSYNC WINAPI BASS_ChannelSetSync(
    DWORD handle,
    DWORD type,
    QWORD param,
    SYNCPROC *proc,
    DWORD user
);

SYNCPROC
void CALLBACK YourSyncProc(
    HSYNC handle,
    DWORD channel,
    DWORD data
    DWORD user
);
The info on this DLL says
user         User instance data to pass to the callback function
So I thought I could pass "this" as the user, but user is a DWORD so it wouldn't compile

Steven

Avatar of DanRollins
DanRollins
Flag of United States of America image

Your DLL needs to have
      AFX_MANAGE_STATE(AfxGetStaticModuleState());
protect each call.  If you are exporting an object, then each exposed objeect method needs to have that as the first line of the function.  If you are exporting simple C-style fns, then it should be the first line of each of those, as well.

Part of what that does is to set up the processing the per-thread handle mapping that goes on behind the scenes.

This may not be the only problem (your definition of a callback function does not match what I consider to be a callback) but it is something to try.

-- Dan
Avatar of AlexFM
AlexFM

Any pointer may be casted to DWORD (because it is really DWORD). You can pass pass "this" as the user casting it to DWORD.
However, passing pointers to MFC objects to Dll may not work because of heap or thread problems. If you have such problems, pass windows handles instead of class pointers. Having window handle you can talk with any window using PostMessage and SendMessage.
Avatar of Dj_Fx8

ASKER

Hi

Thanks for your replies only I'm still lost,
Dan I assume that  AFX_MANAGE_STATE(AfxGetStaticModuleState()); needs to be in the DLL, if so I did not write the DLL and have no access to add this, if not where and how do I use  AFX_MANAGE_STATE(AfxGetStaticModuleState()); also
>>>>your definition of a callback function does not match what I consider to be a callback
why?
Alex I had tried (DWORD)this as the user param but as you said it didn't work either
>>>>pass windows handles instead of class pointers. Having window handle you can talk with any window using PostMessage and SendMessage, could you perhaps explain this a little more.

I'm very new to this and greatly appreciate all the help.

Steven
Try putting it at the top of that function.
Avatar of Dj_Fx8

ASKER

Hi Dan
I tried that but still the same problem, also the auther of the DLL says I can pass (DWORD)this as the user, so I'm really lost, however I have managed to pass this to the callback and use it to postmessage, I suppose along the lines Alex said, but still curious as to what the cause / solution is.

Steven
In most callback scenarios, it is permitted for the user to pass a "cookie" (typically a DWORD value) into the DLL.  THen when it makes the callback, the DLL passes that value back to you.  Is that the case in this situation (perhaps that is what the
    DWORD user
value is in your call to BASS_ChannelSetSync().

If so, then when your callback is called, the DLL will pass that back to you.  Perhaps that is what the
      DWORD user
is in the YourSyncProc() fn.   If that is all correct, then do it this way:

SYNCPROC void CALLBACK MySyncProc( HSYNC handle, DWORD channel, DWORD data, DWORD user )
{
          CMyObject* pThis= (CMyObject*)user;
          pMyThis->DoAnyMemberFnInTheCMyObjectClassDef( nAnyParam );
}

CMyObject::SetupTheCallback()
{
        BASS_ChannelSetSync( myHandle, myType, param, MySyncProc, (DWORD)this);
);

The callback goes to a global function, but that global fn uses the passed in 'user' parameter as  a pointer to an object.  The call:

          pMyThis->AnyFn( parm1, parm2,...);

then 'gets into' the object and so can access all members.  It is possible to do this in another way -- using a static member -- but that accompishes exactly the same thing and just makes it a little more complicated to explain.

-- Dan
Avatar of Dj_Fx8

ASKER

Hi Dan,
The first parts are just as I thought and had tried. Now when you say
>>>It is possible to do this in another way -- using a static member  
I think thats maybe my problem I was declaring
SYNCPROC void CALLBACK MySyncProc( HSYNC handle, DWORD channel, DWORD data, DWORD user )
as static, could this be the problem as you say
>>>The callback goes to a global function
If so I'm not sure how to make it golbal, I always thought callbacks had to be static.

I've still a lot to learn :-))

Steven
What I meant by 'global' was just it is not a member function of any class object.  Declare it just as shown, (without a leading 'MyObject::' in the declaration).

You can also declare it 'static' to make it module-local, but again, that is not relevant to making it work and that meaning of 'static' is quoite different from what it means when you declare a 'static member function'  You are probably confusing the two concepts.

-- Dan
Avatar of Dj_Fx8

ASKER

Ok I think I'm with you so far, so looking at my org prob, in my callback I got a pointer to CMyObject (pMyThis) and used it call a function(MySomething) in CMyObject just as you said, now this seemed to work fine until the following line in my function MySomething
 UpdatData(FALSE);
if fails the assert in wincore.cpp at the following

CWnd::AssertValid()
..
..
// should also be in the permanent or temporary handle map
CHandleMap* pMap = afxMapHWND();
ASSERT(pMap != NULL); >>>>Here

The UpdateDate is called to refresh the screen with newly received data from the callback

I can't understand why this would be cause when I look at the pointer pMyThis it does point the CMyObject, whats happened to my pMap

ASKER CERTIFIED SOLUTION
Avatar of DanRollins
DanRollins
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Also try this:
         AFX_MANAGE_STATE( AfxGetAppModuleState() );  << *App* ModuleState
(just guessing, never used it)
-- Dan
Avatar of Dj_Fx8

ASKER

Hi

I tried both
AFX_MANAGE_STATE( AfxGetStaticModuleState() );  
and
AFX_MANAGE_STATE( AfxGetAppModuleState() );
and neither of the two prevented the problem so I tried
::SetDlgItemText( m_hDlg, IDD_SomeEditBox, sSomeStringFromTheDLL );
and it worked ok, so would I be right in assuming this is some sort of bug in MFC.
Just one last brief question, I now can process by callback by either using SendMessage or by the method we have been working on, is there any advantage of using one or the other methods
It is not really a bug in MFC, but if you want to call it that, then that's OK.

>>I now can process by callback by either using SendMessage or by the method we have been working on,

I don't know what you mean.  But here is the rule that will avoid those errors:

In your callback handler, access all CWnd-derived objects via the HWND, not the CWnd*  Thus, for instance, use

          ::SetDlgItemText( m_hWnd, nID, sText)
rather than
          pwnd->SetDlgItemText( nID, sText);  // will fail
or  
         SetDlgItemText( nID, sText);  // will fail... implying the CWnd* associated with 'this' object.

You can also use ::SendMessage( hWnd, ...) and ::PostMessage( hWnd,...) should the need arise.

-- Dan
Avatar of Dj_Fx8

ASKER

Sorry I didn't explaine that to well

>>I now can process by callback by either using SendMessage or by the method we have been working on,

In the callback function
Method 1

  CNetRadio* pNetRadio = (CNetRadio*)user;
  pNetRadio->SendMessage(UWM_METASYNC, data, 0);

Method 2

  CNetRadio* pNetRadio = (CNetRadio*)user;
  pNetRadio->DoMeta((char*)data); //call function

Is it beter to use method 1 and send/post a msg or method 2 by just  calling a function.

Anyway I can't thank you enough for all your help and here's the well earned pts

Thanks
Steven

PS I'm just writing a new question, for a new prob :-( which I be posting soon, you might like to keep an eye out for it, I'm sure you'd have no probs sorting it :-)
Thanks for the points and the grade :)

As to Method #1 or #2, I can't say for certain which is better.  Both equate to a direct call into the CWnd-derived object -- which could cause problems.  As far as I know, the DLL could be calling from a different thread.  Also, certain resource-related issues could crop up (think of this: if you look up something in a StringTable, should you look in the DLL or the EXE?  -- as a callback handler it is physically 'in' the EXE, but the module that is calling it is the DLL).   If I had to chose, I'd say SendMessage, since there may be more context-insulation there.

I think that PostMessage would be perfectly safe, since the message would wait there until the real thread, in the context of the main app, could process it.

In general, I would recommend doing a bare minimum of work in the callback... just update some status variables in memory and let a timer in the main window handle U/I work.  Or post custom message like "UM_CheckForAnyStatusUpdatesNeeded"  to tell the main window to update the U/I when it gets a chance.

-- Dan
Avatar of Dj_Fx8

ASKER

I would have given you an A++ but, no suce thing here. Your last post has given me a few guidelines to work along.

Cheers

Steven