12-10-2010 04:18 PM
I'm writing an application that communicates with another application via SendMessage. Responses are returned using the WM_COPYDATA message which I interecpt with a callback installed as follows:
static HWND ClientWindowHandle = 0;
GetPanelAttribute (MainPanelHandle, ATTR_SYSTEM_WINDOW_HANDLE, (intptr_t *)&ClientWindowHandle);
InstallWinMsgCallback (MainPanelHandle, WM_COPYDATA, ProcessWmCopyDataMessage, VAL_MODE_INTERCEPT, NULL, (intptr_t *)&ClientWindowHandle);
In some cases my applicaiton needs to send a message to the other app and wait for the repsonse to come back. I've implemented this using a flag to indicate when the response has arrived, something like this:
void GetInfoFromOtherApp(void)
{
// prepare message data
....
SendMessage(ServerWindowHandle, WM_COPYDATA, (WPARAM)ClientWindowHandle, (LPARAM)&MessageData);
WaitingForResponse = TRUE;
while (WaitingForResponse)
{
ProcessSystemEvents();
};
// process response
}
int CVICALLBACK ProcessWmCopyDataMessage (int panelHandle, int message, unsigned int* wParam, unsigned int* lParam, void* callbackData)
{
// save data in buffer
.....
WaitingForResponse = FALSE;
}
This works fine as long as I initiate the call to GetInfoFromOtherApp from a thread separate from the callback thread (e.g., on a UI event, timer event, etc.). However, there are some cases where I need to send more messages once a reposnse arrives, something like this:
int CVICALLBACK ProcessWmCopyDataMessage (int panelHandle, int
message, unsigned int* wParam, unsigned int* lParam, void* callbackData)
{
// save data in buffer
.....
WaitingForResponse = FALSE;
GetInfoFromOtherApp();
}
My problem is that if I call GetInfoFromOtherApp while I'm in the callback thread, my while loop doesn't work because the callback is never invoked. Put another way, apparently the callback can't be called reentrantly.
I'm not that familar with setting up separate threads in CVI, and I'm not even sure if that is the right way to handle this problem or if it is OK to call a Windows callback handler reentrantly. I've tried to think of other ways to handle this, llike setting a timer in the first response and having the timer place the second call to GetInfroFromOtherApp, but that's pretty ugly (especially since I need to call GetInfroFromOtherApp about 30 times to get all the data I need).
I'd appreciate any suggestions on ways to attack this problem.
12-10-2010 05:46 PM
Hey Tony -
A couple quick comments:
There doesn't seem to be anything about your setup that requires multiple threads. All it looks like you need is a little different logic concerning when you exit the loop and when you call GetInfo... from within the WM_COPYDATA handler.
NickB
National Instruments
12-13-2010 09:48 AM
NickB,
I haven't created any additional threads. I just wasn't sure if the MW_COPYDATA callback was handled in the same thread as my own UI events.
The example is over simplified. There is no infinite recursion. What's really going on is that I ping the other app during my init. Once I get the response through the WM_COPYDATA message, I need to get a bunch of data from the other app. So the process looks something like this:
1) Send ping message
2) Start UI
3) On ping response call the GetInfo function multiple times
Each message sent to the other app includes some ID info specifying what data to get and the same ID is returned with the response so I know what data was recevied (I didn't show all this is the example code). My WM_COPYDATA callback copies the data to temporary storage and sets the flag so that the GetInfo function can then process that data. Once that data is processed, another call is issued to GetInfo. This continues until all the info has been received, at which point we return from the initial WM_COPYDATA callback that started this while process.
To restate my problem, here is what I've found:
1) If I call my GetInfo function from a button event, then the WM_COPYDATA callback is called while I'm sitting in my while loop and everything works perfectly.
2) If I call my GetInfo function from the WM_COPYDATA callback then the callback does not get called while I'm sitting in my while loop and my program freezes.
I verified the problem in two ways. First I placed a breakpoint in the WM_COPYDATA callback. The breakpoint is hit once during the ping response but never again. Once it got stuck in the while loop wiating for the second callback, I paused the code and then set the execution point to the end of the function that processes the ping response (i.e., the end of the WM_COPYDATA callback). As soon as that function exited, I hit the callback breakpoint.
12-13-2010 11:19 AM - edited 12-13-2010 11:21 AM
@tonyg 614 wrote:
2) If I call my GetInfo function from the WM_COPYDATA callback then the callback does not get called while I'm sitting in my while loop and my program freezes.
I'm sure someone from NI will correct me if I'm wrong, but I'm pretty sure that the CVI callback mechanism is such that it cannot be called from within itself - it must terminate before it can be re-invoked. You need to re-arrange your code accordingly.
JR
12-13-2010 11:45 AM
JR,
That's the conclusion I've been hoping to avoid. The one thing I don't get is that if everything in my app is handled in the same thread, then why does it work if I call my GetInfo function from a buton callback but not when I call it from the WM_COPYDATA callback? If both callbacks are handled in the same thread, then neither way should work or both should work.
12-13-2010 01:58 PM - edited 12-13-2010 01:59 PM
After looking into this a little bit further (and refreshing myself on the details of BroadcastSystemMessage), I think I might be able to explain what's happening in such as way that you can resolove it in your code. The short answer is that you don't need the while loop. BroadcastSystemMessage will not return until the server has sent you a message back, and you have in turn processed that message.
Lets break that down a little bit.
BroadcastSystemMessage *sends* (not posts) a message to all applications. We know that the SendMessage function is serial - meaning it does not return until the thread owning the window the message is sent to processes the message. This means that BroadcastSystemMessage will not return until all applications either respond to the message or timeout (depending on the flags you pass for first parameter).
When the server receives the message you've broadcast, it calls SendMessage (..., WM_COPYDATA, ..., ...) back to your window. Meanwhile, your client is still inside of BroadcastSystemMessage, which is likely (who knows how Microsoft actually implements it...) inside SendMessage. When you read the documentation for SendMessage, you see that nonqueued messages (sent messages) will be processed by the implementation of SendMessage, which means that your WM_COPYDATA handler will actually be called by the internals of BroadcastSystemMessage. Thus, there is no need to wait for a response from the server - this is done implicitly by BroadcastSystemMessage.
Hopefully you will be able to see this by stepping through your code. I'm hopeful that understanding how this works will allow you to architect your code in a way that solves the issue for you.
NickB
National Instruments
12-14-2010 08:22 AM
As I've thought a little more about this, I've realized it may be unwise to depend upon the server responding to your broadcast message synchronously. It is entirely possible that the server spins off a new thread to respond to your message, which would necessitate you waiting upon a response. In light of this, I would recommend setting the Waiting... flag to true *before* you broadcast the system message. This is because, as I mentioned in my previous post, it's entirely possible that the value of the flag is set to false while you're still inside the BroadcastSystemMessage call.
NickB
National Instruments
12-14-2010 01:02 PM
NickB,
Thanks for all your feedback. In the end I implemented a one-shot timer which I enable after I get the ping response. The timer event then handles all the init. That's working well (so far).
Thanks again!
TonyG