Jump to content

Calling arbitrary code straight from the diagram


dadreamer

Recommended Posts

11 hours ago, Taylorh140 said:

No this is definitely cool. It’s cool to know that there is a way to keep the data in the same scope. Now all you need is a (c compiler/assembler) written in labview along with prebuild actions and the world would be your oyster. 

That's a bit ambitious! 😀

I would rather think something in the sense of the Python ctypes package to allow arbitrary function calls to DLLs including callbacks and such. We just need to find a method that does the opposite for this: calling a VI as C function pointer. 😀

Link to comment
14 hours ago, ShaunR said:

hmmmm. ;)

No not really. I mean something quite different.

Given a VI create a sort of function wrapper around it that works as a C function pointer. For that we would need something like

MgErr CallVIFunc(VIDSRef viRef, int32 numInParams,  VIParams *inParams, int32 numOutParams, VIParams *outParams);

with both parameters something like an array of

typedef struct
{
  LStrHandle controlName;
  int16 *typedesc;
  void *data;
} VIParams;

That way one could do a C function wrapper in assembly code that then converts its C parameters into LabVIEW parameters and then calls the VI as function.

These are not actual functions that exist but just something I came up with. I'm sure something similar actually exists!

Edited by Rolf Kalbermatter
Link to comment
On 9/29/2020 at 12:54 AM, Rolf Kalbermatter said:

These are not actual functions that exist but just something I came up with. I'm sure something similar actually exists!

All I could find about this is just these two internal functions:

  • CallVIFromDll
  • NCGRunVirtualInstrument

For the first one I was able to find .NET prototype only:

CallVIFromDll.Invoke(Int32 epIndex, IntPtr lvClient, IntPtr entryPointDataSpace)

I'm kind of unsure, how it could be used for the mentioned task. It looks like it doesn't accept the VI parameters. And what do these arguments mean exactly?..

As to the second one, it doesn't accept the VI parameters as well and must be called in UI Thread only. The prototype is as follows:

int32_t NCGRunVirtualInstrument(uint32_t VIRef);

I did a limited testing and it appears to work. The VI is launched with the parameters on its FP and no panel is shown. We could prepare the parameters before the call with Control Value.Set method. Not very flexible solution, as I think.

 

I saw your post from 2016, where you said that you have found some functions suitable for the task. Do you remember the details?

Link to comment
7 hours ago, dadreamer said:

All I could find about this is just these two internal functions:

  • CallVIFromDll
  • NCGRunVirtualInstrument

For the first one I was able to find .NET prototype only:


CallVIFromDll.Invoke(Int32 epIndex, IntPtr lvClient, IntPtr entryPointDataSpace)

I'm kind of unsure, how it could be used for the mentioned task. It looks like it doesn't accept the VI parameters. And what do these arguments mean exactly?..

As to the second one, it doesn't accept the VI parameters as well and must be called in UI Thread only. The prototype is as follows:


int32_t NCGRunVirtualInstrument(uint32_t VIRef);

I did a limited testing and it appears to work. The VI is launched with the parameters on its FP and no panel is shown. We could prepare the parameters before the call with Control Value.Set method. Not very flexible solution, as I think.

 

I saw your post from 2016, where you said that you have found some functions suitable for the task. Do you remember the details?

What about VIRefPrepNativeCall and VIRefFinishNativeCall? They sound interesting but maybe a red herring.

Link to comment
16 hours ago, ShaunR said:

What about VIRefPrepNativeCall and VIRefFinishNativeCall? They sound interesting but maybe a red herring.

I'm afraid, we can't use them, because they don't actually run a VI, but "prepare" its state for a run. I guess it's used only for LabVIEW Adapters to be called later from TestStand. VIRefPrepNativeCall requires a VI to be in reserved state, otherwise it returns 1027 error. If we mark the VI as reserved with StatVIRefReserve, then it all goes OK, but the target VI is not executed. Something like this:

2020-10-01_14-29-48.jpg.f0c35e037c676e45c5dd3b3addb869e3.jpg

int32_t StatVIRefReserve(uintptr_t viDS, uint32_t *pVIRef, int32_t unknown, int32_t setUnset);
int32_t VIRefPrepNativeCall(uint32_t viRef, uintptr_t *pVIDS);
int32_t VIRefFinishNativeCall(uint32_t viRef);
void  StatVIRefRelease(uint32_t viRef);

There must be something between VIRefPrepNativeCall and VIRefFinishNativeCall like NCGRunVirtualInstrument, but with the ability to pass the parameters. Of course, we could use WriteDCOTransferData before the call to set our parameters, but the BD / assembly code becomes kinda cumbersome then.

Link to comment

I think CallInstrument() is more promising although the documenttion I found seems to indicate that it is an old function that is superseded by something called C Interface in LabVIEW. But I haven't found any information about that new interface.

/* Legacy C function access to call a VI. Newer code should consider upgrading to use the C Interface to LabVIEW. */

/*  Flags to influence window behavior when calling a VI synchronously via CallInstrument* functions.

	The following flags offer a refinement to how the former 'modal' input to
	CallInstrument* functions works. For compatibility, a value of TRUE still
	maps to a standard modal VI. Injecting the kCI_AppModalWindow flag will allow
	the VI to stack above any Dlg*-based (C-based LV) windows that may be open as well.
	Use kCI_AppModalWindow with caution! Dlg*-based dialogs run at root loop, and VIs
	that run as app modal windows might be subject to deadlocking the UI. */
const int32 kCI_DefaultWindow	= 0L;		///< in CallInstrument*, display VI's window using VI's default window styles
const int32 kCI_ModalWindow		= 1L<<0;	///< in CallInstrument*, display VI's window as modal
const int32 kCI_AppModalWindow	= 1L<<1;	///< in CallInstrument*, display VI's window as 'application modal'

/* Legacy C function access to call a VI. Newer code should consider upgrading to use the C Interface to LabVIEW. */
/*
	@param viPath fully qualified path to the VI
	@param windowFlags flags that influence how the VIs window will be shown
	@param nInputs number of input parameters to send to the VI
	@param nOutputs number of output parameters to read from the VI
	@return error code describing whether the VI call succeeded

	The actual parameters follow nOutputs and are specified by a combination of
	parameter Name (PStr), type (int16*) and data pointer.

	@example
		CallInstrument(vi, kCI_ModalWindow, 2, 1,
		               "\07Param 1", int16_Type_descriptor_in1, &p1,
		               "\07Param 2", int16_Type_descriptor_in2, &p2,
		               "\06Result", int16_Type_descriptor_res, &res);


	@note This function does not allow the caller to specify a LabVIEW context in which to load
	      and run the VI. Use a newer API (such as the C Interface to LV) to do so.

	@note Valid values for windowFlags are:
	        kCI_DefaultWindow  (0L)
	        kCI_ModalWindow    (1L<<0)
	        kCI_AppModalWindow (1L<<1)
*/
TH_SINGLE_UI EXTERNC MgErr _FUNCC CallInstrument(Path viPath, int32 windowFlags, int32 nInputs, int32 nOutputs, ...);

 

  • Thanks 1
Link to comment

Nice catch, Rolf! It works and I am able to pass the input/output parameters now.

2020-10-05_13-00-41.jpg.588b4620274cd690c90b033cd23dabb2.jpg

RunVI.vi

SubVI.vi

But it appears that CallInstrument wants to be ran in UI Thread only, else LabVIEW goes big crash. It calls the VI synchronously, waiting until it finishes. That makes me think that this function is not the best idea to use as a callback, because when the user will be interacting with some GUI elements or the program will be running some property/invoke nodes, the callback VI will be waiting UI Thread to become idle, thus we could experience the delay between the events from our callback (or even loss of earlier ones?). It would be much better to run the VI in any thread somehow, but CallInstrument doesn't support that. That's why I decided not to adapt the asm samples for that function for now. Maybe I would be lucky enough to find some other options or overcome the threading issues somehow. Or end on PostLVUserEvent until some better ideas come to mind. 🙂

On 10/3/2020 at 8:52 PM, Rolf Kalbermatter said:

But I haven't found any information about that new interface.

It's likely has to do with these two functions: GetCIntVIServerFuncs and GetCInterfaceFunctionTable. They return some tables, filled with the function pointers. There are InitLVClient / InitLVClient2, UninitLVClient / UninitLVClientNoDelay, WaitLVClientReady, WaitLVShuttingDown and a whole bunch of unnamed functions (safety precautions?). It'll take a serious effort to study how those work.

Edited by dadreamer
added some thoughts
Link to comment
55 minutes ago, dadreamer said:

Nice catch, Rolf! It works and I am able to pass the input/output parameters now.

2020-10-05_13-00-41.jpg.588b4620274cd690c90b033cd23dabb2.jpg

RunVI.vi 10.95 kB · 0 downloads

SubVI.vi 12.43 kB · 0 downloads

But it appears that CallInstrument wants to be ran in UI Thread only, else LabVIEW goes big crash. It calls the VI synchronously, waiting until it finishes. That makes me think that this function is not the best idea to use as a callback, because when the user will be interacting with some GUI elements or the program will be running some property/invoke nodes, the callback VI will be waiting UI Thread to become idle, thus we could experience the delay between the events from our callback (or even loss of earlier ones?). It would be much better to run the VI in any thread somehow, but CallInstrument doesn't support that. That's why I decided not to adapt the asm samples for that function for now. Maybe I would be lucky enough to find some other options or overcome the threading issues somehow. Or end on PostLVUserEvent until some better ideas come to mind. 🙂

I'm aware of the limitation with this function. It not only wants to run in the UI thread but also doesn't allow to specify the context it should be running under, so will likely always use the main application context (which could be fine really, as each project has its own context). And that with the missing context is logical since this function existed before LabVIEW 8.0 which introduced (at least publically) application contexts.

I think the mentioned C interface has to do with the function GetCInterfaceFunctionTable(), exported since around LabVIEW 8.0, but that is a big black hole. From what I could see, it returns a pointer to a function pointer table containing all kinds of functions. Some of them used to be exported as external functions in LabVIEW too. But without a header file declaring this structure and the actual functions it exports, it is totally hopeless to think one could use it. As most of the functions aren't really exported from LabVIEW in other ways they also didnt retain the function name, like all those other functions in the official export table. But even with a name it would be very tedious to find out what parameters a function takes and how to call it especially if it needs to be called in conjunction with other functions in there.

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

There are InitLVClient / InitLVClient2, UninitLVClient / UninitLVClientNoDelay, WaitLVClientReady, WaitLVShuttingDown and a whole bunch of unnamed functions (safety precautions?). It'll take a serious effort to study how those work.

Not really safety precautions. Most C(++) compilers will strip by default all symbols from linked non-debug code, unless these symbols are needed for certain puposes like function export tables. While these functions also return a pointer to some sort of export table, they are not official export tables, just as virtual tables in C++ aren't really export tables. The name is unneeded as far as the compiler is concerned, so they get all stripped. This has certain anti reverse engineering reasons as someone distributing a release version isn't usually interested in letting its users reverse engineer their software  (just check your license agreement you entered into when installing LabVIEW 😀) but the main reason is really that these symbols simply blow up the executable image size for no useful reason and it's an easy thing to do by the linker. The functions with retained symbol names in there are usually functions that are also exported in the official export table.

GetCIntVIServerFuncs() existed before LabVIEW 8.0 and was/is mostly related to functions needed by various VI Server interfaces. The first version of VI server was a very small set of exported functions that got called by a CIN. This was then changed into the fully diagram accessible VI server interface as we know it now around LabVIEW 5. Sometimes the LabVIEW compiler needs to create callbacks into the LabVIEW kernel. Initially this was done through direct calls of exported functions but that was changed for several reasons to call a special internal export interface. Hiding things was likely more a byproduct than the main reason for this, making this interface more uniform among the different platforms was likely more important.

The C Interface supposedly took this idea further and here hiding LabVIEW internas might have been part of the decision. But because this privately exported function table is highly inflexible and can only be amended to in subsequent LabVIEW versions but never modified without creating serious trouble with version compatiblity, I think it's not something they would want to use for everything. The advantage is that you do not really need to use dynamic link functionality for this except for the one function to get at this interface, so there is one simple function that will use platform specific dynamic loading and everything else is simply a function pointer call into a table at a specific offset.

A COM like interface would be much more flexible in terms of version compatibility and NI uses that in some parts even across platforms despite that real COM isn't supported on non-Windows platforms, but it is also a lot more complicated to create and use even when you program everything in C++.

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
Unfortunately, your content contains terms that we do not allow. Please edit your content to remove the highlighted words below.
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.