Jump to content

DLL Linked List To Array of Strings


Recommended Posts

I have a DLL for a device which holds a linked list of structs. Obviously I cannot pass this to LabVIEW, but all I really need are the serial numbers from within the struct. So my idea is to write a wrapper to map the serial numbers to a string array and return them from a dll. Due to my lack of experience with C and lack of examples with the LabVIEW memory manager dll, I'm having quite a hard time. Because the linked list can be of a dynamic length, I am not sure how to handle the memory allocation. This is my attempt so far.

My questions are

1) How do I properly size DSNewHandle to fit the serial number, or is there no need to since it's a pointer which will just be set equal to the address of s_deviceInfo -> serialNumber

2) how do I assign each new 'h' to 'arr'

3) since the number of devices/serial numbers can vary, can I still manage this on the LabVIEW side by passing in an empty array constant as 'arr' to the dll? Or do I need to modify how I'm doing things?

    typedef struct { 
        int32 len;  
        LStrHandle str[];
    } **LStrArrayHandle;
 
   int iir_get_serial_numbers(LStrArrayHandle arr) {
        int i = 0;
 
        while(s_deviceInfo -> next) {
            LStrHandle h = (LStrHandle)DSNewHandle(sizeof(LStrHandle));
            (*h) -> cnt = strlen(s_deviceInfo -> serial_number);
            (*h) -> str = s_deviceInfo -> serial_number;
        }
        return 0;
    }
Edited by GregFreeman
Link to comment

1) LabVIEW manages the memory of the string itself. You must allocate the memory to hold your full string plus a 4-byte header, then copy all of your string data from the DLL memory into LabVIEW memory:

LStrHandle cString2LVString(const char *myCString)
{
	const int headerSize = 4;
	const int stringSize = strlen(myCString);

	// Allocate memory for the LV string
	LStrhandle h = (LStrHandle)DSNewHandle(headerSize + stringSize);

	// Write the LV string header
	(*h)->cnt = stringSize;

	// Copy the data into the LV string
	memcpy( (*h)->str, myCString, stringSize );

	return h;
}

 

2) Assuming that you initialized an empty array in LabVIEW and passed the handle into your Call Library Function Node for iir_get_serial_numbers(),

typedef struct
{
	int32 len;
	LStrHandle str[];
} **LStrArrayHandle;

int iir_get_serial_numbers(LStrArrayHandle arr)
{
	LStrHandle buffer[1024]; // Make sure this is big enough for all cases, or use
	                         // a dynamically-resizing array like a C++ std::vector.
	int n = 0;
	while(s_deviceInfo -> next)
	{
		buffer[n] = cString2LVString(s_deviceInfo->serial_number);
		++n;
	}	
	// NOTE: At this point, n equals the number of devices

	// Resize the LabVIEW array
	const int headerSize = 4;
	const int arraySize = n*sizeof(LStrHandle);
	DSSetHandleSize(arr, headerSize+arraySize);
	
	// Write the array header
	(*arr)->len = n;
	
	// Copy the string handles into the array
	for (int i = 0; i < n; ++i)
	{
		(*arr)->str[i] = buffer[i];
	}
	
	return 0;
}

 

3) Yes you can.

 

NOTE: The variable names are different, but from the LabVIEW Manager's point of view an array handle is basically the same thing as a string handle (in Classic LabVIEW, a string is an array of bytes). Allocation, resizing, and data copying techniques are the same for both types of handles. See http://zone.ni.com/reference/en-XX/help/371361R-01/lvconcepts/how_labview_stores_data_in_memory/

Edited by JKSH
Replace strcpy() with memcpy()
Link to comment

You're welcome :)

39 minutes ago, GregFreeman said:

My only follow up question is, is it necessary to size the LStrHandle to fit the string and use strcpy?

Yes. The string bytes must be placed in the memory space immediately after the 4-byte header:

loc_eps_fig14.gif

(source: http://zone.ni.com/reference/en-XX/help/371361R-01/lvconcepts/how_labview_stores_data_in_memory/)

Here's another illustration:

  • A 5-char C string takes 6 bytes of contiguous memory, ending with a null byte:
    • ['H', 'e', 'l', 'l', 'o', 0x00]
  • A 5-char LabVIEW string takes 9 bytes of contiguous memory, starting with a 4-byte integer:
    • [0x05, 0x00, 0x00, 0x00, 'H', 'e', 'l', 'l', 'o']

The LStrHandle cannot hold a pointer to a char array that is held elsewhere.

Edited by JKSH
Link to comment
On 11/14/2019 at 1:09 AM, JKSH said:

1) LabVIEW manages the memory of the string itself. You must allocate the memory to hold your full string plus a 4-byte header, then copy all of your string data from the DLL memory into LabVIEW memory:

NOTE: The variable names are different, but from the LabVIEW Manager's point of view an array handle is basically the same thing as a string handle (in Classic LabVIEW, a string is an array of bytes). Allocation, resizing, and data copying techniques are the same for both types of handles. See http://zone.ni.com/reference/en-XX/help/371361R-01/lvconcepts/how_labview_stores_data_in_memory/

There is a serious problem with this if you ever intend to compile this code for 64 bit. Then alignment comes into play (LabVIEW 32 bit uses packed data structures but LabVIEW 64 bit uses default alignment so the Array of handles requires sizeof(int32) + 4 + n * sizeof(LStrHandle) bytes.

More universally it is really:

#define RndToMultiple(nDims, elmSize)    ((((nDims * sizeof(int32)) + elmSize - 1) / elmSize)  * elmSize) 

#if IsOpSystem64Bit || OpSystem == Linux /* probably also || OpSystem == MacOSX */

#define ArrayHandleSize(nDims, nElm, elmSize)       RndToMultiple(nDims, elmSize)  + nElms * elmSize

#else

#define ArrayHandleSize(nDims, nElm, elmSize)       nDims * sizeof(int32) + nElms * elmSize

#endif

But NumericArrayResize() takes care of these alignment troubles for the platform you are running on!

Personally I solve this like this instead:

#include "extcode.h"

/* Make sure to wrap any data structure definitions that are passed from and to LabVIEW with the two include files that make sure to set
   and reset the memory alignment to what LabVIEW expects for the current platform */ 
#include "lv_prolog.h"
typedef struct
{
	int32 cnt;
	LStrHandle elm[];
} **LStrArrayHandle;
#include "lv_epilog.h"

/* define a typecode that depends on the bitness of the platform to indicate the pointer size */
#if IsOpSystem64Bit
#define uPtr uQ
#else
#define uPtr uL
#endif

MgErr iir_get_serial_numbers(LStrArrayHandle *strArr)
{
	MgErr err = mgNoErr;
	LStrHandle *pH = NULL;
	deviceInfo_t *ptr, *deviceInfo = getFirstDeviceInfo();
	int len, i = 0, n = (*strArr) ? (**strArr)->cnt : 0;

	/* This only works reliably if there is guaranteed that the deviceInfo linked list won't change
	   in the background while we are in this function! */
	for (ptr = deviceInfo; ptr; ptr = ptr->next, i++)
	{
		/* Resize the array handle only in power of 2 intervals to reduce the potential overhead
		   for resizing and reallocating the array buffer every time! */
		if (i >= n)
		{
			if (n)
				n = n << 1;
			else
				n = 8;
			err = NumericArrayResize(uPtr, 1, (UHandle*)strArr, n);
			if (err)
				break;
		}

		len = strlen(ptr->serial_number);
		pH = (**strArr)->elm + i;
		err = NumericArrayResize(uB, 1, (UHandle*)pH, len); 
		if (!err)
		{
			MoveBlock(ptr->serial_number, LStrBuf(**pH), len);
			LStrLen(**pH) = len;
		}
		else
			break;
	}
	if (deviceInfo)
		freeDeviceInfo(deviceInfo);

	/* If we did not find any device AND the incoming array was empty it may be NULL as this is the canonical empty array value in LabVIEW.
	   So check that we have not such a canonical empty array before trying to do anything with it! It is valid to return a valid array handle
	   with the count value set to 0 to indicate an empty array!*/ 
	if (*strArr)
	{
		/* If the incoming array was bigger than the new one, make sure to deallocate superfluous strings in the array! This may look
		   superstitious but is a very valid possibility as LabVIEW may decide to reuse the array from a previous call to this function
		   in a any Call Library Node instance! */
		n = (**strArr)->cnt;
		for (pH = (**strArr)->elm + (n - 1); n > i; n--, pH--)
		{
			if (*pH)
			{
				DSDisposeHandle(*pH);
				/* Clean out the handle pointer to indicate it was disposed */
				*pH = NULL;
			}
		}
		(**strArr)->cnt = n;
	}
	return err;
}

This is untested code but should give an idea!

Edited by Rolf Kalbermatter
Link to comment

Very helpful, both of you. Thanks!

Rolf, you mention this will only work if the linked list doesn't change in the background, which of course makes sense. Theoretically it won't change, but it is coming from another API, so I assume there is no way to handle keeping this safe? Even if I were to loop through the linked list immediately and make copies of the items and/or serial numbers, during my looping there is still the risk it could change, correct?

Link to comment
13 minutes ago, GregFreeman said:

Very helpful, both of you. Thanks!

Rolf, you mention this will only work if the linked list doesn't change in the background, which of course makes sense. Theoretically it won't change, but it is coming from another API, so I assume there is no way to handle keeping this safe? Even if I were to loop through the linked list immediately and make copies of the items and/or serial numbers, during my looping there is still the risk it could change, correct?

That entirely depends on the implementation. If the API just returns a pointer to a globally maintained list then there is no way to make this safe unless:

1) It only ever updates this list when calling this function just before returning the first element pointer. This means that updating this list because of OS events for unplugging devices is not a safe option.

2) It provides some locking such that getFirstDeviceInfo() acquires a lock and you have to unlock the list after you are done for instance by calling a function unlockDeviceInfo() or similar.

Another option is that the API returns a newly allocated list and you need to call a freeDeviceInfo() or similar function everytime afterwards to deallocate the entire list. If any of these is true you SHOULD be safe, otherwise there is no way to make it safe.

Link to comment
2 hours ago, Rolf Kalbermatter said:

Another option is that the API returns a newly allocated list and you need to call a freeDeviceInfo() or similar function everytime afterwards to deallocate the entire list.

Looking at the API, I believe this is the case. 

@Rolf Kalbermatter would you mind explaining what is happening with the first NumericArrayResize? The one for the string buffer is pretty self explanatory, but I don't quite understand how the first one that resizes the LStrArrayHandle works. How does the function know how to resize memory for a data structure that we have defined ourselves?

Edited by GregFreeman
Link to comment
32 minutes ago, GregFreeman said:

Looking at the API, I believe this is the case. 

@Rolf Kalbermatter would you mind explaining what is happening with the first NumericArrayResize? The one for the string buffer is pretty self explanatory, but I don't quite understand how the first one that resizes the LStrArrayHandle works. How does the function know how to resize memory for a data structure that we have defined ourselves?

A string array is simply an array handle of string handles! Sounds clear doesn't it? 😄

In reality we didn't just arbitrarily create this data structure but LKSH simply defined it knowing how a LabVIEW array of string handles is defined. You can actually get this definition from LabVIEW by creating the Call Library Node with the Parameter configured to Adapt to Type, Pass Array Handle Pointer, then right click on the Call Library Node and select "Create .c file". LabVIEW then creates a C file with all datatype definitions and an empty function body.

Link to comment
3 hours ago, Rolf Kalbermatter said:

A string array is simply an array handle of string handles! Sounds clear doesn't it? 😄

In reality we didn't just arbitrarily create this data structure but LKSH simply defined it knowing how a LabVIEW array of string handles is defined. You can actually get this definition from LabVIEW by creating the Call Library Node with the Parameter configured to Adapt to Type, Pass Array Handle Pointer, then right click on the Call Library Node and select "Create .c file". LabVIEW then creates a C file with all datatype definitions and an empty function body.

Actually, I did this yesterday to try and figure out the datatypes 😂. I thought some other magic was happening but I suppose not. I guess my question then becomes, why do we choose the particular sizes for uPtr, and how does that translate to our struct size? I suppose I'm just not understanding the relation between uPtr, the cast of arr to UHandle, and 'n' in terms of what is being done inside that resize function.

 

Anyways, unfortunately I keep crashing here:

err = NumericArrayResize(uB, 1, (UHandle*)pH, len);

I did get a compiler error here: 

pH = (**strArr)->elm + i;

I assume it should be (*strArr) -> elm + i; but I'm not certain. I did change it to this however to get it to compile

I have confirmed my variable 'len' = 4 for my serial_number 'abcd' 

Other than that, I'm not sure what the problem may be. Do we need to include the \0 in the len?

I believe doing (UHandle*)(&pH) gets us closer?

 

Edited by GregFreeman
Link to comment
12 hours ago, GregFreeman said:

Actually, I did this yesterday to try and figure out the datatypes 😂. I thought some other magic was happening but I suppose not. I guess my question then becomes, why do we choose the particular sizes for uPtr, and how does that translate to our struct size? I suppose I'm just not understanding the relation between uPtr, the cast of arr to UHandle, and 'n' in terms of what is being done inside that resize function.

uQ is the LabVIEW typcode for an unsigned 8 byte integer (uInt64). uL is the typecode for a uInt32. These are the sizes of a pointer in the respective environment and a LabVIEW handle is a pointer!

Quote

I believe doing (UHandle*)(&pH) gets us closer?

You are right of course. That was a typo! But the real typo is in the declaration:

	LStrHandle *pH = NULL;

The rest of the code is meant to have this variable be a reference to the handle not the handle itself.

Edited by Rolf Kalbermatter
Link to comment

Ah, that makes sense. Needs to be a pointer to the handle...

I am not currently crashing mid-function anymore, but i do crash when it reaches the end. I have attached the code as it stands now. I'm creating a linked list myself since I don't have access to the hardware. I am seeing something strange in the visual studio debugger. Notice the value of the str variable in hdl has some junk after "efgh." It's making me think something in the resize and MoveBlock aren't quite right, but I can't figure out what. 

image.png.c1ecd2dbb1b1d4bbc83cf88f5d66e6e8.png


int EXPORT_API iir_get_serial_numbers(LStrArrayHandle *arr) {
	MgErr err = mgNoErr;

	IIR_USB_RELAY_DEVICE_INFO_T* ptr = NULL;
	IIR_USB_RELAY_DEVICE_INFO_T* deviceInfo = NULL;
	IIR_USB_RELAY_DEVICE_INFO_T *prev = NULL;
	LStrHandle *pH = NULL;

	const char* sn[] = {
		"abcd",
		"efgh",
		"ijkl"
	};

	int len, i = 0, n = (*arr) ? (**arr)->cnt : 0;

	for (int j = 0;j < 3;++j) {
		IIR_USB_RELAY_DEVICE_INFO_T* info = (IIR_USB_RELAY_DEVICE_INFO_T*)malloc(sizeof(IIR_USB_RELAY_DEVICE_INFO_T));
		info->serial_number = (unsigned char*)malloc(5*sizeof(unsigned char*));
		strcpy_s(info->serial_number,5*sizeof(unsigned char*),sn[j]);
		info->next = NULL;
		if (!deviceInfo) {
			deviceInfo = info;
		}
		else {
			prev->next = info;
		}
		prev = info;
	}
	//IIR_USB_RELAY_DEVICE_INFO_T* deviceInfo = (IIR_USB_RELAY_DEVICE_INFO_T*)iir_usb_relay_device_enumerate();

	/* This only works reliably if there is guaranteed that the deviceInfo linked list won't change
		in the background while we are in this function! */
	for (ptr = deviceInfo; ptr; ptr = ptr->next, i++)
	{
		/* Resize the array handle only in power of 2 intervals to reduce the potential overhead
			for resizing and reallocating the array buffer every time! */
		if (i >= n)
		{
			if (n)
				n = n << 1;
			else
				n = 8;
			err = NumericArrayResize(uPtr, 1, (UHandle*)arr, n);
			if (err)
				break;
		}

		len = strlen(ptr->serial_number);
		pH = (**arr)->elm + i;
		err = NumericArrayResize(uB, 1, (UHandle*)pH, len);
		if (!err)
		{
			MoveBlock(ptr->serial_number, LStrBuf(**pH), len);
			LStrLen(**pH) = len;
		}
		else
			break;
	}
	if (deviceInfo) {
		IIR_USB_RELAY_DEVICE_INFO_T *t;
		while (deviceInfo != NULL) {
			t = deviceInfo;
			deviceInfo = deviceInfo->next;
			free(t);
		}
	}

	/* If we did not find any device AND the incoming array was empty it may be NULL as this is the canonical empty array value in LabVIEW.
		So check that we have not such a canonical empty array before trying to do anything with it! It is valid to return a valid array handle
		with the count value set to 0 to indicate an empty array!*/
	if (*arr)
	{
		/* If the incoming array was bigger than the new one, make sure to deallocate superfluous strings in the array! This may look
			superstitious but is a very valid possibility as LabVIEW may decide to reuse the array from a previous call to this function
			in a any Call Library Node instance! */
		n = (**arr)->cnt;
		for (pH = (**arr)->elm + (n - 1); n > i; n--, pH--)
		{
			if (*pH)
			{
				DSDisposeHandle(*pH);
				*pH = NULL;
			}
		}
		(**arr)->cnt = i + 1;
		char buf[1024];
		LStrHandle hdl;
		for (int k = 0; k < ((**arr)->cnt);++k) {
			hdl = (**arr)->elm[k];
		}

	}
	//iir_usb_relay_device_free_enumerate(deviceInfo);
	return err;
}

 

Edited by GregFreeman
Link to comment

There were a few errors in how you created the linked list. I did this and did a quick test and it seemed to work.

The chunk in the debugger is expected. The debugger does not know anything about LabVIEW Long Pascal handles and that they contain an int32 cnt parameter in front of the string and no NULL termination character. So it sees a char array element and tries to interpret it until it sees a NULL value, which is of course more or less beyond the actual valid information.

#include "extcode.h"
#include "stdlib.h"
#include "string.h"

typedef struct IIR_USB_RELAY_DEVICE_INFO
{
	char *serial_number;
	struct IIR_USB_RELAY_DEVICE_INFO *next;
} IIR_USB_RELAY_DEVICE_INFO_T;

static IIR_USB_RELAY_DEVICE_INFO_T* iir_usb_relay_device_enumerate()
{
	int i, len;
	IIR_USB_RELAY_DEVICE_INFO_T *ptr, *deviceInfo = NULL;
	const char* sn[] = {
		"abcd",
		"efgh",
		"ijkl",
		NULL
	};

	for (i = 0; sn[i]; i++)
	{
		IIR_USB_RELAY_DEVICE_INFO_T* info = (IIR_USB_RELAY_DEVICE_INFO_T*)malloc(sizeof(IIR_USB_RELAY_DEVICE_INFO_T));
		len = (int32)strlen(sn[i]) + 1;
		info->serial_number = (unsigned char*)malloc(len);
		memcpy(info->serial_number, sn[i], len);
		info->next = NULL;
		if (!deviceInfo)
		{
			deviceInfo = info;
		}
		else
		{
			ptr->next = info;
		}
		ptr = info;
	}
	return deviceInfo;
}

static void iir_usb_relay_device_free_enumerate(IIR_USB_RELAY_DEVICE_INFO_T *deviceInfo)
{
	IIR_USB_RELAY_DEVICE_INFO_T *ptr;
	while (deviceInfo)
	{
		ptr = deviceInfo;
		deviceInfo = deviceInfo->next;
		free(ptr->serial_number);
		free(ptr);
	}
}

#define EXPORT_API   __declspec(dllexport)

/* Make sure to wrap any data structure definitions that are passed from and to LabVIEW with the two include files that make sure to set
   and reset the memory alignment to what LabVIEW expects for the current platform */ 
#include "lv_prolog.h"
typedef struct
{
	int32 cnt;
	LStrHandle elm[];
} **LStrArrayHandle;
#include "lv_epilog.h"

/* define a typecode that depends on the bitness of the platform to indicate the pointer size */
#if IsOpSystem64Bit
#define uPtr uQ
#else
#define uPtr uL
#endif

MgErr EXPORT_API iir_get_serial_numbers(LStrArrayHandle *arr)
{
	MgErr err = mgNoErr;
	LStrHandle *pH = NULL;
	int len, i, n = (*arr) ? (**arr)->cnt : 0;
	IIR_USB_RELAY_DEVICE_INFO_T *ptr, *deviceInfo = iir_usb_relay_device_enumerate();

	/* This only works reliably if there is guaranteed that the deviceInfo linked list won't change
		in the background while we are in this function! */
	for (i = 0, ptr = deviceInfo; ptr; ptr = ptr->next, i++)
	{
		/* Resize the array handle only in power of 2 intervals to reduce the potential overhead
			for resizing and reallocating the array buffer every time! */
		if (i >= n)
		{
			if (n)
				n = n << 1;
			else
				n = 8;
			err = NumericArrayResize(uPtr, 1, (UHandle*)arr, n);
			if (err)
				break;
		}

		len = (int32)strlen(ptr->serial_number);
		pH = (**arr)->elm + i;
		err = NumericArrayResize(uB, 1, (UHandle*)pH, len);
		if (!err)
		{
			MoveBlock(ptr->serial_number, LStrBuf(**pH), len);
			LStrLen(**pH) = len;
		}
		else
			break;
	}
	iir_usb_relay_device_free_enumerate(deviceInfo);

	/* If we did not find any device AND the incoming array was empty it may be NULL as this is the canonical empty array value in LabVIEW.
		So check that we have not such a canonical empty array before trying to do anything with it! It is valid to return a valid array handle
		with the count value set to 0 to indicate an empty array!*/
	if (*arr)
	{
		/* If the incoming array was bigger than the new one, make sure to deallocate superfluous strings in the array! This may look
			superstitious but is a very valid possibility as LabVIEW may decide to reuse the array from a previous call to this function
			in a any Call Library Node instance! */
		n = (**arr)->cnt;
		for (pH = (**arr)->elm + (n - 1); n > i; n--, pH--)
		{
			if (*pH)
			{
				DSDisposeHandle(*pH);
				*pH = NULL;
			}
		}
		(**arr)->cnt = i;
	}
	return err;
}

 

Edited by Rolf Kalbermatter
Link to comment
On 11/15/2019 at 5:54 PM, Rolf Kalbermatter said:

There were a few errors in how you created the linked list. I did this and did a quick test and it seemed to work.

The chunk in the debugger is expected. The debugger does not know anything about LabVIEW Long Pascal handles and that they contain an int32 cnt parameter in front of the string and no NULL termination character. So it sees a char array element and tries to interpret it until it sees a NULL value, which is of course more or less beyond the actual valid information.

 

Very helpful, now it's working. Another big problem I realized is that I had the CLFN set to WINAPI, not C, calling convention 🤦‍♂️

Edited by GregFreeman
Link to comment

I've taken this one step further, because I realized I will need to return more than just an array of strings, but instead return an array of clusters. I have modified the dll, and sprintf statement seems to output the correct values, but I'm getting garbage back in LabVIEW. My best guess is it has something to do with what my handles are pointing at, but I haven't been able to figure out the issue.

/*Free an enumeration Linked List*/
void EXPORT_API iir_usb_relay_device_free_enumerate(IIR_USB_RELAY_DEVICE_INFO_T* info) {
	//usb_relay_device_free_enumerate((struct usb_relay_device_info*)info);
	IIR_USB_RELAY_DEVICE_INFO_T *t;
	if (info) {
		while (info) {
			t = info;
			info = info->next;
			free(t->serial_number);
			free(t);
		}
	}
}
static MgErr resize_array_handle_if_required(DevInfoArrayHdl* hdl, const int32 requestedSize, int32* currentSize) {
	MgErr err = mgNoErr;
	if (requestedSize >= *currentSize)
	{
		if (*currentSize)
			*currentSize = *currentSize << 1;
		else
			*currentSize = 8;	
		err = NumericArrayResize(uPtr, 1, (UHandle*)hdl, *currentSize);
		for (int i = 0; i < *currentSize;i++) {
			*((**hdl)->elm + i) = (DevInfoHandle)DSNewHClr(sizeof(DevInfo));
		}
	}
	return err;
}

static MgErr add_dev_info_to_labview_array(DevInfoHandle* pH, const IIR_USB_RELAY_DEVICE_INFO_T* info) {
	MgErr err = mgNoErr;
	int len, i = 0;
	(**pH)->iir_usb_relay_device_type = info->type;

	len = strlen(info->serial_number);
	err = NumericArrayResize(uB, 1, (UHandle*)&((**pH)->elm), len);
	if (!err)
	{
		MoveBlock(info->serial_number, LStrBuf((*(**pH)->elm)), len);
		LStrLen(*(**pH)->elm) = len;
	}
	return err;
}

static void free_unused_array_memory(DevInfoArrayHdl* hdl) {
	int n, i = 0;
	DevInfoHandle* pH = NULL;
	if (*hdl)
	{
		/* If the incoming array was bigger than the new one, make sure to deallocate superfluous strings in the array! This may look
			superstitious but is a very valid possibility as LabVIEW may decide to reuse the array from a previous call to this function
			in a any Call Library Node instance! */
		n = (**hdl)->cnt;
		for (pH = (**hdl)->elm + (n - 1); n > i; n--, pH--)
		{
			if (*pH)
			{
				DSDisposeHandle(*pH);
				*pH = NULL;
			}
		}
	}
}

IIR_USB_RELAY_DEVICE_INFO_T EXPORT_API * iir_usb_relay_device_enumerate(void) {
	//return (IIR_USB_RELAY_DEVICE_INFO_T*)usb_relay_device_enumerate();
	IIR_USB_RELAY_DEVICE_INFO_T* ptr = NULL;
	IIR_USB_RELAY_DEVICE_INFO_T* deviceInfo = NULL;
	IIR_USB_RELAY_DEVICE_INFO_T *prev = NULL;
	int len = 0;
	const char* sn[] = {
		"abcd",
		"efgh",
		"ijkl",
		NULL
	};
	IIR_USB_RELAY_DEVICE_TYPE deviceType[] = {
		IIR_USB_RELAY_DEVICE_ONE_CHANNEL,
		IIR_USB_RELAY_DEVICE_TWO_CHANNEL,
		IIR_USB_RELAY_DEVICE_FOUR_CHANNEL
	};
	for (int j = 0;sn[j];j++) {
		IIR_USB_RELAY_DEVICE_INFO_T* info = (IIR_USB_RELAY_DEVICE_INFO_T*)malloc(sizeof(IIR_USB_RELAY_DEVICE_INFO_T));

		len = (int)strlen(sn[j]) + 1;
		info->serial_number = (unsigned char*)malloc(len);
		info->type = deviceType[j];
		memcpy(info->serial_number, sn[j], len);
		info->next = NULL;
		if (!deviceInfo) {
			deviceInfo = info;
		}
		else {
			prev->next = info;
		}
		prev = info;
	}
	return deviceInfo;
}

int EXPORT_API iir_get_device_info(DevInfoArrayHdl *arr) {
	MgErr err = mgNoErr;

	IIR_USB_RELAY_DEVICE_INFO_T* ptr = NULL, *prev = NULL;
	DevInfoHandle* pDevInfo = NULL;
	IIR_USB_RELAY_DEVICE_INFO_T* deviceInfo = (IIR_USB_RELAY_DEVICE_INFO_T*)iir_usb_relay_device_enumerate();

	int i = 0, n = (*arr) ? (**arr)->cnt : 0;

	for (ptr = deviceInfo; ptr; ptr = ptr->next, i++)
	{
		err = resize_array_handle_if_required(arr, i, &n);
		if (err)
			break;
		pDevInfo = (**arr)->elm + i;
		err = add_dev_info_to_labview_array(pDevInfo,ptr);
		if(err)
			break;
	}

	iir_usb_relay_device_free_enumerate(deviceInfo);
	free_unused_array_memory(arr);

	DevInfoHandle* hdl2;
	char buf[1024];
	(**arr)->cnt = i;
	for (hdl2 = (**arr)->elm,i=0; i<(**arr)->cnt ;i++,hdl2++) {
		sprintf_s(buf, 1024, "%s: %d", (*(**hdl2)->elm)->str, (**hdl2)->iir_usb_relay_device_type);
	}
	return err;
}
      

 

Link to comment
14 hours ago, GregFreeman said:

I've taken this one step further, because I realized I will need to return more than just an array of strings, but instead return an array of clusters. I have modified the dll, and sprintf statement seems to output the correct values, but I'm getting garbage back in LabVIEW. My best guess is it has something to do with what my handles are pointing at, but I haven't been able to figure out the issue.


static MgErr resize_array_handle_if_required(DevInfoArrayHdl* hdl, const int32 requestedSize, int32* currentSize) {
	MgErr err = mgNoErr;
	if (requestedSize >= *currentSize)
	{
		if (*currentSize)
			*currentSize = *currentSize << 1;
		else
			*currentSize = 8;	
		err = NumericArrayResize(uB, 1, (UHandle*)hdl, *currentSize * sizeof(DevInfo));
	}
	return err;
}

 

You completely borked it! An array of clusters is not an array of pointers to a C struct! It's simply an array of clusters all inlined in one single memory block!

Look at resize_array_handle_if_required() and what you try to do in there! The first NumericArrayResize() should not allocate n * uPtr anymore but rather use a type of uB and allocate n * sizeof(DevInfo). Of course you still need to allocate any string handles inside the struct but that would be done in:

static MgErr add_dev_info_to_labview_dev_info(DevInfo* pDevInfo, const IIR_USB_RELAY_DEVICE_INFO_T* info)
{
	MgErr err = mgNoErr;
	int len = strlen(info->serial_number);
	pDevInfo->iir_usb_relay_device_type = info->type;
	err = NumericArrayResize(uB, 1, (UHandle*)&(pDevinfo->serial_number), len);
	if (!err)
	{
		MoveBlock(info->serial_number, LStrBuf(*(pDevinfo->serial_number)), len);
		LStrLen(*(pDevinfo->serial_number)) = len;
	}
	return err;
}

Nobody said C programming was easy at all. That is why we all use LabVIEW! In LabVIEW you can concentrate on the actual problem rather than having to go byte counting and using all over the place malloc() and free() (or the LabVIEW memory manager equivalents thereof).

Also now you absolutely really and truely need to make sure to encompass the typedef for the DevInfo structure with the lv_prolog.h and lv_epilog.h  include statements. Memory alignment for the 32 bit version of your DLL will otherwise play badly havoc with what the C compiler thinks the array should look like and what LabVIEW expects unless you happen to have defined the device_type variable element to be an int32.

Edited by Rolf Kalbermatter
Link to comment
Quote

You completely borked it! An array of clusters is not an array of pointers to a C struct! It's simply an array of clusters all inlined in one single memory block!

Look at resize_array_handle_if_required() and what you try to do in there! The first NumericArrayResize() should not allocate n * uPtr anymore but rather use a type of uB and allocate n * sizeof(DevInfo). Of course you still need to allocate any string handles inside the struct but that would be done in:

Got it, this makes sense. Thanks!

I suppose from this output when I create 'c file I just didn't correctly reverse engineer the data structures in terms of what LabVIEW wanted. TD1 has a TD2 not a TD2Handle. Looking at it again with your clarification makes sense

typedef struct {
	int32_t numPorts;
	LStrHandle Sn;
	} TD2;

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

 

Edited by GregFreeman
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.