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: 

CIN to CLFN Update

Solved!
Go to solution

I have inhered some very old code in which a DLL is created from LabVIEW code and the LabVIEW code calls a C DLL with the Code Interface Node (CIN).  The last time the C DLL was compiled was with Visual Basic 6 and the LabVIEW DLL was complied with LabVIEW v7.1.  My task is to recompile the code into a 64-bit DLL but before I do that, I wanted to rebuild everything in 32-bit to confirm I have the correct setup.  The first issue is that the CIN has be replaced by the CLFN (Call Library Function Node).  The tools required to rebuild a CIN are not available in the latest version of LabVIEW, so I have to update to the CLFN.

 

I found the tools to build a CIN in LabVIEW 2009 but when trying to utilize those I ran into an error during the Visual Studio compile.  As the CIN is deprecated I have not looked into trying to rebuild it any further.


Build started...
1>------ Build started: Project: ReadInputReportCIN, Configuration: Debug Win32 ------
1>cl : command line warning D9035: option 'Gm' has been deprecated and will be removed in a future release
1>ReadInputReportCIN.c
1>   Creating library .\Debug\ReadInputReportCIN.lib and object .\Debug\ReadInputReportCIN.exp
1>ReadInputReportCIN.vcxproj -> C:\Users\DavidWilt\Desktop\Driver\UtilityFunctions\ReadInputReportCIN\Debug\ReadInputReportCIN.dll
1>Performing Custom Build Step
1>FMOpen error 1 on path C:\Users\DavidWilt\Desktop\Driver\UtilityFunctions\ReadInputReportCIN\Debug"\ReadInputReportCIN.dll
1>Couldn't get code resource
1>C:\Program Files (x86)\National Instruments\LabVIEW 2009\cintools\lvsbutil: error building resource file: 1
1>C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\MSBuild\Microsoft\VC\v160\Microsoft.CppCommon.targets(286,5): error MSB3073: The command ""C:\Program Files (x86)\National Instruments\LabVIEW 2009\cintools\lvsbutil" "ReadInputReportCIN" -d "C:\Users\DavidWilt\Desktop\Driver\UtilityFunctions\ReadInputReportCIN\.\Debug\"
1>C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\MSBuild\Microsoft\VC\v160\Microsoft.CppCommon.targets(286,5): error MSB3073: :VCEnd" exited with code 2.
1>Done building project "ReadInputReportCIN.vcxproj" -- FAILED.
========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========


Visual Studio Error.jpg

 

 

I am able to build an DLL from the C source code without the additional CIN tools, but the new DLL is about half the size as the one previously build.  This may be due to the CIN process defined 2 functions when there is only 1 in the C code, or it may have something to do with upgrading the project from Visual Studio 6 to 2019.  There are many possible issues here as I cannot guarantee that the source code, I have is exactly what was used to build the working DLL.

 

With all this I figured I needed to make one change at a time and the first thing I believe that should be possible is to replace the CIN with a CLFN.  Based on my understanding of the CIN process, a DLL is first built and then an additional process is performed to create a LSB file.  As the CLFN directly uses a DLL my assumption is that I should be able to use the DLL previously created as part of the CIN process with a CLFN.  If my assumption is correct, then I am missing something in how to configure the CLFN as I am unable to get this to work.

 

I have tired manually configuring the CLFN parameters in many ways and none of them worked.  I also tried to create a header file so that I could use the Import Shared Library tool but there I am running into an issue with 2 undefined symbols which I am not able to figure out how to resolve.

 

Screenshot 2022-02-22 171552.jpg

 

There appears to be a difference in how the function is called with a CIN verses a CLFN.  In the C code, I can see that a structure has been defined for 2 inputs.  It is for an array of data where the structure includes the size and the data.  In the CIN call, only an array of data is passed in.  Is the structure something that the LSB is doing before interfacing to the DLL?  I question this because the structure is the exact some format which is called out in the Using External Code in LabVIEW manual when working with arrays.  In some examples of CLFNs I have seen where the array data and array size is passed as separate elements but I am not sure why this is done and if/when it is required.

 

As you can see, I have been working on this for a bit of time now and feel that I am going in circles.  I would appreciate anyone that may be able to provide some assistance or additional incite on this.

 

I have attached the CIN and CLFN VI which I am using for testing along with the previously built DLL and LSB files which are used by the VIs.  I have also attached the .h file which I created to try and utilize the Import Shared Library tool.  Finally, I am attaching the C file so that the function prototype can be seen.

David Wilt
The New Standard LLC
0 Kudos
Message 1 of 7
(1,631 Views)
Solution
Accepted by topic author DavidWilt

You can not use the CIN code unchanged. You need to tell the C compiler which function it needs to export. In the past that was not neccessary since the CINRun function was not really exported. Instead a CINEntry or similar function was exported that branched out into the various CINLoad, CINInit, CINUnload, CINSave and CINRun functions depending on a seperate parameter passed to it. LabVIEW called all these functions at various times during loading and unloading of the VI and the CINRun function whenever the CIN was executed.

 

So you need to make sure the C compiler exports this function. In very old times you needed an according .def file for that, that defined all exported symbols but nowadays you can simply use __declspec(dllexport) in front of the function declaration.

 

__declspec(dllexport) MgErr CINRun(int32 *ReadHandle, TD1Hdl arg1, TD2Hdl HIDOverlapped, 
	int32 *hEventObject, int32 *TimeoutMs, uInt8 *ReportID, LVBoolean *ReadPending,
	LVBoolean *Success, LVBoolean *Timeout);

 

 

In this way the function will be available in the LabVIEW Call Library Node for selection. However when recompiling this you will run into 64-bit problems. The ReadHandle is used as a Windows HANDLE and that is a 32-bit value in 32-bit LabVIEW and a 64-bit value in 64-bit LabVIEW. So it would be better to change the datatype accordingly to be a HANDLE. Same for the hEventObject. And in the LabVIEW you need to make these two values Pointer Sized Integers (and passed as Pointer to Value of you keep the pointer operator (*) in the declaration).

Rolf Kalbermatter
My Blog
Message 2 of 7
(1,610 Views)

Thank you for the quick rely.  I found many of your comments on other posts in this realm so I was hoping you would take a look at this one.

 


@rolfk wrote:

So you need to make sure the C compiler exports this function. In very old times you needed an according .def file for that, that defined all exported symbols but nowadays you can simply use __declspec(dllexport) in front of the function declaration.

 

__declspec(dllexport) MgErr CINRun(int32 *ReadHandle, TD1Hdl arg1, TD2Hdl HIDOverlapped, 
	int32 *hEventObject, int32 *TimeoutMs, uInt8 *ReportID, LVBoolean *ReadPending,
	LVBoolean *Success, LVBoolean *Timeout);

Will this take care of the issue with the import Shared Library tool also or will I still need to define something in the Preprocessor Definitions to define the missing symbols?  I assume I will still need to address the missing symbols.  I saw you previously mention adding the Windows SDK include paths to address this but I am not able to find this path on my system.  I installed a Windows SDK from here in order to be able to compile the DLL but I do not find the include folder on my system.

David Wilt
The New Standard LLC
0 Kudos
Message 3 of 7
(1,590 Views)

@DavidWilt wrote:



Will this take care of the issue with the import Shared Library tool also or will I still need to define something in the Preprocessor Definitions to define the missing symbols?  I assume I will still need to address the missing symbols.  I saw you previously mention adding the Windows SDK include paths to address this but I am not able to find this path on my system.  I installed a Windows SDK from here in order to be able to compile the DLL but I do not find the include folder on my system.


It's going to be a single function to import!!

 

Forget the Import Library Wizard for a moment and simply configure the Call Library Node manually yourself. That's a lot faster than trying to get the Import Library Wizard to understand your header file (that you haven't really).

 

The Import Library Wizard is NOT a magician. It can not create code that is going to be valid without someone going over the result and checking that everything is really as it should. The C header file syntax is notoriously inadequate to document all requirements for the caller, much of it has to be taken care of by the C programmer. But it is all the Import Library Wizard has to work on. It can not read the hopefully available API documentation of a library to understand what a specific parameter really means and how it may or may not rely to some other parameters such as the size of a buffer that is passed in. That is something the programmer needs to know and take care of after having read the API documentation several times and quite often written some test programs to test his or her understanding of that documentation. If you can't do the Call Library Node configuration yourself in the first place, you can't verify the created code from the Import Library Wizard to be correct either and trying to execute the Import Library Wizard created code without such a verification is just plain Russian Roulette sooner or later. And I'm here not so much worried about crashes that happen while you are testing your work. Memory buffer overruns can go on for a long time undetected. In the best case they crash immediately, often they will only crash your program at some other point when some function tries to access the corrupted memory, and it is always a security concern that could be exploited too.

 

For instance the two TDxHdl parameters directly map to LabVIEW array handles. While it would not really be necessary anymore to do that when using a Call Library Node (you could not pass C pointers to a CIN), the Import Library Wizard will have a hard time to recognize that these are actually LabVIEW handles. It may be able to recognize it if you change the type definition to be a LStrHandle, as that is an officially documented LabVIEW C datatype but I'm not sure the Import Library Wizard was built to recognize LabVIEW compatible variable types as that was not its main concern. Why would anyone use it to import DLLs that were specifically written for LabVIEW?

 

A LStrHandle has in fact the same memory structure, but if you make this change it will require you to also change part of the C code that accesses the interna of those arrays as the elements in a LStrHandle are somewhat named differently.

 

Principally you could make the HIDOverlapped parameter simply a char* pointer and pass it directly to the ReadFile() function and configure the Call Library Node parameter to be an Array of unsigned 8-bit integers passed as C array Pointer, by value.

 

The arg1 parameter, which is an awful name for a parameter, should be left as a handle, if you do not want to change the C code any more. And in LabVIEW you configure it as a LabVIEW String or Array of unsigned 8-bit integers, passed as LabVIEW handle, and as Value.

 

It's memory handling is however suboptimal in the C code. The Read File() function does not necessarily return as much data as is required through its 3rd parameter but only as much as it returns in the 4th parameter in the BytesRead variable. However that variable is never used or passed back to the caller in any way. In addition the ReadFile() function is told to copy the data into a global variable in the C code of size 4096 bytes but with the size of the passed in array handle. If you ever intend to read more than those 4096 bytes and preallocate that much of a buffer in your LabVIEW diagram and pass it into this function, you will corrupt memory. All in all it is pretty terrible code!!

 

And wherever your program creates the file handle and the event handle, make sure those parameters are also changed to be pointer sized integers in those Call Library Nodes. Otherwise the code will need to be revisited again when you test it on 64-bit LabVIEW. Also, are you sure you don't have another CIN somewhere that opens the file handle and creates the event handle as well as allocating the LPOVERLAPPED structure, in which the event handle has to be assigned to one of the structure elements?

Rolf Kalbermatter
My Blog
Message 4 of 7
(1,565 Views)

Thank you for providing this very detailed response.  It looks like I need to take a different approach.  I was trying not to modify the C code, as this is not my expertise, but it appears that is exactly what I need to do.

 


@rolfk wrote:

... after having read the API documentation several times ...


I assume that the documentation you are referring to here is the documentation for the C code I am using, correct?  Of course, the code I inherited did not come with any documentation.

 

Is there any documentation/resources that you would suggest to help provide more information on the interface between the CLFN and the C dll?  I have found the External Code (DLL) Execution.vi but that is about it.

 


@rolfk wrote:

Also, are you sure you don't have another CIN somewhere that opens the file handle and creates the event handle as well as allocating the LPOVERLAPPED structure, in which the event handle has to be assigned to one of the structure elements?


There is a second CIN, I have attached the C code, but tracing the execution it did not appear to be used regularly like the first CIN I referenced in this post.  In reviewing the second CIN code, it does not appear to me to be doing what you have stated here and I am not sure where the file handle is opened.

David Wilt
The New Standard LLC
0 Kudos
Message 5 of 7
(1,546 Views)

@Dino099 wrote:

If you are using Microsoft Visual Studio .NET 2003, a great reference would be the tutorial Creating a CIN Project in Visual Studio .NET 2003. It describes how to configure Microsoft Visual Studio .NET 2003 to create a Code Interface Node (CIN) project to write CIN code for use in LabVIEW 7.1. 


You have to love those pesky spammers who recycle long obsolete messages on the web to get their sneaky links on forums and websites. It's removed now.

 

As to your real problem, let me summarize your situation:

 

You inherited some code. The writer of that code did some seemingly sneaky things to put some of the work in CINs. Unless this code dates originally from before LabVIEW 5 or 6, I can not envision what made him do so. Nothing in those two CINs you showed is inherently more tricky to do in pure LabVIEW. Your first CIN is simply a pretty badly written wrapper around the Windows API ReadFile(). It could be called from within LabVIEW with the Call Library Node directly in pretty much the same way as what the CIN does and that is no voodoo art.

 

The second CIN is just as hideous. It is basically doing some byte array manipulation depending on some version specific array data input and then uses the Windows API PostMessage() to send that data to another app.

 

The array manipulation just as easily could have been done in LabVIEW itself and the call to PostMessage() is then simply another Call Library Node call. As simple as that.

 

But there are several unsolved riddles yet. Both these APIs use Windows handles. The ReadFile() call requires the file handle and event handle and the PostMessage() call a window handle. They must be created somewhere in your application  (and hopefully also properly destructed at the end).

 

For the file handle there must be a call to the Windows API CreateFile() somewhere, for the event handle a call to CreateEvent() is required and for the window handle there are many different possibilities how to get at one but it almost certainly involves calling another Windows API somehow. To close those handles you need to call  the CloseHandle() API although the window handle may not need to be closed depending on how it was retrieved.

 

Go and investigate where these things come from. They will CERTAINLY pose additional problems if you intend to move to 64-bit so just hoping the problem is going away by not researching it, is hopeless. And knowing how theses things are created in the first place, may also help to decide about a real solution for this. The attempt to just drop the C code of a CIN into a DLL is never a good one and simply can't usually work if the CIN was not an in itself contained function that has no other interactions with the rest of your program. Most likely you could completely forget those CINs and call the relevant Windows APIs directly with the Call Library Node. And even if not, you do not want to create one DLL per CIN but one DLL for your entire application that contains all those function your app needs.

Rolf Kalbermatter
My Blog
0 Kudos
Message 6 of 7
(1,504 Views)

@rolfk wrote:

But there are several unsolved riddles yet. Both these APIs use Windows handles. The ReadFile() call requires the file handle and event handle and the PostMessage() call a window handle. They must be created somewhere in your application  (and hopefully also properly destructed at the end).

 

For the file handle there must be a call to the Windows API CreateFile() somewhere, for the event handle a call to CreateEvent() is required and for the window handle there are many different possibilities how to get at one but it almost certainly involves calling another Windows API somehow. To close those handles you need to call  the CloseHandle() API although the window handle may not need to be closed depending on how it was retrieved.

 

Go and investigate where these things come from. They will CERTAINLY pose additional problems if you intend to move to 64-bit so just hoping the problem is going away by not researching it, is hopeless. And knowing how theses things are created in the first place, may also help to decide about a real solution for this. The attempt to just drop the C code of a CIN into a DLL is never a good one and simply can't usually work if the CIN was not an in itself contained function that has no other interactions with the rest of your program. Most likely you could completely forget those CINs and call the relevant Windows APIs directly with the Call Library Node. And even if not, you do not want to create one DLL per CIN but one DLL for your entire application that contains all those function your app needs.


You were right.  When I started to trace things down I found that everything was being done with other Windows API calls and I even found a VI already created which did everything one of the CIN code was doing.  Why did they use two different methods?  Who knows, that knowledge is long gone.  I agree that all this could be done within LabVIEW but as I had already spend so much time trying to figure out the CLFN I continued down that path.

 

Ultimately, --declspec(dllexport) and understanding how to properly configure the CLFN allowed me to rebuild the DLL and utilize it in the LabVIEW code.  After getting this to work, I did confirm that I could not use the DLL generated in the CIN process with the CLFN.

 

Thank you for your assistance.

 

David Wilt
The New Standard LLC
0 Kudos
Message 7 of 7
(1,468 Views)