01-27-2012 10:11 AM
I am trying to interface to a DLL used for managing a piece of hardware. The DLL is written in C uses _stdcall and the import wizard has successfully hooked up most of it to VIs in a library; many of them simply work as is. I have also played with the Call Library Function Node and think I have mostly got the hang of it. I have successfully run the odd Windows API call (e.g. MessageBox). However, I am a newcomer to Labview. My problem relates to a DLL function which requires a pointer to a block of memory, into which it then processes data from the hardware (as array of UINTs). I want to view this data in Labview, via an array I guess. I think I found everything I need to accomplish this in terms of creating an array and hooking it up to the Call LiBrary Function Node with a pointer input.
I expect it would work IF the memory block was fully filled before the function returned. HOWEVER, this is not how the DLL function works. It spawns a thread which carries on putting data into the memory block after the DLL function returns. Additional functions are available to deal with finding out whether the transfer of data is complete. I assume that this will defeat the data marshalling in Labview, unless there are some special commands for the purpose.
I did think perhaps I should allocate the memory using the Windows API (LocalAlloc with LPTR flag so I get a pointer) and I've written a little VI which allocates and deallocates memory, which seems to work. This way I can deallocate the memory when the transfer is complete. MY PROBLEM is, given the pointer to the memory block as returned by LocallAlloc, how do I get Labview to read the memory into an array of integers.
I'm now a bit stuck and would appreciate any advice on either how to convert the memory block, or on a btter way to approach things.
TIA
Solved! Go to Solution.
01-27-2012 11:04 AM
Someone asked a similar question earlier this week. See if this thread http://forums.ni.com/t5/LabVIEW/How-do-I-get-the-memory-address-of-an-array/m-p/1851549 gives you the information you need.
01-30-2012 03:51 AM
nathan, Thanks for your comment and sorry for the delayed reply - I only have LabView at work currently and the weekend intervened
I quite agree that the problem described in the reference you gave is essentially the same as the one I am trying to solve. I did not find that in my searches, but I guess it is probably not such an uncommon question.
Their solution seems to involve manual allocation of memory, so I guess I got that bit right, only using the Windows API. I guess it would be better to use the Labview Manager functions i.e. DSNewPClr(). I was being a bit dim-witted; I had seen those functions, but the help files introduces them as things to call from external code, rather than from LabView. I should have realised that there was no reason not to treat them like any other DLL, callable from LabView in the usual way.
However, the MoveBlock() part of the solution still looks rather inelegant. I don't actually need to copy any data in the world seen by the DLL code. What I actually need (I think) is to appropriately provoke the implicit marshalling code that Labview must create when a terminal on the CLF node, which actually refers to a pointer, is configured to be a pointer to an output array. If I've understood the world of Labview correctly, I could provoke this behaviour by writing a DLL function with two U32 arguments that simply executes param2=param1, and then plug my pointer into param1 and my ouput array into param2. I just wondered, given that this must have come up before, whether there was an "official" way to do this. My hope was to write code in such a way that it would be obvious what I was doing and why to those who might use it.
Anyway, it can clearly be done, so thanks for your help.
01-30-2012 04:02 PM
Think a bit more about your proposed DLL solution. If the call library function node is like a standard C function call - and it probably is - then you're passing the pointers by value and assigning them to each other will have no effect on the calling function, since the pointers the function sees are only temporary copies for the length of the function call. When the function returns, the input array will still point at the same place where it pointed before the function was called. You could try dealing with handles, but you'll probably get in trouble if you make a handle point at memory that was not originally allocated as a handle.
MoveBlock is the right solution, so far as I've been able to determine from reading a lot of posts on this forum and elsewhere over the years. Arrays in LabVIEW are not simple pointers; they are handles (pointers to pointers) so that the LabVIEW memory manager can relocate them (sometimes necessary when resizing arrays). While there must be some internal mechanism to "lock" a handle, I haven't seen an external interface to it. Without this, there's a risk that LabVIEW would relocate the array while your DLL is still writing to it in the background, which would, of course, be unfortunate.
02-03-2012 04:18 AM
It's quite true that you would have to figure out how to pass the destination pointer by reference. This means using a "Handle" I guess. One of the difficulties is that the Labview documentation is rather unspecific about what happens entering and leaving the function call. Unfortunately the meaning of "Handle" is very context dependent, so the documentation needs to be much more detailed. If you select "Handle" rather than "Array data pointer" the minimum size box disappears implying that Labview is going to assume a pointer to a pointer to a Labview format. One would then have to allow for the size information when allocating the array and adjust the pointer offset approprately when passing a pointer to the function that would fill the memory. If the size of the array was big enough, it might be worth fiddling around with this to avoid the unnecessary copying (and allocation), but I don't have the time now, unless I find better documentation that explains explicity what marshalling takes place for each dialog option.
So I have a working solution, that is basically the same idea as your suggestion, but uses the Windows API. The reason I bother to mention this, is that when I looked at "MoveBlock", "DSNewPtrt" etc more closely, I realised that they are a static C library. That means to use them requires that you have a C development system and the knowledge of how to build DLLs on it. As it happens that doesn't cause me a problem, but it seemed like a sledgehammer in this case because the routines you need are already in kernel32.dll. [Yes this makes it Windows specific, but then you'd need to rebuild your DLLs for each os anyway].
There is no worry about Labview reclaiming the memory used, because it is allocated outside Labview's remit. Conversely, you do have to release it your self after the data has been copied into a Labview array. The only hazard is to remember that the pointer you get from LocalAlloc is just an integer as far as Labview is concerned. When you pass it back into a called sub-routine (e.g. RtlMoveMemory, LocalFree, or indeed the hardware routine that needs the space to fill) you must specify it as a numeric, pass-by-value, even though that means that the function prototype helpfully provided in the Labview dialog doesn't match the function prototype for the sub-routine. I've attached a little picture showing the allocation and copying. One can safely interpose an external routine that puts data in the memory in the line (representing the pointer) that emerges as the return value of LocalAlloc, providing that one must make sure that all the writing is finished by the time you make the call to RtlMoveMemory. If anyone wanted the code, or a picture of the code with routines that put data in the array I guess I could put that up on request.
Thanks for your help nathand
02-03-2012 12:11 PM
@SteveJM wrote:
So I have a working solution, that is basically the same idea as your suggestion, but uses the Windows API. The reason I bother to mention this, is that when I looked at "MoveBlock", "DSNewPtrt" etc more closely, I realised that they are a static C library. That means to use them requires that you have a C development system and the knowledge of how to build DLLs on it. As it happens that doesn't cause me a problem, but it seemed like a sledgehammer in this case because the routines you need are already in kernel32.dll. [Yes this makes it Windows specific, but then you'd need to rebuild your DLLs for each os anyway].
This paragraph is completely inaccurate. Those functions are built into the LabVIEW Run-Time Engine, without which your LabVIEW code will not run. You are guaranteed that if you can run your LabVIEW code, either within the development environment or as a compiled application, those functions are available. No need for a C development system (if it did, don't you think someone would have mentioned it in one of the many posts on this forum about using MoveBlock?).
@SteveJM wrote:
It's quite true that you would have to figure out how to pass the destination pointer by reference. This means using a "Handle" I guess. One of the difficulties is that the Labview documentation is rather unspecific about what happens entering and leaving the function call. Unfortunately the meaning of "Handle" is very context dependent, so the documentation needs to be much more detailed. If you select "Handle" rather than "Array data pointer" the minimum size box disappears implying that Labview is going to assume a pointer to a pointer to a Labview format. One would then have to allow for the size information when allocating the array and adjust the pointer offset approprately when passing a pointer to the function that would fill the memory. If the size of the array was big enough, it might be worth fiddling around with this to avoid the unnecessary copying (and allocation), but I don't have the time now, unless I find better documentation that explains explicity what marshalling takes place for each dialog option.
All the help you need is available. Configure a call library function node to pass by handle, close the configuration dialog box, right-click on the call library function node and choose Create .c File... You'll get a C stub with all the structs defined for you, and you'll see that the LabVIEW array handle includes size information. There's no "marshalling." It's just passing pointers around (and some byte- and word-swapping due to endianness issues). When you pass an array by pointer to array value, you get what you'd expect - LabVIEW dereferences the handle once and passes the array pointer. It's exactly like a C function call. Nothing unusual happens when the call returns (except, again, endianness swapping if needed). That said, it's still a bad idea to do what you want to do with a handle, because LabVIEW is allowed to relocate the array when it likes, and has no way to know that some other function needs that memory block to stay where it is.
02-06-2012 04:55 AM
I started by assuming that the memory managment functions would be available from a DLL and I went and looked for them. After an unsuccessful search I looked at the help-file and the only reference to, for example, DSNewPClr was in a section that started by describing a 4 step process that included making and calling your own dll.
However, taking your word for it that is available in the run-time, I went looking again. After wasting time rummidging around in DLLs found in a directory called "Labview run-time", it occured to me to simply try some guesses for the module name in the Call library Function dialog. Labview.dll does not work, but labview.exe yields all the memory functions and many more. I hope this is helpful to someone.
The tip on using "Create .c file..." is a handy one. It does indeed remove much of the ambiguity. However, "endiness swapping" is exactly what I mean by "marshalling". The details would be handy. I understand your point about reallocation. I do of course need to know when the external code has finished messing with the memory block before I give Labview an opportunity to mess with it.
02-06-2012 12:04 PM
@SteveJM wrote:
However, taking your word for it that is available in the run-time, I went looking again. After wasting time rummidging around in DLLs found in a directory called "Labview run-time", it occured to me to simply try some guesses for the module name in the Call library Function dialog. Labview.dll does not work, but labview.exe yields all the memory functions and many more. I hope this is helpful to someone.
Sorry for any confusion resulting in wasted time, but if you'd read the post to which I linked in my first reply - or other posts on this forum about calling memory manager functions - you'd see that it is sufficient to set the library name to "LabVIEW" no filename extension necessary.