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: 

CLFN crashing without returning anything

Hello everyone,

 

I need to implement a MQTT client and server in LV. I found a library in C# which looks promising and I am trying to create a DLL which I will call from LV which in turn calls the functions from the library. I do not want to use .NET Constructor in LV but CLFN so when building the bridge DLL it needs to be StdCall. This is the C# code:

 

public class TE_MQTT
    {
        static private MqttClient mqttClient;
        [DllExport(CallingConvention = System.Runtime.InteropServices.CallingConvention.StdCall)]
        static public MqttClient Connect(string IpAdress, int Port, string clientID)
        {
            mqttClient = new MqttClient(IpAdress, Port, false, new System.Security.Cryptography.X509Certificates.X509Certificate(), new System.Security.Cryptography.X509Certificates.X509Certificate(), MqttSslProtocols.None);
            mqttClient.Connect(clientID);
            return mqttClient;
        }
    }
 
The return type is MqttClient which I think it's a int32 reference but I cannot find out what type it is. My CLFN prototype looks like this:
MarcusOs_0-1579012008976.png
 
I cannot get this to work in any way, shape or form.
I appreciate any idea.
 
Thank you very much.
Message 1 of 5
(2,052 Views)

You definitely should NOT attempt to store the returned object in a static variable. Or is that necessary that you can create a globally exported function from inside an object? Can you not do this (or something similar):

 

 

[DllExport(CallingConvention = System.Runtime.InteropServices.CallingConvention.StdCall)]
static MqttClient Connect(string IpAdress, int Port, string clientID)
{
     MqttClient mqttClient = new MqttClient(IpAdress, Port, false, new System.Security.Cryptography.X509Certificates.X509Certificate(), new System.Security.Cryptography.X509Certificates.X509Certificate(), MqttSslProtocols.None);
     mqttClient.Connect(clientID);
     return mqttClient;
}

 

 

The return value is an object pointer so as far as LabVIEW is concerned you would want to configure this as pointer sized integer.

 

The nastier part is your string variable though. In .Net a string is a 16-bit Unicode string, but LabVIEW uses 8-bit ANSI strings.

 

You would either have to configure the parameter in LabVIEW as an Array of 16-bit Integers and call some function to convert the 8-bit LabVIEW string into a 16-bit Unicode string for instance by calling the function in the ni_lib_unicode VI package that you can download and install with the VIPM.

The other variant is to declare the function with a byte array input instead of string and convert that into a .Net string through the according string encoding facility.

 

 

[DllExport(CallingConvention = System.Runtime.InteropServices.CallingConvention.StdCall)]
static MqttClient Connect(byte IpAdress[], int Port, byte ClientID[])
{
     string strIpAddress = Encoding.ASCII.GetString(IpAddress);
     string strClientID = Encoding.ASCII.GetString(ClientID);
     MqttClient mqttClient = new MqttClient(strIpAdress, Port, false, new System.Security.Cryptography.X509Certificates.X509Certificate(), new System.Security.Cryptography.X509Certificates.X509Certificate(), MqttSslProtocols.None);
     mqttClient.Connect(strClientID);
     return mqttClient;
}

 

 

Rolf Kalbermatter
My Blog
0 Kudos
Message 2 of 5
(2,005 Views)

Thank you for your reply.

 

I made some adjustments from your post but now I face another problem. In C# only the first element from both arrays reach the function and I do not know why.

So for example: 10.10.10.10 -> [49,49,49,49,49,....] if I use a popup in C# I will see a 1 element array and only a single 49 reaches the function.

MarcusOs_1-1579077617600.png

 

Have a nice day.

0 Kudos
Message 3 of 5
(1,957 Views)

I was actually afraid of that. On further consideration the .Net code has no way of knowing how long the byte array is that is passed in, so the interop translation probably just assumes one element when the pointer seems valid. Maybe it would work if you changed the syntax to something like this:

 

static MqttClient Connect(byte IpAdress[50], int Port, byte clientID[50])

 

and then made sure that you configure the Minimum length in the Call Library Node to these lengths. Or there might be a .Net Interop annotation of some kind that you can append to the parameter specification, to tell it that the array length is determined by a Zero byte in the stream?

 

https://docs.microsoft.com/en-us/dotnet/framework/interop/default-marshaling-for-arrays says:

When marshaling arrays from unmanaged code to managed code, the marshaler checks the MarshalAsAttribute associated with the parameter to determine the array size. If the array size is not specified, only one element is marshaled.

 

This would indicate that you might have to do something like this instead:

 

static MqttClient Connect(
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex=1)] byte IpAdress[], int addressLen, int Port, 
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex=4)] byte clientID[], int clientLen)

 

But!!!!

 

Your library you want to call is a .Net library! Why don't you use the LabVIEW .Net functionality to call it directly instead?? That removes the need to try to make the LabVIEW Call Library Node to match the .Net Interop specific nitty gritty details, that can be sometimes hard to determine.

 

If you can't call it directly because its constructors or methods use generics then you can still create much more easily a .Net assembly wrapper for it in C# that you then call with the LabVIEW .Net functions.

Rolf Kalbermatter
My Blog
0 Kudos
Message 4 of 5
(1,946 Views)

Hello,

 

I will use LVs .Net function. I avoided using them because i was used to CLFN and didn't want to change. I don't know how LV handles the constructor when building. Does it add it to a "data" folder automatically? Something I got the "the folder already exists and I need to select a new build folder" when using constructors. 

I didn't want to deal with these building "problems" which are maybe just misunderstandings on my side.

 

Have a nice day and thank you for your information. It helped :).

0 Kudos
Message 5 of 5
(1,934 Views)