LabVIEW

cancel
Showing results for 
Search instead for 
Did you mean: 

Call library function node with array of clusters using array data pointer

Solved!
Go to solution

Hello all.

 

I am writing a LabVIEW wrapper for an existing DLL function.

 

The function has, as one of its parameters, an array of structs.  The struct is very simple, containing two integers.  I am using the call library function node to access it.

 

In Labview I created an array of clusters, where the cluster has two 32-bit integers as its members.  So far, so good.

 

Now I have to pass this in to the Call Library Function Node.  Here I am running into trouble.

 

I have used The topic in LAVA and The topic in the knowledge base as my primary sources of information, though I have read a bunch of forum topics on the subject too.

 

I do understand that I could write a new function which takes as a parameter a struct with the size as the first member and an array as the second, and I might just do this and have it call the regular function, but I was hoping to do it more simply.

 

According to the C file which LabVIEW generates for me from the CLFN when I choose "Adapt to Type" and "Array Data Pointer", the prototype it is expecting is:

 

int32_t myFunc(uint32_t handle, uint16_t channel,
int32_t FIFOnumber, void data[], int32_t numWords, int32_t *actualLoaded,
int32_t *actualStartIndex);

 

And the prototype of the function in my DLL is

int borland_dll myFunc(DWORD handle, usint channel,
int FIFOnumber, struct mStruct *data, int numWords, int *actualLoaded, int *actualStartIndex);

 

This looks like a match to me, but it doesn't work (I get garbage in data).  From the topic in LAVA referenced above, I understood that it would work.  It does not.

 

If I cast data to the pointer-to-pointer I get when I generate c code by wiring my struct to a CIN and generating, then I seem to get what I expect. But this seems to work when I choose "pointers to handles" too, and I would expect array data pointer to give a different result.

 

Is there any way to get this to work directly, or will I have to create a wrapper?  (I am currently using LabVIEW 2011, but we have customers using 2009 and 2012, if not other versions as well).

 

Thank you.

Batya

0 Kudos
Message 1 of 21
(5,956 Views)

There is an automatic wizard available, but that requires the header file to be available.

 

Create drivers from DLL.png

 

Can you share the DLL and your code, to see what exactly you're trying to do.


I am not allergic to Kudos, in fact I love Kudos.

 Make your LabVIEW experience more CONVENIENT.


Message 2 of 21
(5,945 Views)

Are you sure you preallocate the array with as many elements as you tell the function to fill into it?

Rolf Kalbermatter
My Blog
Message 3 of 21
(5,939 Views)

I initially created the wrapper VI with the import wizard.  With a function parameter which is a pointer, the wizard doesn't know if it is an input parameter, an output parameter, an array, a single value......so I have to modify a bunch of the parameters afterwords, and this was one of them.  The wizard correctly created the cluster, but did not make it an array of clusters (how, indeed, could it know?).

 

As for preallocating the array, this array is an input parameter; that is, I initialize the array in LabVIEW and the DLL function only reads it.

 

Thank you.

 

Batya

0 Kudos
Message 4 of 21
(5,927 Views)

Well, the Adapt to Type, Array Data Pointer setting seems to work perfectly as expected.

Rolf Kalbermatter
My Blog
Message 5 of 21
(5,922 Views)

OK, here is more detailed information.

 

I have attached the VI.

This is the code from the  "C" file created by right-clicking the CLN and creating a "C" file. 

 

When the parameter in the CLN is set to "array data pointer":

/* Call Library source file */

#include "extcode.h"

int32_t Load_Transmit_FIFO_RTx(uint32_t handle, uint16_t channel, 
	int32_t FIFOnumber, void data[], int32_t numWords, int32_t *actualLoaded, 
	int32_t *actualStartIndex);

int32_t Load_Transmit_FIFO_RTx(uint32_t handle, uint16_t channel, 
	int32_t FIFOnumber, void data[], int32_t numWords, int32_t *actualLoaded, 
	int32_t *actualStartIndex)
{

	/* Insert code here */

}

 When the parameter is "pointers to handles":

/* Call Library source file */

#include "extcode.h"

/* lv_prolog.h and lv_epilog.h set up the correct alignment for LabVIEW data. */
#include "lv_prolog.h"

/* Typedefs */

typedef struct {
	int32_t control;
	int32_t data;
	} TD2;

typedef struct {
	int32_t dimSize;
	TD2 data[1];
	} TD1;
typedef TD1 **TD1Hdl;

#include "lv_epilog.h"

int32_t Load_Transmit_FIFO_RTx(uint32_t handle, uint16_t channel, 
	int32_t FIFOnumber, TD1Hdl *data, int32_t numWords, int32_t *actualLoaded, 
	int32_t *actualStartIndex);

int32_t Load_Transmit_FIFO_RTx(uint32_t handle, uint16_t channel, 
	int32_t FIFOnumber, TD1Hdl *data, int32_t numWords, int32_t *actualLoaded, 
	int32_t *actualStartIndex)
{

	/* Insert code here */

}

 When the parameter is set to "handles by value":

/* Call Library source file */

#include "extcode.h"

/* lv_prolog.h and lv_epilog.h set up the correct alignment for LabVIEW data. */
#include "lv_prolog.h"

/* Typedefs */

typedef struct {
	int32_t control;
	int32_t data;
	} TD2;

typedef struct {
	int32_t dimSize;
	TD2 data[1];
	} TD1;
typedef TD1 **TD1Hdl;

#include "lv_epilog.h"

int32_t Load_Transmit_FIFO_RTx(uint32_t handle, uint16_t channel, 
	int32_t FIFOnumber, TD1Hdl *data, int32_t numWords, int32_t *actualLoaded, 
	int32_t *actualStartIndex);

int32_t Load_Transmit_FIFO_RTx(uint32_t handle, uint16_t channel, 
	int32_t FIFOnumber, TD1Hdl *data, int32_t numWords, int32_t *actualLoaded, 
	int32_t *actualStartIndex)
{

	/* Insert code here */

}

 

As to the DLL function, it is a bit more complicated than I explained above, in the current case.  My VI calls the function by this name in one DLL, and that DLL loads a DLL and calls a function (with the same name) in the second DLL, which does the work. (Thanks Rolfk, for helping me with that one some time back!)

 

Here is the code in the first ("dispatcher") DLL:

int borland_dll Load_Transmit_FIFO_RTx(DWORD handle, usint  channel, int FIFOnumber, struct FIFO_DATA_CONTROL *data, int numWords, int *actualLoaded, int *actualStartIndex)
{
	t_DispatchTable *pDispatchTable = (t_DispatchTable *) handle;
	int				retStat = 0;

	retStat = mCheckDispatchTable(pDispatchTable);
	if (retStat < 0)
		return retStat;
	if (pDispatchTable->pLoad_Transmit_FIFO_RTx == NULL)
		return edispatchercantfindfunction;

	return pDispatchTable->pLoad_Transmit_FIFO_RTx(pDispatchTable->handlertx, channel, FIFOnumber, data, numWords, actualLoaded, actualStartIndex);
}

borland_dll is just "__declspec(dllexport)"

 

The current code in the DLL that does the work is:

// TEMP

typedef struct {
	int control;
	int data;
} TD2;

typedef struct {
	int dimSize;
	TD2 data[1];
} TD1;
typedef TD1 **TD1Hdl;
// END TEMP

int borland_dll Load_Transmit_FIFO_RTx(int handlertx, usint  channel, int FIFOnumber, struct FIFO_DATA_CONTROL *data, int numWords, int *actualLoaded, int *actualStartIndex){
	struct TRANSMIT_FIFO *ptxFIFO;  //pointer to transmit FIFO structure
	usint *pFIFOlist;  //pointer to array of FIFO pointers to FIFO structures
	int FIFOentry, numLoaded;
	usint *lclData;
	usint nextEntryToTransmit;

	// TEMP
	FILE *pFile;
	int i;
	TD1**  ppTD = (TD1**) data;
	TD1 *pTD = *ppTD;
	
	pFile = fopen("LoadFIFOLog.txt", "w");
	fprintf(pFile, "Starting Load FIFO with %d data words, data pointer 0x%x, with the following data&colon; \n", numWords, data);
	for (i = 0; i < numWords; i++) {
		fprintf(pFile, "%d: control--0x%x, data--0x%x \n", i, data[i].control, data[i].data);
	}
	fflush(pFile);


	
	fprintf(pFile, "OK, using CIN generated structures: dimSize %d, with the following data&colon; \n", pTD->dimSize);
	for (i = 0; i < numWords; i++) {
		fprintf(pFile, "%d: control--0x%x, data--0x%x \n", i, pTD->data[i].control, pTD->data[i].data);
	}
	fflush(pFile);
	

	// END TEMP

	if ((handlertx) <0 || (handlertx >= NUMCARDS)) return ebadhandle;
	if (cardrtx[handlertx].allocated != 1) return ebadhandle;

	
	pFIFOlist = (usint *) (cardrtx[handlertx].segaddr + cardrtx[handlertx].glob->dpchn[channel].tr_stk_ptr);
	pFIFOlist += FIFOnumber;
	ptxFIFO = (struct TRANSMIT_FIFO *)(cardrtx[handlertx].segaddr + *pFIFOlist);
	//use local copy of ptxFIFO->nextEntryToTransmit to simplify algorithm
	nextEntryToTransmit = ptxFIFO->nextEntryToTransmit;
	//on entering this routine nextEntryToLoad is set to the entry following the last entry loaded
	//this is what we need to load now unless it's at the end of the FIFO in which case we need to wrap around
	if ( ptxFIFO->nextEntryToLoad >= ptxFIFO->numEntries)
		*actualStartIndex = 0;
	else
		*actualStartIndex = ptxFIFO->nextEntryToLoad;

	//if nextEntryToLoad points to the last entry in the FIFO and nextEntryToTransmit points to the first, the FIFO is full
	//also if nextEntryToLoad == nextEntryToTransmit the FIFO is full and we exit without loading anything
	if ((   (( ptxFIFO->nextEntryToLoad >= ptxFIFO->numEntries) && (nextEntryToTransmit == 0)) || 
		( ptxFIFO->nextEntryToLoad == nextEntryToTransmit)) && (ptxFIFO->nextEntryToLoad != INITIAL_ENTRY)){
		*actualLoaded = 0; //FIFO is full already, we can't add anything
		return 0;  //this is not a failure, we just have nothing to do, this is indicated in actualLoaded
	}
	numLoaded = 0;
	lclData = (usint *)data;  //must use 16 bit writes to the module
	//conditions are dealt with inside the for loop rather than in the for statement itself
	for (FIFOentry = *actualStartIndex; ; FIFOentry++) {
		//if we reached the end of the FIFO
		//if the module is about to transmit the first element of the FIFO, the FIFO is full and we're done
		//OR if the module is about to transmit the element we're about to fill in, we're done - the
		//exception is if this is the first element we're filling in which means the FIFO is empty
		if ((( FIFOentry >= ptxFIFO->numEntries) && (nextEntryToTransmit == 0))  ||
		   ((FIFOentry == nextEntryToTransmit) && (FIFOentry != *actualStartIndex) )){
			*actualLoaded = numLoaded;
			//set nextEntryToLoad to the end of the FIFO, we'll set it to the beginning next time
			//this allows us to distinguish between full and empty: nextEntryToLoad == nextEntryToTransmit means empty
			ptxFIFO->nextEntryToLoad = FIFOentry;
			return 0;
		}
		//we reached the end but can continue loading from the top of the FIFO
		if ( FIFOentry >= ptxFIFO->numEntries) 
			FIFOentry = 0;
		//load the control word
		ptxFIFO->FifoData[FIFOentry * 3] = *lclData++;
		//skip the high of the control word, the module only has a 16 bit field for control
		lclData++;
		//now put in the data
		ptxFIFO->FifoData[(FIFOentry * 3) + 2] = *lclData++;
		ptxFIFO->FifoData[(FIFOentry * 3) + 1] = *lclData++;
		numLoaded++;
		//we're done because we loaded everything the user asked for
		if (numLoaded >= numWords) {
			*actualLoaded = numLoaded;
			ptxFIFO->nextEntryToLoad = FIFOentry+1;
			return 0;
		}
	}
	//if we reached here, we're done because the FIFO is full
	*actualLoaded = numLoaded;
	ptxFIFO->nextEntryToLoad = FIFOentry;

	fclose (pFile);
	return 0;
}

 As you can see, I added a temporary diagnostic with the structures that were created in the "Handles by value" case, and print out the data.  I see what is expected, whichever of the options I pick in the CLN!  

 

I understood (from the information in the two links I mentioned in my original post, and from the name of the option itself) that "array data pointer" should pass the array of data itself, without the dimSize field.  But that does not seem to be what is happening.

 

Batya

 

 

 

 

0 Kudos
Message 6 of 21
(5,921 Views)

Now, since I have control over the dispatcher DLL (I am the author and maintainer at the moment), though I can't change the target DLL (the one that does the work), I have one option to make this work.

 

I could change the function in the dispatcher DLL to take the struct with the dimSize, and then pass the data pointer to the target DLL.

 

However, I would rather get it working directly, or at least understand why it doesn't work, as I work on a lot of these DLL wrapper projects and not all of them use two DLLs.

 

Thank you.

Batya

0 Kudos
Message 7 of 21
(5,920 Views)

Hmm, you have quite a bit of cruft in there that tries to cast the array data pointer back into a LabVIEW array handle and even work on that data in this section. This of course will access invalid memory as you are working on a data structure that is not allocated like this in memory.

 

	fprintf(pFile, "OK, using CIN generated structures: dimSize %d, with the following data&colon; \n", pTD->dimSize);
	for (i = 0; i < numWords; i++) {
		fprintf(pFile, "%d: control--0x%x, data--0x%x \n", i, pTD->data[i].control, pTD->data[i].data);
	}
	fflush(pFile);

 

But does this part produce valid data in the log file? (it should)

 

	pFile = fopen("LoadFIFOLog.txt", "w");
	fprintf(pFile, "Starting Load FIFO with %d data words, data pointer 0x%x, with the following data&colon; \n", numWords, data);
	for (i = 0; i < numWords; i++) {
		fprintf(pFile, "%d: control--0x%x, data--0x%x \n", i, data[i].control, data[i].data);
	}
	fflush(pFile);

 

Also you do not show the declaration of FIFO_DATA_CONTROL but I would hope it to be like

 

typedef struct FIFO_DATA_CONTROL {
	int32_t control;
	int32_t data;
};

 

Look at my example. I didn't really input the array, but instead output it from the C function (which is generally the more tricky operation as if something is going wrong there, you will very quickly get access violations of some sort). Also since your DLL function does not seem to modify the data in the array it would be sweet to indicate to LabVIEW that fact by checking the "Constant" checkbox in the Call Library Node for that parameter, as this can allow LabVIEW to optimize memory usage for this array.

 

 

Rolf Kalbermatter
My Blog
0 Kudos
Message 8 of 21
(5,915 Views)

@rolfk wrote:

Hmm, you have quite a bit of cruft in there that tries to cast the array data pointer back into a LabVIEW array handle and even work on that data in this section. This of course will access invalid memory as you are working on a data structure that is not allocated like this in memory.

 

------------------------------------------------------------------

Sorry, I totally lost you here.  Could you explain this?

 

I thought that "array data pointer" would give me the data array without the dimSize.  Since the knowledge base article suggested attaching a CIN and generating code I did that and copy and pasted it into our DLL in a debugging version.  All that stuff is just test code to try to figure out what is going on.

 

------------------------------------------------------------------

 

	fprintf(pFile, "OK, using CIN generated structures: dimSize %d, with the following data&colon; \n", pTD->dimSize);
	for (i = 0; i < numWords; i++) {
		fprintf(pFile, "%d: control--0x%x, data--0x%x \n", i, pTD->data[i].control, pTD->data[i].data);
	}
	fflush(pFile);

 

But does this part produce valid data in the log file? (it should)

 

	pFile = fopen("LoadFIFOLog.txt", "w");
	fprintf(pFile, "Starting Load FIFO with %d data words, data pointer 0x%x, with the following data&colon; \n", numWords, data);
	for (i = 0; i < numWords; i++) {
		fprintf(pFile, "%d: control--0x%x, data--0x%x \n", i, data[i].control, data[i].data);
	}
	fflush(pFile);

 

Yes, it does.  Why?  I understand why it should if I select "handles by value", but I would expect that the array would be passed differently for the three options.  Yet, this works whether I pick "handles by value", "pointers to handles", or "array data pointer". 

 

This doesn't make sense to me.

 

And it also frustrates me because yes, if I were writing the DLL from scratch I could just generate C code from the CLN and then fill it in.  But I am trying to wrap an existing DLL (and I want to convince LabVIEW to match the existing interface, as opposed to creating a DLL to match what LabVIEW wants).

 

Also you do not show the declaration of FIFO_DATA_CONTROL but I would hope it to be like

 

typedef struct FIFO_DATA_CONTROL {
	int32_t control;
	int32_t data;
};

 

 Sorry, I missed that bit.  It is declared as:

struct FIFO_DATA_CONTROL {
	int control;
	int data;
};

 Which should be identical to what you wrote (I am using LabWindows/CVI in 32-bit Windows).

 

 

Look at my example. I didn't really input the array, but instead output it from the C function (which is generally the more tricky operation as if something is going wrong there, you will very quickly get access violations of some sort).

 

It seems to me that the difference between your example and mine is that your parameter is ValStruct array[], an array,

and mine is ValStruct *array.  In C passing arrays as arrays or as pointers is tricky business sometimes, even though an array is essentially a pointer in C. 

 

But that still doesn't explain why mine works when I cast it to a TD1**, which means it points to a struct containing the size and the array.

 

 

Also since your DLL function does not seem to modify the data in the array it would be sweet to indicate to LabVIEW that fact by checking the "Constant" checkbox in the Call Library Node for that parameter, as this can allow LabVIEW to optimize memory usage for this array.

 

 Good point.  Will that work OK, though, since the DLL programmer did not declare the parameter as "const"?  I don't know if it safe to do, either, since a later version of the DLL could touch the contents, since it is not declared "const"?

 

Thank you.
Batya
0 Kudos
Message 9 of 21
(5,905 Views)

Now you lost me!

 


@BPerlman wrote:

@rolfk wrote:

Hmm, you have quite a bit of cruft in there that tries to cast the array data pointer back into a LabVIEW array handle and even work on that data in this section. This of course will access invalid memory as you are working on a data structure that is not allocated like this in memory.

------------------------------------------------------------------

Sorry, I totally lost you here.  Could you explain this?

 

You can't have code in your function that treats the incoming array in two different ways at the same time. The array is either a LabVIEW data handle or an array data pointer and trying to treat it in a different way than what you passed into it will surely cause strange effects including possible Access Violations.

 

I thought that "array data pointer" would give me the data array without the dimSize.  Since the knowledge base article suggested attaching a CIN and generating code I did that and copy and pasted it into our DLL in a debugging version.  All that stuff is just test code to try to figure out what is going on.

 

And that is what my example shows to happen!

 

------------------------------------------------------------------

	fprintf(pFile, "OK, using CIN generated structures: dimSize %d, with the following data&colon; \n", pTD->dimSize);
	for (i = 0; i < numWords; i++) {
		fprintf(pFile, "%d: control--0x%x, data--0x%x \n", i, pTD->data[i].control, pTD->data[i].data);
	}
	fflush(pFile);

But does this part produce valid data in the log file? (it should)

	pFile = fopen("LoadFIFOLog.txt", "w");
	fprintf(pFile, "Starting Load FIFO with %d data words, data pointer 0x%x, with the following data&colon; \n", numWords, data);
	for (i = 0; i < numWords; i++) {
		fprintf(pFile, "%d: control--0x%x, data--0x%x \n", i, data[i].control, data[i].data);
	}
	fflush(pFile);

Yes, it does.  Why?  I understand why it should if I select "handles by value", but I would expect that the array would be passed differently for the three options.  Yet, this works whether I pick "handles by value", "pointers to handles", or "array data pointer". 

 

You say that the first part I quoted does not work (which uses the LabVIEW data handle format) and the second always works irrespective of the configuration you use for the array parameter in the Call Library.

I suspect some mess up on your part. You can't have those two codes inside at the same time. Either you configure the parameter as array data pointer and use the code from my second quote (the first logging part in your source) or you configure it as array data handle and use the code in my first quoted source code (your second part in the logging). You can't have both active at the same time or you will at least access invalid memory if not worse and that could cause all kind of strange effects, including you seeing each time a valid logging and some garbage but the first time the first logging part creates the valid code and the second time the second one does.

 

 

Look at my example. I didn't really input the array, but instead output it from the C function (which is generally the more tricky operation as if something is going wrong there, you will very quickly get access violations of some sort).

It seems to me that the difference between your example and mine is that your parameter is ValStruct array[], an array,

and mine is ValStruct *array.  In C passing arrays as arrays or as pointers is tricky business sometimes, even though an array is essentially a pointer in C. 

 

type *name and type name[] are completely interchangable in standard C, but can mean very different things in C++ especially with operator overloading. In fact the array notation is a later addition to the C standard when C++ was introduced, but for standard C it does basically add nothing but a clearer indication to the programmer that it is an array instead of a skalar passed by reference. That would be different when you write type name[x] which would prompt the C compiler to make sure you only pass fixed size arrays to this parameter of matching size, but in terms of the ABI it makes still no difference and LabVIEW can't know about the fixed size aspect of parameters nor could it control it.

 

But that still doesn't explain why mine works when I cast it to a TD1**, which means it points to a struct containing the size and the array.

Also since your DLL function does not seem to modify the data in the array it would be sweet to indicate to LabVIEW that fact by checking the "Constant" checkbox in the Call Library Node for that parameter, as this can allow LabVIEW to optimize memory usage for this array.

 

Good point.  Will that work OK, though, since the DLL programmer did not declare the parameter as "const"?  I don't know if it safe to do, either, since a later version of the DLL could touch the contents, since it is not declared "const"?

 

That is a valid concern. Most C programmers are however just to lazy to get themselves concerned about proper const declarations. Because if you do it wrong you can get a big mess with compiler warnings and even errors with any but the lowest warning levels.

 

You could declare your wrapper as const. Passing a const to a non-const declared value is valid, the opposite not. Of course it doesn't really buy you anything as the underlaying DLL developer could still decide he wants to update the data in the array at some point and LabVIEW believing it is safe to reuse the array elsewhere could then be tripped by this as it might suddenly use the modified array, believing it should be still the original array as when it got passed to your CLN.


 

 

 


 

 

 

Rolf Kalbermatter
My Blog
0 Kudos
Message 10 of 21
(5,898 Views)