Call C# and Other DLLs Written in .NET Language in LabWindows™/CVI

Updated Jun 13, 2023

Environment

Software

  • LabWindows/CVI

I used the Loadlibrary function in LabWindows/CVI to directly call the function in the DLL written by C# according to the pointer but failed. Then i tried the method of creating the DLL import library file but also failed. So, what is the difference between a DLL written in C# and a normal DLL, and how should it be called in CVI?

      The DLLs in the C# programming specification are divided into managed DLLs and unmanaged DLLs. Managed DLLs are intermediate code and depend entirely on the .NET platform. Non-managed DLLs are machine code and do not depend on the .NET platform. C#, VB.NET and F# programming are developed in pure .NET language and the generated DLL belongs to the managed DLL. The DLLs written in other languages including C/C++, CVI, and LabVIEW are unmanaged DLLs. Managed applications and DLLs can call unmanaged DLLs directly, while non-managed applications and DLLs must adopt .NET Runtime to call managed DLLs.

      The methods of calling DLL in CVI is divided into two methods: display call and implicit call, which are both applicable for unmanaged DLLs, ie DLLs written in .NET. For details, refer to How To Create a DLL from a LabWindows™/CVI Instrument Driver. The display call method does not need header file of the called DLL, and use the Loadlibrary, GetProcAddress, Freelibrary functions provided by windows.h to directly access the functions in the DLL according to the pointer. This method is applicable to a DLL without header files, but for the case of the prototype of the called function. The implicit call method needs header file of called DLL, which is called by the method that creates the DLL import library .lib file. This method uses the Generate DLL Import Library wizard in the Options tab to create a DLL import library. After adding the import library to the project, you can easily and flexibly use the function in the called DLL by the function name. Note that the function to be called needs to be declared in the header file.

      CVI calls C# and other .NET language DLLs need to use the Create .NET controller in the Tools tab. It generates a wrapper that calls .NET assembly code, which contains the corresponding instrument drivers, source files, and header files. The specific steps for the wrapper generation are as follows:

1. Select Create .NET controller from the Tools tab.

2. Generally, the DLLs developed by third parties are not in the Global Assembly Cache, so check the Specify Assembly by Path in the pop-up dialog box and select the C# DLL to be called.

3. Specify an instrument driver .fp file in Target Instrument and click OK. The CVI program will generate an instrument driver that can call the selected C# DLL.
4. Add the generated .fp file to the project;



Now that the preparations have been completed, let's take a look at the .NET DLL programming methods and procedures. The instrument driver automatically generated by the wrapper will encapsulate some calling functions. The naming method of the function is [namespace]_[class name]. The namespace and class name are defined when writing the .NET program, and the namespace is also the name of the DLL. As shown below, ClassLibrary1 is a namespace, Class1 is a class name, a DLL includes a namespace, a namespace can contain one or more classes, and a class can contain multiple functions. ClassLibrary1_Class1__add  is an addition function in a DLL.
      Generally, the functions such as Initialize_[namespace] and Close_[namespace] call CDotNetLoadAssembly function and CDotNetDiscardAssemblyHandle function respectively, [namespace]_[class name]__Create calls CDotNetCreateGenericInstance function, [namespace]_[class name]_[ Function name] Calls the CDotNetInvokeGenericStaticMember function and so on.



In particular, since the called DLL is not in the Global Assembly Cache, you need to register the .NET DLL with the CDotNetRegisterAssemblyPath function before calling. If not registered, the failed error as shown below will appear.



In addition, C# programming does not need to care about garbage memory collection (Garbage Collector), and the variables involved in the called function in the C environment need to release the memory space of the variable by manually calling the function that releases the memory. The main process of programming is as follows:

1. Declare a handle to the [DLL name]_[class name] type;
2. Call CDotNetRegisterAssemblyPath("[DLL name], Version=xxxx, Culture=xx, PublicKeyToken=xx", ''Full Path of DLL") to register the .NET DLL. The path separator of the DLL uses "\\", such as D:\\CVI\\Projects\\C#net DLL call;
3. Call the Initialize_[DLL name] function to initialize the .NET controller;
4. According to the handle, call [DLL name]_[class name]__Create to create an instance of the called DLL;
5. Call the specific function such as [DLL name]_[class name]_[function name] and write the corresponding code;
6. Call CDotNetDiscardHandle to release the .NET DLL instance handle;
7. Call CDotNetFreeMemory to release the variable memory;
8. Call Close_[DLL name] to uninstall the .NET DLL;
     The source code is shown:

#include <ansi_c.h>
#include <cvirte.h>
#include <userint.h>
//informations about DLL
static const char * DLL_Name="ClassLibrary1, Version=1.0.0.0, Culture=EN, PublicKeyToken=null";
int main()
{
    int status;
    int result;
    //declare handle for DLL
    ClassLibrary1_Class1 Handle;
    //register the DLL
    Status = CDotNetRegisterAssemblyPath(DLL_Name,
    “c:\\Users\\kqian\\Desktop\\CVI\\Projects\\C#net DLL call\\simple net dll\\ClassLibrary1.dll");
    //initialize and load the DLL
    status = Initialize_ClassLibrary1();
    //create an instance
    status = ClassLibrary1_Class1_Create(&Handle,NULL);
    //call function
    status = ClassLibrary1_Class1_add(Handle, 1, 3, &result, NULL);

    printf("Result=%d\n",result);
    //release handle
    CDotNetDiscardHandle(Handle);
    //release memory
    CDotNetFreeMemory(&result);
    //unload the DLL
    status = Close_ClassLibrary1();
    
    return 0;
}

Note: The assembly must be fully qualified using the "AssemblyName, Version=x.x.x.x, Culture=language, PublicKeyToken=abcdefghij123456" format.

      DLLs written in C# and other .NET languages ​​require the support of the .NET Runtime engine. CVI calls such DLLs only through the "Creat .NET controller" method. At the same time, you need to pay attention to the problem of data type conversion. Enum, Rectangular Array, String, System.Decimal and System.Boolean in .NET language can automatically complete the conversion, while COM Run-Time Callable Wrapper (RCW) types, Jagged arrays and Boxed Data types require manual call library function conversion.