Jump to content

Call Function Library Node Callbacks


ShaunR

Recommended Posts

The CFLN has a page for callbacks.

image.png.892c16a2fba2d2e71a270c74772b6801.png

 

These are useful in certain rare cases but there is very little information available about how to use them,

MgErr and InstanceDataPointer are defined in extcode.h and, it seems, InstanceDataPointer is a pointer passed by LabVIEW but there the information ends.

Does LabVIEW free the InstanceDataPointer? - it is readable in the Abort, Reserve and Unreserve. Are we limited to a pointer sized variable? It's a (void *) so can we resize the memory it points to and, if so, does LabVIEW free that? (unlikely). Who owns that pointer?

Does someone have a working, concrete, example (including C code) of the DLL side of these functions and how to dereference, write to and free the InstanceDataPointer? Does someone have skeleton templates for these functions  that can make life easier for users like me, that know enough C to be dangerous?

Link to comment
15 hours ago, ShaunR said:

Does LabVIEW free the InstanceDataPointer? - it is readable in the Abort, Reserve and Unreserve. Are we limited to a pointer sized variable? It's a (void *) so can we resize the memory it points to and, if so, does LabVIEW free that? (unlikely). Who owns that pointer?

@Rolf Kalbermatter has the answer, as usual:

 

The parameter type is a pointer-to-InstanceDataPtr (i.e. a pointer-to-a-pointer, or a Handle in LabVIEW terms).

LabVIEW owns the handle, you own the data pointer: You allocate the data in Reserve and you can access that same data in Unreserve/Abort. LabVIEW can't free your pointer since it doesn't know the type/size of your data.

// C++ example
#include <ctime>

MgErr reserveCallback(InstanceDataPtr* instanceState) {
    if (*instanceState == nullptr) {
        time_t* start = new time_t;
        *start = time(nullptr);
        *instanceState = start;
    } else {
        // We should never reach this point, unless the InstanceDataPtr was not cleared after the last run
    }

    return 0;
}

MgErr unreserveCallback(InstanceDataPtr* instanceState) {
    time_t  end = time(nullptr);
    time_t* start = static_cast<time_t*>(*instanceState);

    // Calculate how much time has passed between reserving and unreserving
    double elapsedSecs = difftime(end, *start);
    
    // Note: The handle must be explicitly cleared, otherwise the LabVIEW IDE will pass the old pointer
    // to reserveCallback() when the VI is re-run
    delete start;
    *instanceState = nullptr;

    return 0;
}

 

Edited by JKSH
  • Thanks 1
Link to comment

I don't quite have a working example but the logic for allocation and deallocation is pretty much as explained by JKSH already. But that is not where it would be very useful really. What he does is simply calculating the time difference between when the VI hierarchy containing the CLFN was started until it was terminated. Not that useful really. 😀

The usefulness is in the third function AbortCallback() and the actual CLFN function itself.

// Our data structure to manage the asynchronous management
typedef struct
{
   time_t time;
   int state;
   LStrHandle buff;
} MyManagedStruct;

// These are the CLFN Callback functions. You could either have multiple sets of Callback functions each operating on their own data
// structure as InstanceDataPtr for one or more functions or one set for an entire library. Using the same data structure for all. In
// the latter case these functions will need to be a bit smarter to determine differences for different functions or function sets
// based on extra info in the data structure but it is a lot easier to manage, since you don't have different Callback functions for
// different CLFNs. 
MgErr LibXYZReserve(InstanceDataPtr *data)
{
    // LabVIEW wants us to initialize our instance data pointer. If everything fits into a pointer
    // we could just use it, otherwise we allocate a memory buffer and assign its pointer to
    // the instanceDataPtr
    MyManagedStruct *myData;
    if (!*data)
    {
        // We got a NULL pointer, allocate our struct. This should be the standard unless the VI was run before and we forgot to
        // assign the Unreserve function or didn't deallocate or clear the InstanceDataPtr in there.
        *data = (InstanceDataPtr)malloc(sizeof(MyManagedStruct));
        if (!*data)
            return mFullErr;
        memset(*data, 0, sizeof(MyManagedStruct));
    }
    myData =  (MyManagedStruct*)*data;
    myData->time = time(NULL);
	myData->state = Idle;
    return noErr;
}

MgErr LibXYZUnreserve(InstanceDataPtr *data)
{
    // LabVIEW wants us to deallocate a instance data pointer
    if (*data)
    {
        MyManagedStruct *myData = (MyManagedStruct*)*data;

        // We could check if there is still something active and signal to abort and wait for it
        // to have aborted but it's better to do that in the Abort callback
        .......
          
        // Deallocate all resources
        if (myData->buff)
            DSDisposeHandle(myData->buff);
        // Deallocate our memory buffer and assign NULL to the InstanceDataPointer
        free(*data)
        *data = NULL;
    }
    return noErr;
}

MgErr LibXYZAbort(InstanceDataPtr *data)
{
    // LabVIEW wants us to abort a pending operation
    if (*data)
    {
        MyManagedStruct *myData = (MyManagedStruct*)*data;

        // In a real application we do probably want to first check that there is actually something to abort and
        // if so signal an abort and then wait for the function to actually have aborted.
        // This here is very simple and not fully thread safe. Better would be to use an Event or Notifier
        // or whatever or at least use atomic memory access functions with semaphores or similar.
        myData->state = Abort;
    }
    return noErr;
}

// This is the actual function that is called by the Call Library Node 
MgErr LibXYZBlockingFunc1(........, InstanceDataPtr *data)
{
    if (*data)
    {
        MyManagedStruct *myData = (MyManagedStruct*)*data;
		myData->state = Running;
        
        // keep looping until abort is set to true
        while (myData->state != Abort)
        {

             if (LongOperationFinished(myData))
                 break;
        }
		myData->state = Idle;
   }
   else
   {
       // Shouldn't happen but maybe we can operate synchronous and risk locking the
       // machine when the user tries to abort us.
   }
   return noErr;
}

When you now configure a CLFN, you can assign an extra parameter as InstanceDataPtr. This terminal will be greyed out as you can not connect anything to it on the diagram. But LabVIEW will pass it the InstanceDataPtr that you have created in the ReserveCallback() function configuration for that CLFN. And each CLFN on a diagram has its own InstanceDataPtr that is only valid for that specific CLFN. And if your VI is set reentrant LabVIEW will maintain an InstanceDataPtr per CLFN per reentrant instance!

  • Thanks 1
Link to comment

Awesome.

I was struggling with C malarky.

Since it's hard for me to use extcode.h, I usually find the types and paste them in the header of whatever I'm using. However. I thought the InstanceDataPtr was a void * so I was using prototypes like this:

Quote

LibXYZUnreserve(void *data)

Makes sense, right? WRONG!

InstanceDataPtr is a typedef which means you can do things like

*data = (InstanceDataPtr)malloc(sizeof(MyManagedStruct));

Can't do that with a untypedef'd void * so the compiler was bailing with:

Quote

error: 'void*' is not a pointer-to-object type

That caused me to try all sorts of pointer voodoo which would only work for a pointer-sized variable since:

Quote

The parameter type is a pointer-to-InstanceDataPtr (i.e. a pointer-to-a-pointer, or a Handle in LabVIEW terms).

Looking at your replies it became obvious you were able to do something my compiler was complaining about and It was unlikely to be the compiler.

This is why I do LabVIEW. lol

Link to comment
4 hours ago, ShaunR said:

Awesome.

I was struggling with C malarky.

Since it's hard for me to use extcode.h, I usually find the types and paste them in the header of whatever I'm using. However. I thought the InstanceDataPtr was a void * so I was using prototypes like this:

Makes sense, right? WRONG!

Your problem is that the correct definition for those functions in terms of basic datatypes would be:

MgErr (*FunctionName)(void* *data);

This is a reference to a pointer, which makes all of the difference. 

A little more clearly written as: 

MgErr (*FunctionName)(void **data);

 

Edited by Rolf Kalbermatter
  • Thanks 1
Link to comment
5 hours ago, Rolf Kalbermatter said:

Your problem is that the correct definition for those functions in terms of basic datatypes would be:

MgErr (*FunctionName)(void* *data);

This is a reference to a pointer, which makes all of the difference. 

A little more clearly written as: 

MgErr (*FunctionName)(void **data);

 

Indeed.

It's all working great now.

Thanks all for the help. When I get 5 mins I'll put together a working demo project and post it here.

  • Thanks 1
Link to comment

Oh boy. This got complicated quickly :lol:

Most modes of operation are per node (including UI) except run as clone which seems to be per thread. I'm not sure how that interacts with execution systems and execution systems across multiple VI's ATM.

Running in the UI thread we obviously need "message pumping" for abort as per the dialogue that pops up. I presume this is referring to the the windows messaging to the form but may be wrong. I roughly know how to do this to normal windows forms and register user messages but without the main LabVIEW window refnum, I'm not sure how that would work.

Anyone know what that looks like? Does anyone have a short example?

Link to comment
On 9/25/2021 at 11:06 PM, ShaunR said:

What's the scope of the InstanceDataPtr when run in User Thread?

Does it behave like a LabVIEW-wide global pointer? VI-wide pointer (execution threads etc)? Or does LabVIEW still assign it a pointer local to the node?

The scope is supposedly still CLFN local but I never tested that. The InstanceDataPtr is tied to the CLFN instance and that is dependent on the VI instance. As long as a VI is not reentrant it exists exactly once in the whole LabVIEW hierarchy no matter if it runs in the UI thread or another thread. And each CLFN in the VI has its own InstanceDataPtr. If a VI is reentrant things get more hairy as each CLFN gets for each VI instance its own InstanceDataPtr. And if you have reentrant VIs inside reentrant VIs that instantiation quickly can spiral out of reach of human grasp. 🙂

Think of the InstanceDataPtr as an implicit Feedback Node or Shift Register on the diagram. One of them for every Call Library Node. That's basically exactly how it works in reality.

Obviously if you run a blocking VI in the UI thread you run into a problem as LabVIEW doesn't get any chance to run its message loop anymore which executes in the same thread. And Windows considers an application that doesn't poll the message queue with GetMessage() for a certain time as being unresponsive. But calling GetMessage() yourself is certainly going to mess up things as you now steal events from LabVIEW and PeekMessage() is only for a short time a solution. So if you absolutely have to call the CLFN in the UI thread (why?) you will have to program it differently. You must let the CLFN return to the LabVIEW diagram periodically and do the looping on the diagram instead of inside the C function. You still can use the InstanceDataPtr to maintain local state information for the looping but the Abort mechanisme won't be very important as LabVIEW gets a chance to abort your VI everytime the CLFN returns to the diagram. The nice thing about using an InstanceDataPtr for this instead of simply a pointer sized integer maintained yourself in a shift register in the loop is, that LabVIEW will still call Unreserve() (if configured of course) when terminating the hierarchy so you get a chance to deallocate anything you allocated in there. With your own pointer in a shift register it gets much more complicated to make the deallocation properly when the program is aborted. 

 

 

Edited by Rolf Kalbermatter
Link to comment
On 9/25/2021 at 10:03 PM, ShaunR said:

Hmm. The returns from those functions seem to get eaten. Passing a mgErr back doesn't seem to do much. I would have thought they would appear at the error out of the CLFN. It'd be quite nice to report a "Canceled by user" error when Abort is called.

The Callbacks are executed during initialization of the environment (the instant you hit the run button) or when the hierarchy ends its execution. So returning those errors as part of the Call Library Node may be not really very intuitive. As the Reserve() seems to be called at start and not load, it's also not easy to cause a broken diagram. So yes I can see that these errors are kind of difficult to fit into a consistent model to be reported to the user.

Edited by Rolf Kalbermatter
Link to comment
5 hours ago, Rolf Kalbermatter said:

So if you absolutely have to call the CLFN in the UI thread (why?)

Two reasons.

  1.  Fully document the behaviour and operation as it allows us to do it..
  2. Freeing resources that must be in the root loop (fonts or HDC's , for example).

 

5 hours ago, Rolf Kalbermatter said:

You must let the CLFN return to the LabVIEW diagram periodically and do the looping on the diagram instead of inside the C function.

If you are referring to message pass-throughs where the message is allowed to continue on it's journey, it's what's required for form inspector tools so I've done that before (long time ago though). If that's what is required then it's fairly straight forward (famous last words :yes:)  The thing I'm not sure about is the hooking of the invisible LabVIEW window rather than the VI FP.

Edited by ShaunR
Link to comment
23 minutes ago, ShaunR said:

Two reasons.

  1.  Fully document the behaviour and operation as it allows us to do it..
  2. Freeing resources that must be in the root loop (fonts or HDC's , for example).

 

If you are referring to message pass-throughs where the message is allowed to continue on it's journey, it's what's required for form inspector tools so I've done that before (long time ago though). If that's what is required then it's fairly straight forward (famous last words :yes:)  The thing I'm not sure about is the hooking of the invisible LabVIEW window rather than the VI FP.

You completely got that wrong! What I meant was instead of entering the Call Library Node and locking in the DLL function until your functionality is finished, and polling in the C function periodcally the abort condition in the InstanceDataPtr, you would do the looping on the VI diagram and reenter the CLFN until it returns a status done value, after which you exit the loop. Now you do not need to actually configure the Abort() function and could even not configure the Reserve() function but still pass the InstanceDataPtr to your CLFN function. On entry you check that InstanceDataPtr to be non null and allocate a new one if it is null, and then you store state information for your call in there. Then you start your operation and periodically check for its completion. If it is not completed you still terminate the function but return a not completed status to the diagram, which will cause the diagram loop to continue looping. When the user now aborts the VI hierarchy LabVIEW will be able to abort your VI when your CLFN returns with the complete or not complete status. So you don't need the InstanceDataPtr to be able to abort your CLFN function asynchronously. But you still get the benefit of the Unreserve() function which LabVIEW will call with the InstanceDataPtr. In there you check that the pointer is not null and deallocate all the resources in there and then the pointer itself.

It's almost equivalent if you would use a shift register in your diagram loop to store a pointer in there that you pass into the function and after the CLFN call put back into the shift register on each iteration, except that when the user aborts the VI hierarchy you do not get a chance to call another VI to deallocate that pointer. With the InstanceDataPtr the Unreserve() function can do that cleanup and avoid lingering resources, aka memory leaks.

You could do that for both UI threaded CLFNs as well as any threaded CLFNs, for the first it is mandatory to avoid your function blocking the LabVIEW UI thread, for the second its optional but still would work too.

 

Edited by Rolf Kalbermatter
Link to comment

 

 

8 minutes ago, Rolf Kalbermatter said:

What I meant was instead of entering the Call Library Node and locking in the DLL function until your functionality is finished, and polling in the C function periodcally the abort condition in the InstanceDataPtr, you would do the looping on the VI diagram and reenter the CLFN until it returns a status done value, after which you exit the loop.

I do this for TCP but what if I create a new HDC in the DLL node? Ala New(HDC), Draw(HDC), Close(HDC).

The HDC must be accessed in the Root-loop so the Abort callback for New(HDC) would free it when Abort was pressed. Otherwise Close may never get called to free it.

Link to comment
51 minutes ago, ShaunR said:

 

 

I do this for TCP but what if I create a new HDC in the DLL node? Ala New(HDC), Draw(HDC), Close(HDC).

The HDC must be accessed in the Root-loop so the Abort callback for New(HDC) would free it when Abort was pressed. Otherwise Close may never get called to free it.

I somehow missed the fact that you now work on HDCs. HDCs do not explicitly need to be used in the root loop, BUT they need to be used in the same thread that created/retrieved the HDC. And since in LabVIEW only the UI thread is guaranteed to always be the same thread during the lifetime of the process, you might indeed have to put the CLFN into the UI thread. Also I'm pretty sure that Reserve(), Unreserve() and Abort are all called in the context of the UI thread too.

But what I'm not getting is where your problem is with this. I believe that the Unreserve() function is always called even if Abort() was called too, but that would have to be tested. In essence it changes nothing though. If you need to call the CLFN in the UI thread, you need to make sure that it does not block inside the C function for a long time, or Windows will consider your LabVIEW app to be unresponsive. And maybe even more important, the LabVIEW UI won't be handled either so you can't press the Abort button in the toolbar either.

Link to comment
1 hour ago, Rolf Kalbermatter said:

But what I'm not getting is where your problem is with this. I believe that the Unreserve() function is always called even if Abort() was called too,

Abort doesn't get actioned until the function returns since (as you rightly said) the callbacks run in the root loop. The function blocks the Abort callback until it returns. At that point, both abort and unreserve are actioned. So you cannot set an abort flag in your function when Abort is pressed.

You can see this in the example if you set the nodes to UI thread and is why you need the message pump (I presume).

Edited by ShaunR
Link to comment
3 hours ago, ShaunR said:

Abort doesn't get actioned until the function returns since (as you rightly said) the callbacks run in the root loop. The function blocks the Abort callback until it returns. At that point, both abort and unreserve are actioned. So you cannot set an abort flag in your function when Abort is pressed.

You can see this in the example if you set the nodes to UI thread and is why you need the message pump (I presume).

Well, I do understand all that and that is what I have tried to explain to you in my previous two posts, how to work around that. If you for some reason need to execute the CLFN in the UI thread you can NOT use the Abort() callback anymore. Instead you need to do something like this:

1623083658_TestCLFNCallback.png.ef920e4c2fa120124ca7f1b644e2a0b7.png

Basically you move the polling loop from inside the C function into the LabVIEW diagram and just call the function to do its work and check if it needs to be polled again or is now finished. If you put a small wait in the diagram or not depends on the nature of what your C function does. If it is mostly polling some (external) resource you should put a delay on the diagram, if you do really beefy computation in the function you may rather want to spend as much as possible in that function itself (but regularly return to give LabVIEW a chance to abort your VI).

The C code might then look something like this:

typedef enum
{
	Init,
    Execute,
    Finished,
} StateEnum;

typedef struct
{
    int state;
    int numm;
} MyInstanceDataRec, *MyInstanceDataPtr;


MgErr MyReserve(MyInstanceDataPtr *data)
{
    // Don't specifically allocate a memory buffer here. If it is already allocated just initialize it
    if (*data)
    {
        (*data)->state = Init;
        (*data)->num = 0;
    }
    return noErr;
}

MgErr MyUnreserve(MyInstanceDataPtr *data)
{
    if (*data)
    {
        DSDisposePtr(*data);
        *data = NULL;
    }
    return noErr;
}

MgErr MyPollingFunc(int32_t someNum, uInt8_t *finished, MyInstanceDataPtr *data)
{
    if (!*data)
    {
        *data = (MyInstanceDataPtr)DSNewPClr(sizeof(MyInstanceDataRec));
        (*data)->state = Execute;
    }
    else if ((*data)->state != Execute)
    {
        (*data)->state = Execute;
        (*data)->numm = 0;
    }

    // No looping inside our C function and neither should we call functions in here that can block for long periods. The idea is to do what is
    // necessary in small chunks, check if we need to be executed again to do the next chunk or if we are finished and return the according status.
    (*data)->numm++;
    *finished = (*data)->numm >= someNum;
    if (*finished)
        (*data)->state = Finished;
    else
        usleep(10000);
    return noErr;
}

You could almost achieve the same if you would pass a pointer sized integer into the CLFN instead of an InstanceDataPtr, and maintain that integer in a shift register of the loop. However if the user aborts your VI hierarchy, this pointer is left lingering in the shift register and might never get deallocated. Not a big issue for a small buffer like this but still not neat.

And yes this works equally well for CLFNs that can run in any thread, but it isn't necessary there.

And of course: No reentrant VIs for this! You can not have a reentrant VI execute a CLFN set to run in the UI thread!

 

Edited by Rolf Kalbermatter
Link to comment
12 minutes ago, Rolf Kalbermatter said:

Well, I do understand all that and that is what I have tried to explain to you in my previous two posts, how to work around that. If you for some reason need to execute the CLFN in the UI thread you can NOT use the Abort() callback anymore. Instead you need to do something like this:

1623083658_TestCLFNCallback.png.ef920e4c2fa120124ca7f1b644e2a0b7.png

Basically you move the polling loop from inside the C function into the LabVIEW diagram and just call the function to do its work and check if it needs to be polled again or is now finished. If you put a small wait in the diagram or not depends on the nature of what your C function does. If it is mostly polling some (external) resource you should put a delay on the diagram, if you do really beefy computation in the function you may rather want to spend as much as possible in that function itself (but regularly return to give LabVIEW a chance to abort your VI).

The C code might then look something like this:

typedef enum
{
	Init,
    Execute,
    Finished,
} StateEnum;

typedef struct
{
    int state;
    int num;
} MyInstanceDataRec, *MyInstanceDataPtr;


MgErr MyReserve(MyInstanceDataPtr *data)
{
    // Don't specifically allocate a memory buffer here. If it is already allocated just initialize it
    if (*data)
    {
        (*data)->state = Init;
        (*data)->num = 0;
    }
    return noErr;
}

MgErr MyUnreserve(MyInstanceDataPtr *data)
{
    if (*data)
    {
        DSDisposePtr(*data);
        *data = NULL;
    }
    return noErr;
}

MgErr MyPollingFunc(int32_t someNum, uInt8_t *finished, MyInstanceDataPtr *data)
{
    if (!*data)
    {
        *data = (MyInstanceDataPtr)DSNewPClr(sizeof(MyInstanceDataRec));
        (*data)->state = Execute;
    }
    else if ((*data)->state != Execxute)
    {
        (*data)->state = Execute;
    }

    // No looping inside our C function and neither should we call functions in here that can block for long periods. The idea is to do what is
    // necessary in small chunks, check if we need to be executed again to do the next chunk or if we are finished and return the according status.
    (*data)->numm++;
    *finished = (*data)->numm >= someNum;
    if (*finished)
        (*data)->state = Finished;
    else
        usleep(10000);
    return noErr;
}

You could almost achieve the same if you would pass a pointer sized integer into the CLFN instead of an InstanceDataPtr, and maintain that integer in a shift register of the loop. However if the user aborts your VI hierarchy, this pointer is left lingering in the shift register and might never get deallocated. Not a big issue for a small buffer like this but still not neat.

And yes this works equally well for CLFNs that can run in any thread, but it isn't necessary there.

 

Yes. I understand and, as I said, I've used that in TCP.

However. That's the user solving our problem and puts an onus on the user that I believe we can alleviate. There is a reason that NI specifically name a message pump for the UI thread.

Basically I think that

27 minutes ago, Rolf Kalbermatter said:

you can NOT use the Abort() callback anymore

is only half a story.

Link to comment
55 minutes ago, ShaunR said:

Yes. I understand and, as I said, I've used that in TCP.

However. That's the user solving our problem and puts an onus on the user that I believe we can alleviate. There is a reason that NI specifically name a message pump for the UI thread.

Basically I think that

is only half a story.

It might be, however I'm not aware of a MessagePump() exported function in the LabVIEW kernel. It may exist but without the according header file to be able to call it correctly it's a pretty hopeless endeavor.  It definitely wasn't exported in LabVIEW versions until around 2009, I stopped trying to analyze what LabVIEW might export on secret goodies around that time.

Besides this is not leaving the work to the user. This loop is somewhere inside a subVI of your library. Can the user change it like that? Yes of course but there are funnier ways to shoot in your own feet! 😝

If someone thinks he knows better than me and wants to go and mess with such a subVI, it's his business, but don't come to me and wine if the PC then blows up into pieces! 😀

I'm not quite sure to which statement you refer with the NI reference. Technically every Win32 executable has somewhere pretty much this code, which should be called from the main thread of the process, the same that is created by the OS when launching the process and which is used to execute WinMain()

int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, 
    LPSTR lpszCmdLine, int nCmdShow) 
{ 
    MSG msg;
    BOOL bRet; 
    WNDCLASS wc; 
    UNREFERENCED_PARAMETER(lpszCmdLine); 
 
    // Register the window class for the main window. 
 
    if (!hPrevInstance) 
    { 
        wc.style = 0; 
        wc.lpfnWndProc = (WNDPROC) WndProc; 
        wc.cbClsExtra = 0; 
        wc.cbWndExtra = 0; 
        wc.hInstance = hInstance; 
        wc.hIcon = LoadIcon((HINSTANCE) NULL, 
            IDI_APPLICATION); 
        wc.hCursor = LoadCursor((HINSTANCE) NULL, 
            IDC_ARROW); 
        wc.hbrBackground = GetStockObject(WHITE_BRUSH); 
        wc.lpszMenuName =  "MainMenu"; 
        wc.lpszClassName = "MainWndClass"; 
 
        if (!RegisterClass(&wc)) 
            return FALSE; 
    } 
 
    hinst = hInstance;  // save instance handle 
 
    // Create the main window. 
 
    hwndMain = CreateWindow("MainWndClass", "Sample", 
        WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 
        CW_USEDEFAULT, CW_USEDEFAULT, (HWND) NULL, 
        (HMENU) NULL, hinst, (LPVOID) NULL); 
 
    // If the main window cannot be created, terminate 
    // the application. 
 
    if (!hwndMain) 
        return FALSE; 
 
    // Show the window and paint its contents. 
 
    ShowWindow(hwndMain, nCmdShow); 
    UpdateWindow(hwndMain); 
 
    // Start the message loop. 
 
    while( (bRet = GetMessage( &msg, NULL, 0, 0 )) != 0)
    { 
        if (bRet == -1)
        {
            // handle the error and possibly exit
        }
        else
        {
            TranslateMessage(&msg); 
            DispatchMessage(&msg); 
        }
    } 
 
    // Return the exit code to the system. 
 
    return msg.wParam; 
} 

The message loop is in the while() statement and while LabVIEWs message loop is a little more complex than this, it is still principally the same. This is also called the root loop in LabVIEW terms, because the Mac message loop works similar but a little different and was often referred to as root loop. MacOS was not inherently multithreading until MacOS X, but in OS 7 and later an application could make use of extensions to implement some sort of multithreading but it was not multithreading like the pthread model or the Win32 threading.

And this while loop is also often referred to as message pump, and even Win32 applications need to keep this loop running or Windows will consider the application not responding and will eventually make most people open Task Manager to kill the process. This message pump is also where the (D)COM Marshalling hooks into and makes that marshalling fail if a process doesn't "pump" the messages anymore.

And the window created here is the root window in LabVIEW. It is always hidden but its WndProc is the root message dispatcher that receives all the Windows messages that are sent to processes rather than individual windows.   

 

Edited by Rolf Kalbermatter
Link to comment
37 minutes ago, Rolf Kalbermatter said:

It might be, however I'm not aware of a MessagePump() exported function in the LabVIEW kernel. It may exist but without the according header file to be able to call it correctly it's a pretty hopeless endeavor.  It definitely wasn't exported in LabVIEW versions until around 2009, I stopped trying to analyze what LabVIEW might export on secret goodies around that time.

Besides this is not leaving the work to the user. This loop is somewhere inside a subVI of your library. Can the user change it like that? Yes of course but there are funnier ways to shoot in your own feet! 😝

If someone thinks he knows better than me and wants to go and mess with such a subVI, it's his business, but don't come to me and wine if the PC then blows up into pieces! 😀

I'm not quite sure to which statement you refer with the NI reference. Technically every Win32 executable has somewhere pretty much this code, which should be called from the main thread of the process, the same that is created by the OS when launching the process and which is used to execute WinMain()

int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, 
    LPSTR lpszCmdLine, int nCmdShow) 
{ 
    MSG msg;
    BOOL bRet; 
    WNDCLASS wc; 
    UNREFERENCED_PARAMETER(lpszCmdLine); 
 
    // Register the window class for the main window. 
 
    if (!hPrevInstance) 
    { 
        wc.style = 0; 
        wc.lpfnWndProc = (WNDPROC) WndProc; 
        wc.cbClsExtra = 0; 
        wc.cbWndExtra = 0; 
        wc.hInstance = hInstance; 
        wc.hIcon = LoadIcon((HINSTANCE) NULL, 
            IDI_APPLICATION); 
        wc.hCursor = LoadCursor((HINSTANCE) NULL, 
            IDC_ARROW); 
        wc.hbrBackground = GetStockObject(WHITE_BRUSH); 
        wc.lpszMenuName =  "MainMenu"; 
        wc.lpszClassName = "MainWndClass"; 
 
        if (!RegisterClass(&wc)) 
            return FALSE; 
    } 
 
    hinst = hInstance;  // save instance handle 
 
    // Create the main window. 
 
    hwndMain = CreateWindow("MainWndClass", "Sample", 
        WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 
        CW_USEDEFAULT, CW_USEDEFAULT, (HWND) NULL, 
        (HMENU) NULL, hinst, (LPVOID) NULL); 
 
    // If the main window cannot be created, terminate 
    // the application. 
 
    if (!hwndMain) 
        return FALSE; 
 
    // Show the window and paint its contents. 
 
    ShowWindow(hwndMain, nCmdShow); 
    UpdateWindow(hwndMain); 
 
    // Start the message loop. 
 
    while( (bRet = GetMessage( &msg, NULL, 0, 0 )) != 0)
    { 
        if (bRet == -1)
        {
            // handle the error and possibly exit
        }
        else
        {
            TranslateMessage(&msg); 
            DispatchMessage(&msg); 
        }
    } 
 
    // Return the exit code to the system. 
 
    return msg.wParam; 
} 

The message loop is in the while() statement and while LabVIEWs message loop is a little more complex than this, it is still principally the same. This is also called the root loop in LabVIEW terms, because the Mac message loop works similar but a little different and was often referred to as root loop. MacOS was not inherently multithreading until MacOS X, but in OS 7 and later an application could make use of extensions to implement some sort of multithreading but it was not multithreading like the pthread model or the Win32 threading.

And this while loop is also often referred to as message pump, and even Win32 applications need to keep this loop running or Windows will consider the application not responding and will eventually make most people open Task Manager to kill the process. This message pump is also where the (D)COM Marshalling hooks into and makes that marshalling fail if a process doesn't "pump" the messages anymore.

 

Yes. That look familiar with windows forms.

I've just been playing a bit more. I think there are messages posted to the thread from LabVIEW. PeekMessage will return messages targeted towards the thread (HWND = -1) and I thought I saw a 12 (WM_QUIT) from the LabVIEW Pid. Difficult to catch messages when you don't know what's being sent and Monte Carlo-ing in the debugger. :lol:

I'll probably end up just logging them all to a file, but that will have to be next weekend. It'd be nice if it were just a case of peeking and then terminating on a WM_QUIT or our own ABORT

Link to comment
28 minutes ago, ShaunR said:

Yes. That look familiar with windows forms.

I've just been playing a bit more. I think there are messages posted to the thread from LabVIEW. PeekMessage will return messages targeted towards the thread (HWND = -1) and I thought I saw a 12 (WM_QUIT) from the LabVIEW Pid. Difficult to catch messages when you don't know what's being sent and Monte Carlo-ing in the debugger. :lol:

I'll probably end up just logging them all to a file, but that will have to be next weekend. It'd be nice if it were just a case of peeking and then terminating on a WM_QUIT or our own ABORT

Can you tell me why you want to call PeekMessage() and not simply do the diagram looping to let LabVIEW do the proper abort handling and what else?

Link to comment
1 minute ago, Rolf Kalbermatter said:

Can you tell me why you want to call PeekMessage() and not simply do the diagram looping to let LabVIEW do the proper abort handling and what else?

Why have a work-around when you can have a solution?

8 hours ago, ShaunR said:

Fully document the behaviour and operation as it allows us to do it..

 

Link to comment
12 minutes ago, ShaunR said:

Why have a work-around when you can have a solution?

Calling PeekMessage() is the solution? I would have to disagree! It's more of a workaround than a solution and clearly only Windows only. Trying to do this for Linux or MacOS is going to bring you in very deep waters! Working within the realms of LabVIEW whenever you can is much more of a solution than trying to mess with the message loop.

I'm not sure if you ever used the Windows message queue library, which hooks into the WndProc dispatching for a particular window. It's notorious to only work for certain messages and not give you some of the messages you are interested in since Windows is not dispatching it to that window but some other windows such as the hidden root window. And threading comes into the play at every corner. Windows is quite particular about what sort of functions in its window handling it will allow to be executed in a different thread than the one which created that window. Generally there are many limitations and while it may sometimes seem to work at first, it often breaks apart after making seemingly small modifications to the handling.  The whole window handling and GDI system originates from a time where Windows used cooperative multitasking. While they tried to make it more concurrent threading tolerant when moving to the Win32 model, a lot of that was through semaphores that can potentially even lock up your code if you are not very careful. And some functions simply don't do much if they don't match the thread affinity of the object they are supposed to operate on.

Edited by Rolf Kalbermatter
Link to comment

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.