Jump to content

Sending an array of clusters to a C dll


Recommended Posts

hello,

I'm wondering how I would send an array of clusters to my dll.

What I'm wanting to do is to create an array of clusters based on the count of "boards" that are found, so if 2 boards are found, an array of 2 elements will be created.....with the elements being LabVIEW clusters. Getting the board count is no problem, but when I try to get the information from the boards by sending my array of clusters to the dll I'm hitting a brick wall.

Here is what the dll function call looks like:

extern "C" short CCONV acq_board_info(short cnt, MyStruct_info *info);

Here is what MyStruct looks like:

struct MyStruct_board_info

{

char name[32]

short var1

short var2

short var3

short var4

short var5

char var6

char var7

long var8

long var9

long var10

long var11

char var12

char pad[3]

};

If anyone can point me in the right direction I would greatly appreciate it...also if there is any information that would help out, please ask and i will do my best to provide it. By the way.....I'm using LabVIEW 8.6

Thanks

CK

Link to comment
  • 4 weeks later...

Hi Rolf,

Sorry for the delay in communication...i was put on a different project for a few weeks, but now i'm back.

I tried sending the array to the call function as an array of bytes and then typecasting and doing the Swap Bytes like you mentioned.......so far its working like a charm!

Thank you very much

Chad

Link to comment

Actually, I think that you can simplify my examples quite a bit and use the original prototype you gave:

extern "C" short CCONV acq_board_info(short cnt, MyStruct_info *info);

Configure the CLN as follows:

return type: Numeric, Signed 16-bit Integer

param 1: Numeric, Signed 16-bit Integer

param 2: Adapt to Type, Data Format "Array Data Pointer".

When I tried this LabVIEW didn't properly generate the C file (it put "void arg2[]" instead of "MyStruct_info arg2[]"), but I think it would pass the right data. You just have to make sure the type of the struct matches the cluster and be sure to use the #pragma statements as above for alignment.

Link to comment

Actually, I think that you can simplify my examples quite a bit and use the original prototype you gave:

extern "C" short CCONV acq_board_info(short cnt, MyStruct_info *info);

Configure the CLN as follows:

return type: Numeric, Signed 16-bit Integer

param 1: Numeric, Signed 16-bit Integer

param 2: Adapt to Type, Data Format "Array Data Pointer".

When I tried this LabVIEW didn't properly generate the C file (it put "void arg2[]" instead of "MyStruct_info arg2[]"), but I think it would pass the right data. You just have to make sure the type of the struct matches the cluster and be sure to use the #pragma statements as above for alignment.

Adam, you did miss in your previous post that the original problem was not about interfacing a DLL specifically made for LabVIEW, but about interfacing to an already existing DLL. In there you have normally no way that they use LabVIEW data handles! :D

Your last option is however a good one. I was missing that possibility since the Pass: Array Data Pointer for Adapt to Type is a fairly new feature of the Call Library Node. It seems it was introduced with LabVIEW 8.2.

And there is no pragma necessary (or even possible since the DLL already exists) but the structure is using filler bytes already to align everything to the element size boundaries itself.

The void arg[] datatype generated by LabVIEW is not really wrong or anything as it is fully valid C code, it is just not as perfect as LabVIEW could possibly make it, but I guess when the addition of the Pass Array Data Pointer to the Adapt Datatype was done, that person did not feel like adding extra code to generate a perfect parameter list and instead took the shortcut of printing a generic void data pointer.

Rolf Kalbermatter

Link to comment

Rolf,

I was able to get your way to work so I'm sticking with that until I learn LabVIEW a little more. On the byte swap and word swap I noticed that I neede to use a byte swap and a word swap with long data types but only needed a byte swap with the short data types......I'm a little confused on that since the byte swaps move 8 bytes and the word swaps move 16 bytes.....

I've moved on to my next function in the dll and low and behold this one has a parameter for a struct.......and in this struct are two more structs :| but if I can figure out where to use the swap bytes and swap words I should be in good shape. For the struct that contains a struct I plan on simply creating a byte array that big enough and then sub-arraying the returned values.

Thanks

Chad

Link to comment

Rolf, good point. I didn't notice that it was already padded correctly.

Chad, if you're already having complications with byte swapping then I strongly suggest trying my approach from the second post (ignoring the pragma stuff). It will save a lot of time because it will allow you to just wire the cluster directly. What you might run into still is structs that contain pointers to other structs, and that's not easy to solve. Eventually it just becomes easier to create a wrapper DLL that works better with LabVIEW.

You can also try the Import Shared Library feature. It's hit or miss since there are a lot of things C APIs can do that don't translate well to LabVIEW, but for a lot of simple interfaces (including the one in the first post here) it can do exactly the right thing automatically.

Link to comment

Rolf,

I was able to get your way to work so I'm sticking with that until I learn LabVIEW a little more. On the byte swap and word swap I noticed that I neede to use a byte swap and a word swap with long data types but only needed a byte swap with the short data types......I'm a little confused on that since the byte swaps move 8 bytes and the word swaps move 16 bytes.....

I've moved on to my next function in the dll and low and behold this one has a parameter for a struct.......and in this struct are two more structs :| but if I can figure out where to use the swap bytes and swap words I should be in good shape. For the struct that contains a struct I plan on simply creating a byte array that big enough and then sub-arraying the returned values.

Thanks

Chad

Well, Swap Words swaps the two 16 bit values in a 32bit value. It has no effect whatsoever on 16bit values (word) or 8bit values (byte). So you do not need to apply the Swap Bytes and Swap Words to each individual value but can instead put them simply in the cluster wire itself. They will swap where necessary but not more.

Rolf Kalbermatter

Link to comment

Adam,

Now that I think i've got Rolf's way of doing to it to work for me, I've went back and tried setting everything up how you mentioned.

I've configured the CLN so that it takes a short parameter and an "ADAPT to TYPE" parameter (also selected array data pointer). and for some reason it is crashing LabVIEW. I'm wondering if I need to handle the char[32] name and char[3] pad differently in my cluster...currently i've just got configured as LabVIEW strings...

I will try posting some screen shots of what i'm seeing crash wise later tonight when I get home.

Thanks for any advice on this

-Chad

Link to comment

Adam,

Now that I think i've got Rolf's way of doing to it to work for me, I've went back and tried setting everything up how you mentioned.

I've configured the CLN so that it takes a short parameter and an "ADAPT to TYPE" parameter (also selected array data pointer). and for some reason it is crashing LabVIEW. I'm wondering if I need to handle the char[32] name and char[3] pad differently in my cluster...currently i've just got configured as LabVIEW strings...

I will try posting some screen shots of what i'm seeing crash wise later tonight when I get home.

Thanks for any advice on this

-Chad

Yes you have to! Don't configure those as strings since LabVIEW strings are not only no C string pointer but also no fixed size strings. Leave them as the byte clusters as you did before and convert them to strings after you got the data from the DLL. You need to setup the clusters just the same as before but doing this with Adapt to Type and pass as C array pointer (only possible in LabVIEW 8.2 and above) will save you the hassle of typecasting/unflattening and byte and word swapping.

Rolf Kalbermatter

Link to comment

Rolf / Adam,

I was able to get this to work.....I'm wondering if there is a better way of doing this......I've attached a screenshot of the code I used.

post-15930-125053284673_thumb.jpg

Now I just need to clean up on the front end so that only the returned values appear.....I'm trying to some how hide the "name" cluster that has not yet been converted to a string so that only the converted cluster appears....I'll keep looking for a way to do this.

thanks for your help

Chad

Link to comment

I'm assuming that the "name" field in the cluster is a sub-cluster containing 32 U8s. In that case you can use the Cluster to Array primitive to convert to a U8 array, and then the Byte Array To String primitive to convert it into a string. You would still need to have one internal cluster definition for talking to the DLL and then another cluster with a real string and no padding elements to expose to your VI's callers. Still, it's doable.

Link to comment

Adams suggestion is correct and should work. He only omitted that the string name you will get will always be 32 characters long with zero characters after the end of the string. So after converting the name cluster into a byte array with Cluster to Byte Array, you should do a search for the first 0 byte and cut the array there and only after that convert it into a string. Also the pad cluster you can simply drop. It will most likely not contain any useful information but is just there to make sure the array elements align properly in memory.

Rolf Kalbermatter

Link to comment

Good point. Just to make sure Chad understands, though, you DO need the padding part in the cluster that you pass to the Call Library Node if you ever plan on passing an array of these things to the DLL. What you'll do is have two cluster types, one with the padding and the cluster of 32 U8s, and then another one without the padding and a String control instead of the U8 cluster. You'll just convert from one cluster type to the other one and return the one that works well with LabVIEW. That way the users of your API never see the ugly stuff that you needed just to call the C code.

If you only ever pass one then the padding at the end is not important. Don't you just love low-level programming? Aren't you glad LabVIEW hides this stuff from you? :)

Link to comment

You are of course correct. I meant he could drop the pad string from the user friendly cluster, but of course it should be present in the low level cluster.

And well about your latest remark, I do love to dabble in that low level stuff too, but yes it is always nice to return back to LabVIEW high level programming and concentrate on the real problem instead of making the memory ends meet everywhere and watch out about correct memory allocation and deallocation and never to access an invalid buffer :yes:.

Rolf Kalbermatter

Link to comment
  • 1 year later...

Thanks Adam for your great TUT "interfacing a DLL specifically made for LabVIEW".

Everything works fine, but If I try to change the new element without Initialising, the DLL throws an error.

How can i initialize the new element?

Can you give me short example for my application?

I have an Array of Clusters with 2 Strings.

Link to comment

How can i initialize the new element?

I don't know what you mean by this. Can you show me the C code which is giving an error (I assume you probably mean crashing?).

If you have an array of clusters of two strings then your data will look something like this in C (I assume you are including extcode.h, which comes with LabVIEW in the cintools directory):

typedef struct {  LStrHandle str1;  LStrHandle str2;} ClusterWithTwoStrings;typedef struct {  int32 cnt;  ClusterWithTwoStrings elt[1];} ArrayOfClustersWithTwoStrings;

Then your C function would look like this (assuming you set it up to be "adapt to type" and "handles by value"):

void MyFunction(ArrayOfClustersWithTwoStrings** arrayHandle){  if(arrayHandle)  {   for(int32 i=0; i != (*arrayHandle)->cnt; ++i)   { 	if((*arrayHandle)->elt[i].str1)  	; // do something with this string 	if((*arrayHandle)->elt[i].str2)  	; // do something with this string	}  }}

Note the checks to make sure the array handle and the string handles are not NULL. In LabVIEW an empty array is a NULL handle, and an empty string is a NULL handle as well. You may be crashing because you are not checking for empty.

So let's assume now that you want to actually create this array in your C code. That's a little more complicated. What you have to do now is pass in a pointer to the handle (just in case it's empty you need to allocate it and give it back to LabVIEW), so you set your call library up to be "adapt to type" and "pointers to handles". Now your C function looks something like this:

void MyFunction(ArrayOfClustersWithTwoStrings*** arrayHandlePtr){  if(arrayHandlePtr)  {	if(*arrayHandlePtr)	{  	// free any existing elements      for(int32 i=0; i != (**arrayHandlePtr)->cnt; ++i) 	{        DSDisposeHandle((**arrayHandlePtr)->elt[i].str1); // This function can handle NULL without an error        DSDisposeHandle((**arrayHandlePtr)->elt[i].str2);   	(**arrayHandlePtr)->elt[i].str1 = NULL;   	(**arrayHandlePtr)->elt[i].str1 = NULL;  	}      // Resize the array and clear any new elements      DSSetHSzClr(*arrayHandlePtr, Offset(ArrayOfClustersWithTwoStrings, elt) + sizeof(ClusterWithTwoStrings)*numberOfElementsToAllocate);	}	else	{  	// Allocate a new handle  	*arrayHandlePtr = (ArrayOfClustersWithTwoStrings**)DSNewHandle(Offset(ArrayOfClustersWithTwoStrings, elt) +  sizeof(ClusterWithTwoStrings)*numberOfElementsToAllocate);	}	if(*arrayHandlePtr)	{  	(**arrayHandlePtr)->cnt = numberOfElementsToAllocate;  	// Initialize new elements	}	else  	; // error  }  else	; // error}

(sorry about the tabbing; I can't figure out what this editor wants...)

The important things to note here are:

  1. Arrays and strings must be allocated (or resized) using the LabVIEW memory manager functions (DSNewHandle, DSDisposeHandle, DSSetHandleSize, DSSetHSzClr, etc.) from extcode.h. Link against labviewv.lib to get access to those functions.
  2. Empty arrays or strings are NULL.
  3. After disposing a handle set it to NULL
  4. After allocating or resizing a handle for an array or string set the cnt field to the number of elements you allocated.
  5. When allocating space for an array or string you have to calculate the total number of bytes for that array or string. You do this by using Offset(structName, eltField) (to get the number of bytes for the cnt field plus any padding) plus the size of your element (ClusterWithTwoStrings for your cluster or uChar for a string) times the number of elements you want.

Also note that you can convert an LStrHandle (a LabVIEW string) to a null-terminated C string using LToCStrN, and you can also convert from a C string to an LStrHandle using LStrPrintf. You can find documentation for these functions in the LabVIEW help. Also check out the "Calling Code Written in Text-Based Languages" section of the help. There's a lot of information in there.

Link to comment

This is the code:

typedef struct {	LStrHandle SerialNumber;	LStrHandle ModelName;	LStrHandle FullName;	LStrHandle VendorName;	LStrHandle DeviceClass;	LStrHandle DeviceVersion;	LStrHandle UserDefinedName;	} CamInfoCluster;typedef struct {	int32_t_Dilas dimSize;	CamInfoCluster CamInfo[1];	} CamInfoArray;typedef CamInfoArray **CamInfoArrayHdl;//DT1Hdltypedef struct {	LVBoolean status;	int32_t_Dilas code;	LStrHandle source;	} ErrorCluster;void EnumerateCameras(CamInfoArrayHdl CamArray, ErrorCluster *LVError){	int numDevicesAvail;	char* String;	numDevicesAvail = 3;	int deviceIndex;	MgErr err = mgNoErr;	if( mgNoErr == (err = DSSetHSzClr(CamArray, Offset(CamInfoArray, CamInfo) + sizeof(CamInfoCluster)*numDevicesAvail)) )	{  	(*CamArray)->dimSize = numDevicesAvail;  	//Initialize new elements????? (Example please)   	}	else	{		FillErrorCluster(LVError, TRUE, mFullErr, "mFullErr");		return;	}	for ( deviceIndex = 0; deviceIndex < numDevicesAvail; ++deviceIndex )	{		String = "Serial";		//SerialNumber		(*((*(CamArray))->CamInfo[deviceIndex].SerialNumber))->cnt = strlen(String);		NumericArrayResize(uB, 1, (UHandle*)(&((*(CamArray))->CamInfo[deviceIndex].SerialNumber)),				       4 + (*((*(CamArray))->CamInfo[deviceIndex].SerialNumber))->cnt);				strncpy((char*)(*((*(CamArray))->CamInfo[deviceIndex].SerialNumber))->str, String, (*((*(CamArray))->CamInfo[deviceIndex].SerialNumber))->cnt);	} }void FillErrorCluster(ErrorCluster *LVError, LVBoolean bStatus, int iCode,char* cSource){	LVError->status = bStatus;	LVError->code = iCode;	(*(LVError->source))->cnt = strlen(cSource);	NumericArrayResize(uB, 1, (UHandle*)(&(LVError->source)),			  	4 + (*(LVError->source))->cnt);	strncpy((char*)(*(LVError->source))->str, cSource, (*(LVError->source))->cnt);}

The incoming Array can be empty!

LV Error:

Possible reason(s):

LabVIEW: An exception occurred within the external code called by a Call Library Function Node. The exception may have corrupted LabVIEW's memory. Save any work to a new location and restart LabVIEW

Link to comment

This is the code:

typedef struct {	LStrHandle SerialNumber;	LStrHandle ModelName;	LStrHandle FullName;	LStrHandle VendorName;	LStrHandle DeviceClass;	LStrHandle DeviceVersion;	LStrHandle UserDefinedName;	} CamInfoCluster;typedef struct {	int32_t_Dilas dimSize;	CamInfoCluster CamInfo[1];	} CamInfoArray;typedef CamInfoArray **CamInfoArrayHdl;//DT1Hdltypedef struct {	LVBoolean status;	int32_t_Dilas code;	LStrHandle source;	} ErrorCluster;void EnumerateCameras(CamInfoArrayHdl CamArray, ErrorCluster *LVError){	int numDevicesAvail;	char* String;	numDevicesAvail = 3;	int deviceIndex;	MgErr err = mgNoErr;	if( mgNoErr == (err = DSSetHSzClr(CamArray, Offset(CamInfoArray, CamInfo) + sizeof(CamInfoCluster)*numDevicesAvail)) )	{  	(*CamArray)->dimSize = numDevicesAvail;  	//Initialize new elements????? (Example please)   	}	else	{		FillErrorCluster(LVError, TRUE, mFullErr, "mFullErr");		return;	}	for ( deviceIndex = 0; deviceIndex < numDevicesAvail; ++deviceIndex )	{		String = "Serial";		//SerialNumber		(*((*(CamArray))->CamInfo[deviceIndex].SerialNumber))->cnt = strlen(String);		NumericArrayResize(uB, 1, (UHandle*)(&((*(CamArray))->CamInfo[deviceIndex].SerialNumber)),	                   4 + (*((*(CamArray))->CamInfo[deviceIndex].SerialNumber))->cnt);				strncpy((char*)(*((*(CamArray))->CamInfo[deviceIndex].SerialNumber))->str, String, (*((*(CamArray))->CamInfo[deviceIndex].SerialNumber))->cnt);	} }void FillErrorCluster(ErrorCluster *LVError, LVBoolean bStatus, int iCode,char* cSource){	LVError->status = bStatus;	LVError->code = iCode;	(*(LVError->source))->cnt = strlen(cSource);	NumericArrayResize(uB, 1, (UHandle*)(&(LVError->source)),  				4 + (*(LVError->source))->cnt);	strncpy((char*)(*(LVError->source))->str, cSource, (*(LVError->source))->cnt);}

The incoming Array can be empty!

LV Error:

Possible reason(s):

LabVIEW: An exception occurred within the external code called by a Call Library Function Node. The exception may have corrupted LabVIEW's memory. Save any work to a new location and restart LabVIEW

There are some serious trouble and some minor nitpicks. First thing is your initialization of the string cnt value before you allocate the string handle with NumericArrayResize(). This can't work, since the cnt element is just another element inside the memory buffer of the handle. Therefore it does not yet exist. In fact the

(*((*(CamArray))->CamInfo[deviceIndex].SerialNumber))->cnt = strlen(String);

must crash since you dereference the (*(CamArray))->CamInfo[deviceIndex].SerialNumber value which is still a NULL handle from the previous DSSetHSzClr(). And if you hadn't used the function with Clr in it, the value would be not necessarily NULL but still as invalid a memory address as NULL is. So you can't initialize cnt before you allocated the string handle with NumericArrayResize() and in fact I make it a rule to always only initialize cnt AFTER I have filled in the data.

The same potential error is done in the FillError function. Potential because the string may be not NULL if the VI that passed in the error cluster had put a non-empty string in there.

Another issue although not fatal is that you do not need to account for the 4 bytes of the dim (for string its the cnt) integer when you use NumericArrayResize(). This function accounts for the dim integer already when allocating the necessary memory handle (but it doesn't fill in the dim/cnt value. You do need to account for the dim/cnt size when using the DSNewHandle(), DSNewHdlClr() and DSSetHandleSize() functions since this are low level memory manager functions that make no assumptions about the content of the handle.

Last but not least strncpy() is a rather tricky function. It's semantics happen to work for this specific case, since a LabVIEW string does not need to be NULL terminated and strncpy() does not insist on adding that NULL character to the end if the count value happens to be equal or smaller than the actual characters in the source string. So while strncpy() actually works perfect for this use case with LabVIEW string handles I try to avoid this function at all costs since when used with normal C strings it often can result in unterminated strings in the memory buffer which can cause all kinds of buffer overrun errors. Call of MoveBlock() or memcpy() is much more clear in this respect since it does what one expects and nothing more or less.

Link to comment
  • 2 years later...

Hi,

I am starting to work with array of clusters inside the C DLL. I followed your nice instructions which help me so much. However, I have problem with simple changing the integer value inside the cluster. 

 

My cluster is defined:
 

typedef struct {	int32_t dimSize;	double Item[1];	} DataArray;typedef DataArray **DataArrayPtr;typedef struct {	LStrHandle Name;	LStrHandle String;	int8_t Type;	DataArrayPtr Data;	} Cluster;typedef struct {	int32_t dimSize;	Cluster Variable[1];	} Buffer;typedef Buffer **BufferPtr;

I would like to change the value of the first cluster array element Type (int8_t). Lets see my test function:

int myfunction(BufferPtr parameters) {    Cluster var;        var = (*parameters)->Variable[0];    var.Type = 15;    return var.Type;}

The myfunction return the value 15, however, I would expected that the value will be changed also inside the output cluster from the Call Library Function Node... But it does not...

Edited by charlie87
Link to comment

You have a misunderstanding about C. When you create "var" inside myfunction, you are allocating memory space for a new Cluster. The assignment to var, "var = (*parameters)->Variable[0];" copies the contents of the parameter into var. Then you modify the value of Type within that new copy of var and the original is left unchanged. It's doing exactly what's expected.

 

Edit: note, however, that if you were to modify one of the strings within Var, it would be reflected in the original, because those are pointer types. The Type field is a value, not a pointer, so your function has two copies of it - one in the parameter, and another in var.

Edited by ned
  • Like 1
Link to comment

Thank you for your answer Ned. U r right, I am not very good in C programming. Stars * are my enemy forever. Also the second part of your answer is relevant. I was able to change the values inside the DataArray (pointer) in original, therefore I was confused when I cannot do the same.

Maybe, you can help me with another issue. I am expected do change the cluster array BufferPtr, add new elements respectively. I am trying to follow the example posted on the first page of this topic: 

err = DSSetHSzClr(arg1, Offset(MyArray, Cluster) + sizeof(MyCluster)*numElems)

 

I have read the specifications and some more examples, but I am not sure that I understand the meaning of the DSSetHSzClr. As I understood, the point is to increase the memory block pointed by the handle arg1. I am little bit confused, how to calculate the block size in my case. I suppose, that the Data which is part of the cluster can be variable sized as well as the strings Name and String. Do I need to calculate the size of the Buffer dynamically (count all items in DataArray and length of all strings) or not ?

Edited by charlie87
Link to comment

You're going to have to understand C pointers if you ever want this to work. The concept is simple: a pointer is a integer that contains a memory address. In a struct, a pointer takes up only the amount of space necessary to store that integer. You then need to allocate space somewhere else, initialize it with some value, and update the pointer to point at it. The size of the struct remains the same regardless of whether it contains a pointer to an empty string, or a pointer to a really long string, because the struct itself contains only the pointer. This should answer your question about whether you need to include the string lengths in calculating the buffer size.

  • Like 1
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.