The VISA I/O API & .NET Picking up where .NET leaves off

  The VISA I/O API & .NET

Picking up where .NET leaves off

(Page 1 of 5)
David Gladfelter
The Virtual Instrument Software Architecture (VISA) is a standard for instrument communication. David presents tools for using VISA with C# and VB.NET.

David is an applications engineer with Agilent Technologies. He can be contacted at author_dgladfelter@agilent.com.


Accessing Unmanaged DLL Functions From .NET Languages

Variable Argument Lists in .NET and Unmanaged Interoperability


The Virtual Instrument Software Architecture (VISA) is a vendor interchangeable Standard for instrument communication developed by the VXIplug&play Systems Alliance (http://www.vxipnp.org/). VISA identifies a common I/O API for communicating over a variety of bus interfaces, including GPIB, VXI, and serial interfaces.

The VISA Standards body (http://www.ivifoundation.org/) has specifications for API versions in C, COM, and other programming technologies—but currently not .NET. Consequently, Agilent Technologies (the company I work for) has developed header files, help, and tutorials for using VISA in C# and Visual Basic .NET. Anyone familiar with the C version of the VISA API should be comfortable using the .NET header files.

I/O APIs and VISA

An I/O API is a set of functions that provide a logical pipe to a device attached to the computer. I/O APIs permit PC/device communication via an arbitrary, typically string-based language defined by the device. The characteristic functions in an I/O API are Read and Write, usually taking string parameters. Most I/O APIs are session based, meaning that an Open method is called, creating a logical exclusive pipe to the device, and at the end of communication a Close method is called ending the session and freeing any allocated resources. Some of the best-known I/O APIs are the Windows Sockets API, which allows network communication over the TCP/IP protocol and the C printf/scanf communication with files and the console.

Well-understood, common devices eventually have driver software developed where each driver function corresponds to an operation on the device. I/O APIs are used in devices where it is not economical or convenient to put the logic to control the device in the PC. Such devices typically have smaller sales volume, a wide array of features, and are used in a variety of environments. All of these characteristics make it difficult to justify providing a more powerful driver on the PC. Instruments such as voltmeters and network analyzers are often devices with these characteristics, and I/O APIs are a common way of controlling them.

VISA is an I/O API targeted at communication with instruments or other test-and-measurement devices. The VISA standard describes communication with devices using test- and measurement-specific I/O hardware as well as better-known communication mechanisms, such as TCP/IP, USB, and RS-232, either directly or over test-measurement-specific protocols on top of those mechanisms. VISA has standard API definitions over two of the most common PC software reuse mechanisms—C DLLs and Microsoft's COM.

Listing One is a VISA I/O session to an instrument that can be accessed via a TCP/IP connection. The Resource Manager is a class factory that knows how to find, enumerate, and open resources. The viOpen call opens an I/O session to the instrument. The viRead and viWrite calls send/receive data to/from the instrument, respectively. The viClear resets the I/O state of the session and viClose ends communication. Using these VISA methods (defined in visa.h) requires that a Visa32.dll DLL be on the computer, findable in the DLL search path, and configured to access the resources with which the program attempts to communicate.

.NET and VISA

Languages such as C# and VB.NET (among others) have a different software reuse and function-calling mechanism than C/C++. .NET methods are still called through call stacks, but a new metadata format is used to describe the method signature, rather than the header files and .lib files used in C/C++. Microsoft recognized that the ability to call C DLLs from .NET (and vice versa) would remain essential for some time, so it built-in the ability to call between unmanaged functions and .NET code (see the accompanying text box entitled "Accessing Unmanaged DLL Functions from .NET Languages").

There is also interoperability between COM objects and .NET, with the ability to use COM objects from .NET code and the ability to easily expose .NET objects as COM objects. The VISA I/O API has both C and COM versions. The COM version can be automatically imported into .NET either through the tools provided in the .NET SDK or in Microsoft Visual Studio .NET. There is no such ability for C DLLs and header files. This discrepancy exists because .h and .lib files describing a C API do not typically have enough metadata for a caller to reliably build call stacks to the C functions they describe. However, the metadata of COM type libraries (.tlb files) do provide enough metadata to call the COM interfaces they describe.

Agilent has developed redistributable .NET header files that import the VISA-C DLL's API into .NET via a package called "Agilent VISA Header Files and Examples for Visual Basic, Visual Basic .NET, and C#" (available at http://www.agilent.com/find/adn-iolib-drivers/; free registration is required to download). This process lets you call the VISA-C functions from .NET code. This is usually preferable because it avoids the overhead of the VISA COM implementation and provides the functions in a way that's familiar to long-time users of the VISA-C API.

Agilent's .NET Header Files for VISA

After creating a .NET project using either C# or VB.NET, the first step is to add the header files to the project. To do this, you have two options:

  • Add them, which creates a copy of the files and places them in the project directory.
  • Link to them, which references them in the directory where they live rather than creating a new copy. The default directory the two header files live in after installation is c:/program files/visa/winnt/agvisa/include.

Linking to files requires that you right click the project in the Solution Explorer window and click the Add Existing Item... option under the Add submenu. The file-open dialog box that opens has a down-arrow inside the Open button, which gives the Link File option; see Figure 1, the Link File option of the Add Existing Item dialog box.

Clicking the Link File option creates a link to the file wherever it exists without copying it. After linking, you can identify the file as linked by observing that a small arrow appears in the lower-left corner of the file's icon in the Solution Explorer window. Linking to a file is the best option if you do not plan on changing it, or if you want any changes to be shared across a number of projects.

Once the file is added, you can begin writing VISA I/O code. The functions do not exist in a namespace so calling them does not require any import statements. Listing Two, a VB.NET program that uses the Agilent VISA-C .NET header file, grabs the screen image of an oscilloscope and downloads it to the PC, saving it as a GIF file.

The viStatusDesc function used in the CheckStatus method returns a string description of the last error on the current thread if such a description is available. The data is put into a buffer provided by the caller. The System.Text.StringBuilder object with a Capacity property value as big as the largest possible message best represents such a parameter, so that is what the method expects.

The viSetAttribute function and its reciprocal viGetAttribute function are used to set and get properties of the I/O session. The visa.h C header file for VISA-C declares the two functions:

ViStatus _VI_FUNC viSetAttribute (ViObject vi,
ViAttr attrName, ViAttrState attrValue);
ViStatus _VI_FUNC viGetAttribute (ViOb ject vi,
ViAttr attrName, void _VI_PTR attrValue);

The ViAttrState type is a typedef of an unsigned 32-bit integer. Agilent chose to represent these functions as several overloads in the .NET header files for the VISA-C library. The viSetAttribute function is used to send signed and unsigned 8-, 16-, and 32-bit integers for various VISA properties. The viGetAttribute function is used to retrieve the types set by viSetAttribute and also to retrieve strings into a preallocated buffer whose address is provided by the caller in the attrValue parameter. Based on the type conversions from managed to unmanaged code, the overloads in Listing Three were chosen for the .NET header files.

The correct version of these functions is available for any VISA attribute type, but if you use the wrong variable type for a particular VISA attribute type, the .NET runtime will not detect this logic error. There is little risk for the viSetAttribute function since all the parameters are integral and the VISA-C library will not overwrite any memory. Unfortunately, for viGetAttribute, some overloaded functions use pointer parameters that the VISA-C library will dereference and write into the dereferenced memory space, meaning that undefined behavior could result from the misuse of this function.

viPrintf and the corresponding viScanf are the main formatted I/O functions in VISA. They correspond to the C runtime library's printf and scanf functions, and take variable argument lists (see the accompanying text box entitled "Variable Argument Lists in .NET and Unmanaged Interoperability"). They extend the supported data types of the C runtime functions to include serialized data formats commonly seen in instrument communication, such as framed binary data structures called "IEEE 488.2 Definite-Length Binary Data blocks." Finally, they take a VISA session parameter instead of a file handle. There are also versions of the functions, viSPrintf and viSScanf, that write out to a string buffer parameter rather than to the VISA session.

Because there are several possible C primitive data types that can be passed, such as individual integral values (for in data) and pointers to individual integral values (for out or in-out data), the possible overload list grows geometrically with the number of data parameters in the variable argument list. Agilent decided to provide one-variable versions of all the most common data parameters. To support this, there are a total of 60 overloads per file in both the C# and VB.NET versions of the header files. If there had been overloads for every combination of two parameters, there would have been hundreds of overloads per file.

There are several reasons we decided this was good enough. The majority of customer use of formatted I/O requires only one parameter per call. Users have the ability to add new overloaded formatted I/O functions based on the ones already presently targeted at common data combinations they use in their applications. Alternatively, they can read and write their data piecemeal, calling the formatted I/O functions repeatedly to read or write a complete message part by part.

The same caution that applied to the viGetAttribute and viSetAttribute functions applies to the formatted I/O functions: Because the .NET runtime cannot compare the format string parameter to the actual argument(s) passed on the call stack, calling these methods incorrectly is unsafe and can result in memory access violations.

Conclusion

The VISA-C in .NET header files provide a fast and simple method of accessing instrument I/O in .NET languages. Moreover, they provide an I/O API that will be very familiar to C/C++ or Visual Basic 6 programmers who move to a .NET language. The design decisions made for these header files illustrate some of the tradeoffs of calling older, unmanaged code from .NET environments.

Because these header files are simple, you must take the same care in calling these unmanaged functions from .NET as they were calling them from an unmanaged language such as C++. They must make sure for the overloaded and variable argument list functions that the parameter describing what the subsequent parameters will be, such as the format string, is consistent with those parameters.

Calling unmanaged code from .NET takes a programmer off the beaten path. There can be a number of gotchas and more than one way of doing the same thing, requiring careful consideration of the tradeoffs of each option. The greatest danger is probably being too clever, such as using undocumented .NET keywords and APIs. The VISA-C in .NET header files aim for the most direct translation of the C API into .NET rather than adding overhead or unnecessary complexity.

DDJ


Accessing Unmanaged DLL Functions From .NET Languages

While there are several ways of accessing C/C++ DLL functions in .NET, there's one standard way of getting access to such unmanaged functionality in C# and two ways in Visual Basic .NET. VB.NET was meant to be an upgrade to Visual Basic 6, which means Microsoft had to provide an upgrade path for the VB 6 C DLL interoperation mechanism—the Declare keyword. VB .NET has this keyword as well as the .NET attribute method of accessing C DLLs. Examples 1(a) and 1(b) are two equivalent declarations in VB.NET.

The Declare statement is more limited than the DllImportAttribute .NET attribute. For that reason and because the Declare keyword is not available in C#, the DllImportAttribute is preferable when source code that interoperates with both C# and VB .NET must be written.

The DllImportAttribute instructs .NET to use an unmanaged function to service a call to the method that the attribute is associated with. The attribute describes what file the unmanaged method is located in, the location in the file of the function's entry point, and any other information necessary to successfully build a call stack and pass parameters to the method.

Previous software reuse architectures, such as COM, focused on protecting programmers from themselves. COM had the concept of threading apartments—in theory, code could be made thread-safe simply by registering it as thread safe. In reality, apartments often created more problems than they solved. .NET did not follow this path, and calling unmanaged code from .NET does not cause thread switching. The thread context of the .NET program is passed directly into the unmanaged code, with the .NET infrastructure simply building the proper C or Pascal method call stack on top of the stack. Similarly, callbacks from unmanaged code are not marshaled across threads, meaning they may come back on a different thread than the one being used by the .NET application. This can cause runtime errors if the client application accesses methods that have a thread affinity such as many of the operations on GUI objects.

The parameters to the .NET method decorated with the DllImportAttribute attribute are used to determine what parameters to pass on or retrieve from the call stack. Each .NET value type such as integers and other built-in types such as .NET array types have standard marshaling behavior that are equivalent to common C/C++ data types. Using the ref or out keywords with the method's parameters will cause them to be marshaled by reference, passing the memory address rather than the value for the variable. Some .NET value types, such as integers, floating-point numbers, or single-dimensional arrays of those types, are even blittable, meaning they have the same memory layout in managed and unmanaged code and .NET does not need to do any time-consuming transformations or marshaling on them when calling unmanaged code.

.NET strings, by default, are passed by value, and attempts to modify such strings by the unmanaged code results in undefined behavior. The StringBuilder class has a useful marshaling behavior: It marshals as an array of C characters that is modifiable, the size of the array equaling the StringBuilder instance's Capacity property. Microsoft meant for StringBuilder objects to be used when unmanaged functions expect a preallocated character buffer parameter to be filled-in with string or character data by the unmanaged code.

While most primitive types have equivalents in .NET, some of these .NET equivalents are not supported by all .NET languages and are, therefore, not recommended. C# is one of the languages with the broadest compatibility with various primitive data types; and VB.NET is an example of a language with more restricted support for primitive types. Primitive types in .NET that are compatible with the widest set of .NET languages are said to be Common Language Specification (CLS) compliant. The biggest set of nonCLS-compliant primitive types are the unsigned integral types such as the System.Uint16 unsigned 16-bit integer. To support the broadest set of languages, Agilent made the VISA-C in .NET header files use signed integer types when the VISA-C function expects or returns unsigned integer types.

There is no need to provide an implementation for the method in the source code of C#, VB.NET, and the like; just provide an empty function and the .NET runtime will call the referenced unmanaged function. Such methods are typically declared static in C# or Shared in VB.NET because their behavior does not depend on any of the instance variables of the class in which they are defined.

—D.G.

Variable Argument Lists in .NET and Unmanaged Interoperability

Different programming technologies have struggled with providing powerful but easy-to-use formatted I/O. I/O formatting is turning various computer data types into streams of bytes that can be sent across the wire, such as across a serial port or across a TCPIP socket. It is inconvenient, tedious, and error prone to have a separate method call for each data element in a serial data transfer, so programming elements such as C's variable argument lists (like in C's printf function) were invented.

.NET has a different mechanism for variable argument lists—the params keyword. In the .NET intermediate language (IL, the assembly language of .NET), the parameters are turned into an array of value object references, and primitives like integers are "boxed" into object wrappers placed in the array.

Microsoft chose not to allow C-style variable argument lists through the params keyword for a number of reasons. The params keyword is not a good fit for C-style variable argument lists, it does not support out or ref passing of parameters, but C-style variable argument lists allow the values pointed to by the pointer arguments to change. Another reason why C-style variable argument lists are a bad fit for .NET is that because variable argument lists are type checked at runtime, there is a large risk of memory-access violations undetectable by the compiler that defeat the memory safety Microsoft tried to provide with the .NET programming technology.

That said, Microsoft did leave in some hidden keywords that allow C-style variable argument lists in .NET—the __arglist and __makeref keywords. A method declared with __arglist accepts a C-style variable argument list, and by wrapping a comma-separated list of variables in an __arglist() declaration, a variable-length argument list can be passed to such methods. If methods using __arglist are decorated with the DllImportAttribute, they can be used for calling such unmanaged DLL functions as printf or scanf. Reference parameters are passed by wrapping the variable name in a makeref() keyword, which is the equivalent of the C practice of putting an ampersand (&) in front of the variable to pass it its memory address rather than its value to the function being called.

These keywords were not used in the VISA header files in .NET for several reasons: The risks of memory access violations and the added burden of forcing you to understand the appropriate times to use keywords such as __arglist and __makeref were good reasons not to go down this route. Additionally, performance is a consideration. It has been reported that the performance of these hidden keywords is about five times worse than params, which makes it many times slower than a standard method call.


Listing One

#include <stdio.h>
#include <stdlib.h>
#include <visa.h>
#include <string.h>

void programExit(char *s)
{
    printf("%s/n",s);
    exit(-1);
}
int main(int argc, char *argv[])
{
    ViSession defaultRM;
    ViStatus status;
    ViSession sess;
    ViUInt32 retCount;
    ViString idnQuery = "*IDN?/n";
    char idnResponse[100];

    // Open Resource Manager that knows how to find, parse, and open resources
    status = viOpenDefaultRM(&defaultRM);
    if ( status != VI_SUCCESS ) { programExit("viOpenDefaultRM() failed");}
    // Open a session to the device, located on the network with hostname 
    // "arbGen.agilent.com" and having a device name of "inst0"
    status = viOpen(defaultRM,"TCPIP::arbGen.agilent.com::inst0::INSTR",
                                        VI_EXCLUSIVE_LOCK,VI_NULL,&sess);
    if ( status != VI_SUCCESS ) { programExit("viOpen() failed");}
    // Perform a clear to put the device I/O in a known state.
    status = viClear(sess);
    if ( status != VI_SUCCESS ) { programExit("viClear() failed");}
    // Ask the instrument to identify itself
    status = viWrite(sess,idnQuery,(ViUInt32)strlen(idnQuery),&retCount);
    if ( status != VI_SUCCESS ) { programExit("viWrite() failed");}
    if ( retCount != strlen(idnQuery)) { programExit("viWrite() failed/n"); }
    // read back the identification string
    status = viRead(sess,idnResponse,sizeof(idnResponse),&retCount);
    if ( status != VI_SUCCESS ) { programExit("viRead() failed/n");}
    // null-terminate the response so it can be printed
    idnResponse[retCount] = 0;
    printf("%s = %s/n","TCPIP::arbGen.agilent.com::inst0::INSTR",idnResponse);
    // Close the I/O resources  
    viClose(sess);
    viClose(defaultRM);
    return 0;
}
Back to article


Listing Two
Module Module1
    Sub Main()
        ' Run sample program with an instrument address of "GPIB::12::INSTR"
        RunExample("GPIB0::12::INSTR")
    End Sub
    Private Sub RunExample(ByVal instrAddress As String)
        ' Declare Variables used in the program
        Dim status As Integer      'VISA function status return code
        Dim defrm As Integer = 0   'Session to Default Resource Manager
        Dim vi As Integer = 0      'Session to instrument
        Dim x As Integer        'Loop Variable
        Dim ResultsArray(50000) As Byte 'results array, holds a GIF
        Dim length As Integer      'Number of bytes returned from instrument
        Dim headerlength As Integer 'length of header
        ' the file to write the picture
        Dim fs As System.IO.FileStream = Nothing
        'Set the default number of bytes that will be contained in the
        'ResultsArray to 50,000 (50kB)
        length = 50000
        Try
            If System.IO.File.Exists("picture.gif") Then
                System.IO.File.Delete("picture.gif")
            End If
            ' Open the default resource manager session
            status = visa32.viOpenDefaultRM(defrm)
            ' Open the session.  For GPIB, the address string looks like: 
            '       GPIB0::18::INSTR
            ' For PSA, to use LAN, change the string to
            ' "TCPIP0::xxx.xxx.xxx.xxx::inst0::INSTR" where 
            ' xxxxx is the IP address
            status = visa32.viOpen(defrm, "instrAddress", 0, 0, vi)
            CheckStatus(defrm, status)
            ' Set the I/O timeout to fifteen seconds
            status = visa32.viSetAttribute(vi,visa32.VI_ATTR_TMO_VALUE,15000)
            CheckStatus(vi, status)
            'Store the current screen image on flash as C:PICTURE.GIF
            status = visa32.viPrintf(vi,":MMEM:STOR:SCR 'C:PICTURE.GIF'" & vbLf)
            CheckStatus(vi, status)
            'Grab the screen image file from the instrument
            status = visa32.viPrintf(vi, ":MMEM:DATA? 'C:PICTURE.GIF'" & vbLf)
            CheckStatus(vi, status)
            ' We're reading this as raw binary, although it is a IEEE 488.2
            ' binary block containing byte data.  We could've used
            ' "%#b" format string, and the byte array would not contain the
            ' IEEE binary block header.
            status = visa32.viScanf(vi, "%#y", length, ResultsArray)
            CheckStatus(vi, status)
            'Delete the tempory file on the flash named C:PICTURE.GIF
            status = visa32.viPrintf(vi, ":MMEM:DEL 'C:PICTURE.GIF'" & vbLf)
            CheckStatus(vi, status)
            'Close the vi session and the resource manager session
            Call visa32.viClose(vi)
            vi = 0
            Call visa32.viClose(defrm)
            defrm = 0
            'Store the results in a text file
            fs = _
                        New System.IO.FileStream("picture.gif", _
                        IO.FileMode.OpenOrCreate)
            Dim zeroVal() As Char = {"0"}
            Dim zeroValByte() As Byte
            zeroValByte = System.Text.Encoding.ASCII.GetBytes(zeroVal)
            headerlength = ResultsArray(1) - zeroValByte(0) + 2
            fs.Write(ResultsArray, headerlength, length - 2 - headerlength)
        Catch err As System.ApplicationException
            MsgBox("*** Error : " & err.Message, vbExclamation, _
                                        "VISA Error Message")
            Exit Sub
        Catch err As System.SystemException
            MsgBox("*** Error : " & err.Message, vbExclamation, _
                                        "System Error Message")
            Exit Sub
        Catch err As System.Exception
            Debug.Fail("Unexpected Error")
            MsgBox("*** Error : " & err.Message, vbExclamation, _
                                        "Unexpected Error")
            Exit Sub
        Finally
            If Not fs Is Nothing Then fs.Close()
            If vi <> 0 Then
                Call visa32.viClose(vi)
            End If
            If defrm <> 0 Then
                Call visa32.viClose(defrm)
            End If
        End Try
    End Sub
    Private Sub CheckStatus(ByVal vi As Integer, ByVal status As Integer)
        If (status < visa32.VI_SUCCESS) Then
            Dim err As System.Text.StringBuilder = New _
                            System.Text.StringBuilder(256)
            visa32.viStatusDesc(vi, status, err)
            Throw New ApplicationException(err.ToString())
        End If
    End Sub
End Module
Back to article


Listing Three
#Region "viGetAttribute Overloads"
    <DllImportAttribute("VISA32.DLL", EntryPoint:="#133", _
                        ExactSpelling:=True, CharSet:=CharSet.Ansi, _
                        SetLastError:=True, _
                        CallingConvention:=CallingConvention.Winapi)> _
    Public Function viGetAttribute(ByVal vi As Integer, _
                                    ByVal attrName As Integer, _
                                    ByRef attrValue As Byte) As Integer
    End Function
    <DllImportAttribute("VISA32.DLL", EntryPoint:="#133", _
                        ExactSpelling:=True, CharSet:=CharSet.Ansi, _
                        SetLastError:=True, _
                        CallingConvention:=CallingConvention.Winapi)> _
    Public Function viGetAttribute(ByVal vi As Integer, _
                                    ByVal attrName As Integer, _
                                    ByRef attrValue As Short) As Integer
    End Function
    <DllImportAttribute("VISA32.DLL", EntryPoint:="#133", _
                        ExactSpelling:=True, CharSet:=CharSet.Ansi, _
                        SetLastError:=True, _
                        CallingConvention:=CallingConvention.Winapi)> _
    Public Function viGetAttribute(ByVal vi As Integer, _
                                    ByVal attrName As Integer, _
                                    ByRef attrValue As Integer) As Integer
    End Function
    <DllImportAttribute("VISA32.DLL", EntryPoint:="#133", _
                    ExactSpelling:=True, CharSet:=CharSet.Ansi, _
                    SetLastError:=True, _
                    CallingConvention:=CallingConvention.Winapi)> _
    Public Function viGetAttribute(ByVal vi As Integer, _
                    ByVal attrName As Integer, _
                    ByVal attrValue As System.Text.StringBuilder) As Integer
    End Function
#End Region

#Region "viSetAttribute Overloads"
    <DllImportAttribute("VISA32.DLL", EntryPoint:="#134", _
                    ExactSpelling:=True, CharSet:=CharSet.Ansi, _
                    SetLastError:=True, _
                    CallingConvention:=CallingConvention.Winapi)> _
    Public Function viSetAttribute(ByVal vi As Integer, _
                                    ByVal attrName As Integer, _
                                    ByVal attrValue As Byte) As Integer
    End Function
    <DllImportAttribute("VISA32.DLL", EntryPoint:="#134", _
                    ExactSpelling:=True, CharSet:=CharSet.Ansi, _
                    SetLastError:=True, _
                    CallingConvention:=CallingConvention.Winapi)> _
    Public Function viSetAttribute(ByVal vi As Integer, _
                                    ByVal attrName As Integer, _
                                    ByVal attrValue As Short) As Integer
    End Function
    <DllImportAttribute("VISA32.DLL", EntryPoint:="#134", _
                        ExactSpelling:=True, CharSet:=CharSet.Ansi, _
                        SetLastError:=True, _
                        CallingConvention:=CallingConvention.Winapi)> _
    Public Function viSetAttribute(ByVal vi As Integer, _
                                    ByVal attrName As Integer, _
                                    ByVal attrValue As Integer) As Integer
    End Function
#End Region

(a)

<DllImportAttribute("VISA32.DLL", EntryPoint:="#141", ExactSpelling:=True, CharSet:=CharSet.Ansi, SetLastError:=True, CallingConvention:=CallingConvention.Winapi)> _
Public Shared Function viOpenDefaultRM(ByRef sesn As Integer) As Integer
End Function


(b)
Public Declare Function viOpenDefaultRM2 Lib "VISA32.DLL" Alias "#141" (ByRef sesn As Integer) As Integer

Example 1: Declaring unmanaged functions in VB.NET. (a) Using .NET attributes; (b) using VB-style declare.

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值