From 04:00 PM CDT – 08:00 PM CDT (09:00 PM UTC – 01:00 AM UTC) Tuesday, April 16, ni.com will undergo system upgrades that may result in temporary service interruption.

We appreciate your patience as we improve our online experience.

LabVIEW

cancel
Showing results for 
Search instead for 
Did you mean: 

Calling function from a DLL with a void ** in parameters

Solved!
Go to solution

Hi everybody,

 

I'm calling 2 functions from a DLL in C : 

 

- OpenMultiReadSession(tDataType eType,int nNbOfItemsToRead,WORD *pnIDArray,void **ppArrayOfOutputPointers,int *pnHandle) 

- TriggerMultiReadSession(int nHandle) {... ReadMultiDouble(pxMRSession->nNbOfItems,pxMRSession->pnArrayOfIDToRead,(double**)pxMRSession->ppArrayOfOutputPointers)) ... }

 

Basically, i enter an array of ID in Open and i get measures in ppArrayOfOutputPointers, the trigger function then calls another dll that use a function for ID that are int and another for double.

 

With the automatic DLL import i have no errors but the result always stays at 0.

I tried the solution from this thread (set ppArrayOfOutputPointers as a pointer sized integer passed by reference) https://forums.ni.com/t5/LabVIEW/array-handle/td-p/2868376

 

Sans titre3.png

 

i get an error "cannot derefence null pointer" in LV and this in CVI :

 

Sans titre2.png

I've only been able to make the DLL returns a good value in CVI by setting it as an array handle. From all the threads i've read this doesn't seem like a good solution because array handle in LV are different than in C.

 

Is it possible to do that in LV or do i have to change the DLL ?

 

Thanks in advance

 

Pierre

0 Kudos
Message 1 of 7
(3,476 Views)

You have by far not provided enough information to make a conclusive statement about what would be the best approach to do. There is not even a good C prototype for the function but even if there was, it is only for simple functions which only accept scalar values as parameters to say for sure how the function interface is done. As soon as you have pointers you really need a little more information such as a good function documentation to make sure, and in absence of such documentation lots of guessing and then trial and error with the well known compile-run-crash-modify cycle.

In that respect it would be useful to attach a lot more to your post such as the VIs you actually created, the header file for the functions in question and some actual prosa text describing what the functions do and what each parameter of the function is and how it is meant to be allocated, used and deallocated and by whom.

 

From the little you provided one possible solution might be maybe something along these lines. That assumes that the actual open function does allocate and fill in the actual pointers in the array. If it doesn't you really got a number of problems:

 

1) an array of pointers to a numeric value (or an array of numeric values) is not a datatype that you can do in LabVIEW directly. As you have found out LabVIEW uses handles which as a pointer to a pointer to a memory area starting with one 32 size integer for every dimension directly followed by the data bytes of the actual array in fully packed format.

 

2) even if you could create such an array of pointers in LabVIEW directly it would not help you. LabVIEW reserves the right to allocate, deallocate and reallocate its data at any time. When passing LabVIEW data into a DLL function the data is only guaranteed to exist for the duration of the DLL function. You can't store the reference (pointer) to that data in the DLL and hope for the DLL to fill in information later on since the reference is not valid anymore after the function returned control to LabVIEW.ArrayPointer.png 

Rolf Kalbermatter
My Blog
Message 2 of 7
(3,454 Views)

Hi rolfk,

 

thanks for your quick response.

 

I don't understand everything in the code but i think the DLL manages totally the memory on its own. Here is what i can give you :

in the .h file

typedef enum
{
TYPE_8BS=0x01, // Integer type (8 bits signed)
TYPE_8BUS=0x02, // Integer type (8 bits unsigned)
TYPE_16BS=0x04, // Integer type (16 bits signed)
TYPE_16BUS=0x08, // Integer type (16 bits unsigned)
TYPE_32BS=0x10, // Integer type (32 bits signed)
TYPE_32BUS=0x20, // Integer type (32 bits unsigned)
TYPE_32BF=0x40, // Float type (32 bits)
TYPE_INT=0x3F, //INT type (include all integer types)
TYPE_NUMERICAL=0x7F, //NUMERICAL type (include all numerical types)
TYPE_BIN=0x80, // Binary block type
TYPE_STRING=0x100 // String type (is the same as a binary bloc + a \0 terminator)
} tDataType;

 

in the .c file : 

typedef struct
{
tDataType eType;
int nNbOfItems;
WORD *pnArrayOfIDToRead;
void **ppArrayOfOutputPointers;
} tMultiReadSession;

 

int DLLEXPORT OpenMultiReadSession(tDataType eType,int nNbOfItemsToRead,WORD *pnIDArray,void **ppArrayOfOutputPointers,int *pnHandle)

//eType is the type of IDs read

//nbofItemToRead is the size of pnIDArray

//IDArray is a array of IDs 

//ppArrayOfOutputPointers point to an array of measures linked to the ID 

//pnHandle is a number used to define a session and trigger a measure 
{
int i;
int nErr=0;
tObject *pxObj=NULL;
tMultiReadSession *pxMRSession=NULL;

if(g_bCommInit)
{
//Init session object
pxMRSession = calloc(1,sizeof(tMultiReadSession));
pxMRSession->eType = eType;
pxMRSession->nNbOfItems = nNbOfItemsToRead;
pxMRSession->pnArrayOfIDToRead = calloc(pxMRSession->nNbOfItems,sizeof(WORD));
pxMRSession->ppArrayOfOutputPointers = ppArrayOfOutputPointers;
for(i=0;i<pxMRSession->nNbOfItems;i++)
{
//Add this object to the array
pxMRSession->pnArrayOfIDToRead[i] = pnIDArray[i];
}
//If ok, set the handle value
*pnHandle = (int)(pxMRSession);
}
else
nErr = ERR_COMM_NO_INIT;
Error:
if(nErr && g_bCommInit)
{
free(pxMRSession->pnArrayOfIDToRead);
free(pxMRSession);
*pnHandle = 0;
}
return nErr;
}

 

int DLLEXPORT TriggerMultiReadSession(int nHandle)
{
int nErr=0;
tMultiReadSession *pxMRSession=NULL;

pxMRSession = (tMultiReadSession *)(nHandle);
if(pxMRSession)
{
//MessagePopup("ReadMultiInt","OK pour continuer");
if(pxMRSession->eType != TYPE_32BF)
{
ERR_Chk(ReadMultiInt(pxMRSession->nNbOfItems,pxMRSession->pnArrayOfIDToRead,(int**)pxMRSession->ppArrayOfOutputPointers));
}
else
{
ERR_Chk(ReadMultiDouble(pxMRSession->nNbOfItems,pxMRSession->pnArrayOfIDToRead,(double**)pxMRSession->ppArrayOfOutputPointers));
}
}
Error:
return nErr;
}

 

and the details of ReadMultiDouble

int ReadMultiDouble(int nNbOfItemsToRead,WORD *pnArrayOfIDToRead,double **ppfArrayOfOutputPointers)
{
int i;
int nErr=0;
int nRet=TRUE;
int nNbOfReadItems=0;
int nVectorSize=0;
double **ppfOutputVector=NULL;
WORD *pnVector=NULL;
WORD wObjectID=0;
BYTE bFormat=0;
BYTE *pbAnswerBuf=NULL;
WORD wAnswerBufSize=0;

if(pnArrayOfIDToRead && ppfArrayOfOutputPointers)
{
// Reset
for(i=0;i<nNbOfItemsToRead;i++)
*ppfArrayOfOutputPointers[i] = 0.0;

//Read all items
while(nRet && (nNbOfReadItems < nNbOfItemsToRead))
{
//The multi read cannot read more than 9 items at a time => divide in vectors of 9 items
pnVector = &pnArrayOfIDToRead[nNbOfReadItems];
nVectorSize = MIN(nNbOfItemsToRead - nNbOfReadItems,9);
ppfOutputVector = &ppfArrayOfOutputPointers[nNbOfReadItems];
// ID request
nRet = ReadMultiEx(pnVector,(BYTE)nVectorSize,0);
// Decode to INT
for(i=0;nRet && (i<nVectorSize);i++)
{
// Read answer
nRet = ReadAnswer(&wObjectID,&bFormat,&pbAnswerBuf,&wAnswerBufSize);
if(nRet)
nRet = AnswerToFloat(ppfOutputVector[i],bFormat,pbAnswerBuf,wAnswerBufSize);
}
//Update the counter of read items
nNbOfReadItems += nVectorSize;
}
//Is there an error ?
if(!nRet)
{
nErr = ERR_SetError(ERR_GENERIC_COM_ERROR);
}
}
return nErr;
}

 

i don't have more details for the lowest functions that are contains in another DLL which i don't have access to the source files.

I attached my VI with the array handle.

 

Pierre

0 Kudos
Message 3 of 7
(3,435 Views)

The code somehow doesn't make a lot of sense to me from a quick glance. You setup a code structure that you treat as a pointer to a handle. In that code structure you do allocate and copy the pnIDArray that you pass into, but you do not copy the  ppArrayOfOutputPointers into, just the reference to it, making the calling application responsble to allocate that array of pointers.

 

But this has two trouble as already pointed out:

1) LabVIEW can't in itself create such an array. You would have to do that on your own by calling memory managment functions and building the array yourself, which is a pretty stupid thing to do if you have already a C DLL with source code where you can do this yourself.

2) As explained, normal LabVIEW memory buffers (not the ones you would allocate like in the point 1) passed into a DLL function are only guaranteed valid for the duration of the function call. The array reference that you store in your handle will basically have to be considered invalid by the time you call your TriggerMultiReadSession() function.

 

So to solve this, get rid of the ppArrayOfOutputPointers parameter in the OpenMultiReadSession() function and allocate that array of pointers in your OpenMultiReadSession().

 

The ReadMultiDouble() looks totally disjoint from the rest since it does not take your pnHandle parameter to use. In fact It seems to me to be the gist of what you really want to do and you most likely want to change this function to simply read like this and directly call it from LabVIEW, abandoning all the rest with complicated handles to store information in.

int ReadMultiDouble(int nNbOfItemsToRead, WORD *pnArrayOfIDToRead, double *pf2DArrayOfData)
{
	int i;
	int nErr=0;
	int nRet=TRUE;
	int nNbOfReadItems=0;
	int nVectorSize=0;
	WORD *pnVector=NULL;
	WORD wObjectID=0;
BYTE bFormat=0; BYTE *pbAnswerBuf=NULL; WORD wAnswerBufSize=0; if (pnArrayOfIDToRead && ppfArrayOfOutputPointers) { // Reset for (i = 0; i < nNbOfItemsToRead; i++)
{ pf2DArrayOfData[i * magicLength] = 0.0; //Read all items }
while (nRet && (nNbOfReadItems < nNbOfItemsToRead)) { //The multi read cannot read more than 9 items at a time => divide in vectors of 9 items pnVector = &pnArrayOfIDToRead[nNbOfReadItems]; nVectorSize = MIN(nNbOfItemsToRead - nNbOfReadItems, 9); nRet = ReadMultiEx(pnVector, (BYTE)nVectorSize, 0); // Decode to double for (i = 0; nRet && (i < nVectorSize); i++) { // Read answer nRet = ReadAnswer(&wObjectID, &bFormat, &pbAnswerBuf, &wAnswerBufSize); if (nRet) nRet = AnswerToFloat(&pf2DArrayOfData[(nNbOfReadItems + i) * magicLength], bFormat, pbAnswerBuf, wAnswerBufSize); } //Update the counter of read items nNbOfReadItems += nVectorSize; } //Is there an error ? if (!nRet) { nErr = ERR_SetError(ERR_GENERIC_COM_ERROR); } } return nErr; }

Except of course if the vector is not a single double but really an array. But how big needs this array to be? Is that always the same size? If so then allocate a 2D array in LabVIEW with nBrOfItemsToRead * <your magic vector size> and pass this into the function. A 2D array in LabVIEW is a single buffer with dim1 * dim2 elements. Now you just have to calculate the correct index into the array 

 

As an extra note you treat the handle as an int. Don't!! That will not be valid if you ever end up compiling this for 64 bit.

Rolf Kalbermatter
My Blog
0 Kudos
Message 4 of 7
(3,425 Views)

I forgot to precise that the ReadMultiDouble function is located in another DLL like AnswerToFloat and i can't modify this code, i'm sorry i didn't mention that.

I only can modify Open and Trigger. 

Would it be possible to create some kind of wrapper in C to go from ppArrayOfOutputPointers to a simpler pointer to array in the Trigger prototype ? This way i wouldn't have to worry about array reference becoming invalid.

 

Pierre 

0 Kudos
Message 5 of 7
(3,394 Views)
Solution
Accepted by topic author pierro_ni

I wouldn't do that. Write a small wrapper around ReadMultiDouble() and friends along these lines:

 

/* pf2DArrayOfData is a LabVIEW 2D array of nNbOfItemsToRead * magicLength double elements,
passed as Array Data Pointer.
Which dimension is first will have to be found out by trial and error by looking at the data in LabVIEW. */ int LV_ReadMultiDouble(int nNbOfItemsToRead, WORD *pnArrayOfIDToRead, double *pf2DArrayOfData) { int i; int nErr = 0; if (nNbOfItemsToRead && pnArrayOfIDToRead && pf2DArrayOfData) { double **ppfPointerArray = malloc(nNbOfItemsToRead * sizeof(*double)); if (!ppfPointerArray) return mFullErr; for (i = 0; i < nNbOfItemsToRead; i++) { ppfPointerArray[i] = &pf2DArrayOfData[i * magicLength]; // Initialize pointer array } nErr = ReadMultiDouble(nNbOfItemsToRead, pnArrayOfIDToRead, ppfPointerArray); free(ppfPointerArray); } return nErr; }

 

Rolf Kalbermatter
My Blog
Message 6 of 7
(3,381 Views)
Yes, it works ! Just a little correction in the code. It works fine with magiclengh = 1 so i just initialized a 1d array into my trigger function in LV with an adapt to type and array data pointer configuration. A big thank you to you rolfk !!
int LV_ReadMultiDouble(int nNbOfItemsToRead, WORD *pnArrayOfIDToRead, double *pf2DArrayOfData)
{
    int i;
    int nErr = 0;
    if (nNbOfItemsToRead && pnArrayOfIDToRead && pf2DArrayOfData)
    {
        double **ppfPointerArray = malloc(nNbOfItemsToRead * sizeof(double*));
        if (!ppfPointerArray)
            return mFullErr;
			
        for (i = 0; i < nNbOfItemsToRead; i++)
        {
             ppfPointerArray[i] = &pf2DArrayOfData[i];		// Initialize pointer array
        }
 
        nErr = ReadMultiDouble(nNbOfItemsToRead, pnArrayOfIDToRead, ppfPointerArray);
        free(ppfPointerArray);
    }
    return nErr;
}
0 Kudos
Message 7 of 7
(3,336 Views)