c# 调用 研华库函数中 C++ 非托管 Dll 一例(包含指针成员的结构体的调用)

2021腾讯云限时秒杀,爆款1核2G云服务器298元/3年!(领取2860元代金券),
地址https://cloud.tencent.com/act/cps/redirect?redirect=1062

2021阿里云最低价产品入口+领取代金券(老用户3折起),
入口地址https://www.aliyun.com/minisite/goods

最近用到研华的一款高精度AD转换卡,PCL816,需要使用C#调用研华库函数中的动态库文件Adsapi32.dll中的函数,参考了网上关于C#调用非托管dll的方法。

其中的难点主要是数据类型的匹配问题。基本的方法在MSDN中关于 c# 调用 C++ 非托管 Dll 的主题有详细的原理说明和例程,我博客上已经转了MSDN这一篇,这里就不赘述了。网上也有很多类型对应关系表,下面是比较好的一个。

 

C#调用C++编写的Win32 DLL文件时参数对应表

Win32 Types

CLR Type

char, INT8, SBYTE, CHAR

System.SByte

short, short int, INT16, SHORT

System.Int16

int, long, long int, INT32, LONG32, BOOL , INT

INT System.Int32

__int64, INT64, LONGLONG

System.Int64

unsigned char, UINT8, UCHAR , BYTE

System.Byte

unsigned short, UINT16, USHORT, WORD, ATOM, WCHAR , __wchar_t

System.UInt16

unsigned, unsigned int, UINT32, ULONG32, DWORD32, ULONG, DWORD, UINT

System.UInt32

unsigned __int64, UINT64, DWORDLONG, ULONGLONG

System.UInt64

float, FLOAT

System.Single

double, long double, DOUBLE

System.Double

 

下面主要说一下调用研华的库函数时遇到的一个新问题。即包含指针类型成员的结构体如何调用的问题。下面是最终成功的源码片段。

        /// <summary>

        /// 获得输入模拟电压,获取指定句柄设备的输入模拟电压结构体

        /// </summary>

        /// <param name="DriverHandle">设备句柄</param>

        /// <param name="lpAIVoltageIn">输入模拟电压</param>

        /// <returns>成功或错误号</returns>

        [DllImport("Adsapi32.dll")]

        public static extern int DRV_AIVoltageIn(int DriverHandle, ref tagPT_AIVoltageIn lpAIVoltageIn);

/// <summary>

        /// 模拟电压结构体

        /// </summary>

        public struct tagPT_AIVoltageIn

        {

            public ushort chan;         //通道

            public ushort gain;         //增益码:参考用户手册中的电压范围

            public ushort TrigMode;     //触发模式:0,内部触发;1,外部触发

            public IntPtr voltage;      //输入模拟电压的指针

        }

在C++函数原型中,tagPT_AIVoltageIn.voltage是一个float* voltage;形式定义的,这里要用C#中特有的指针或句柄类型IntPtr。

调用语句为

dwErrCde = DRV_AIVoltageIn(lDriverHandle, ref ptAIVoltageIn);//读取输入模拟电压

但是这时仍然会提示尝试写入受保护内存。其原因是在调用前没有为指针分配内存。使用下面的初始化语句。

tagPT_AIVoltageIn ptAIVoltageIn = new tagPT_AIVoltageIn();  //电压结构体

ptAIVoltageIn.voltage = Marshal.AllocHGlobal(sizeof(float));//为输入模拟电压指针分配内存,其大小为一个float型变量的大小。

定义一个float型的数组来接收这个指针所指地址的值,代码如下:

float[] fVoltage = new float[1];                            //输入模拟电压

Marshal.Copy(ptAIVoltageIn.voltage, fVoltage, 0, 1);    //从指针地址拷贝到数组中

终于成功接收到数据了!

 

下附我将研华示例代码中的一个C++命令行通用程序改写后的C#命令行程序的源码。

 

using System;

using System.Runtime.InteropServices;

using System.Threading;

 

namespace AD816

{

    class Program

    {

        #region 研华库函数

        /// <summary>

        /// 打开设备,由设备号得到设备句柄

        /// </summary>

        /// <param name="DeviceNum">设备号</param>

        /// <param name="DriverHandle">设备句柄</param>

        /// <returns>成功或错误号</returns>

        [DllImport("Adsapi32.dll")]

        public static extern int DRV_DeviceOpen(uint DeviceNum, ref int DriverHandle);

 

        /// <summary>

        /// 关闭设备,关闭指定句柄的设备

        /// </summary>

        /// <param name="DriverHandle">设备句柄</param>

        /// <returns>成功或错误号</returns>

        [DllImport("Adsapi32.dll")]

        public static extern int DRV_DeviceClose(ref int DriverHandle);

 

        /// <summary>

        /// 获得错误消息,获得指定错误号的错误消息

        /// </summary>

        /// <param name="lError">错误号</param>

        /// <param name="lpszErrMsg">错误消息</param>

        [DllImport("Adsapi32.dll")]

        public static extern void DRV_GetErrorMessage(ref int lError, string lpszErrMsg);

 

        /// <summary>

        /// 配置设备,由配置结构体中的数据配置指定句柄的设备

        /// </summary>

        /// <param name="DriverHandle">设备句柄</param>

        /// <param name="lpAIConfig">配置结构体</param>

        /// <returns>成功或错误号</returns>

        [DllImport("Adsapi32.dll")]

        public static extern int DRV_AIConfig(int DriverHandle, ref tagPT_AIConfig lpAIConfig);

 

        /// <summary>

        /// 获得输入模拟电压,获取指定句柄设备的输入模拟电压结构体

        /// </summary>

        /// <param name="DriverHandle">设备句柄</param>

        /// <param name="lpAIVoltageIn">输入模拟电压</param>

        /// <returns>成功或错误号</returns>

        [DllImport("Adsapi32.dll")]

        public static extern int DRV_AIVoltageIn(int DriverHandle, ref tagPT_AIVoltageIn lpAIVoltageIn);

 

        /// <summary>

        /// 配置结构体

        /// </summary>

        public struct tagPT_AIConfig

        {

            public ushort DasChan;      //通道

            public ushort DasGain;      //增益

        }

 

        /// <summary>

        /// 模拟电压结构体

        /// </summary>

        public struct tagPT_AIVoltageIn

        {

            public ushort chan;         //通道

            public ushort gain;         //增益码:参考用户手册中的电压范围

            public ushort TrigMode;     //触发模式:0,内部触发;1,外部触发

            public IntPtr voltage;      //输入模拟电压的指针

        }

 

        /// <summary>

        /// 成功标志

        /// </summary>

       

        const uint SUCCESS = 0;

        #endregion

 

        static void Main(string[] args)

        {

            int dwErrCde = 0;                                           //成功或错误号

            uint lDevNum = 0;                                           //设备号0

            int lDriverHandle=0;                                        //设备句柄

            ushort usChan = 0;                                          //通道0

            ushort usGain = 0;                                          //增益码0

            ushort usMode = 0;                                          //触发模式0,内部触发

 

            tagPT_AIConfig ptAIConfig = new tagPT_AIConfig();           //配置结构体

            tagPT_AIVoltageIn ptAIVoltageIn = new tagPT_AIVoltageIn();  //电压结构体

 

            ptAIVoltageIn.voltage = Marshal.AllocHGlobal(sizeof(float));//为输入模拟电压指针分配内存

            float[] fVoltage = new float[1];                            //输入模拟电压

           

            dwErrCde = DRV_DeviceOpen(lDevNum, ref lDriverHandle);      //打开设备 

 

            if (dwErrCde != SUCCESS)                                    //打开失败

            {

                ErrorHandler(dwErrCde);                                 //打印错误

                Console.WriteLine("Program terminated!/n");

 

                Console.WriteLine("Press any key to exit....");

                Console.ReadLine();

                return;

            }

 

            Console.WriteLine("open device " + lDevNum + " success");   //打开成功

 

            ptAIConfig.DasChan = usChan;                                //设置通道

            ptAIConfig.DasGain = usGain;                                //设置增益

 

            dwErrCde = DRV_AIConfig(lDriverHandle, ref ptAIConfig);     //配置设备

 

            if (dwErrCde != SUCCESS)                                    //配置失败

            {

                ErrorStop(ref lDriverHandle, dwErrCde);                 //停止设备

                return;

            }

 

            ptAIVoltageIn.chan = usChan;                                // 输入通道

            ptAIVoltageIn.gain = usGain;                                // 增益码

            ptAIVoltageIn.TrigMode = usMode;                            // 触发模式

 

            Console.WriteLine("open chanel " + usChan + " success");    //配置成功

 

 

            for (int i = 0; i < 1000; i++)                              //读取1000次,间隔0.1s

            {

                dwErrCde = DRV_AIVoltageIn(lDriverHandle, ref ptAIVoltageIn);//读取输入模拟电压

 

                if (dwErrCde != SUCCESS)                                //读取出错,退出

                {

                    ErrorStop(ref lDriverHandle, dwErrCde);

                    return;

                }

 

                Marshal.Copy(ptAIVoltageIn.voltage, fVoltage, 0, 1);    //从指针地址拷贝到数组中

                Console.WriteLine(fVoltage[0]);                         //打印模拟输入电压

                Thread.Sleep(100);                                      //延时0.1s

            }

 

            dwErrCde = DRV_DeviceClose(ref lDriverHandle);

 

            if (dwErrCde != SUCCESS)

            {

                ErrorStop(ref lDriverHandle, dwErrCde);

                return;

            }

 

            Console.ReadLine();

        }

 

        /// <summary>

        /// 打印指定错误号的错误信息

        /// </summary>

        /// <param name="dwErrCde">错误号</param>

        static void ErrorHandler(int dwErrCde)

        {

            string szErrMsg = string.Empty;

 

            ///读错误消息

            DRV_GetErrorMessage(ref dwErrCde, szErrMsg);

            Console.WriteLine("/nError(%d): %s/n", dwErrCde & 0xffff, szErrMsg);

        }

 

        /// <summary>

        /// 错误退出

        /// </summary>

        /// <param name="pDrvHandle">设备句柄</param>

        /// <param name="dwErrCde">错误号</param>

        static void ErrorStop(ref int pDrvHandle, int dwErrCde)

        {

            //打印错误信息

            ErrorHandler(dwErrCde);

            Console.WriteLine("Program terminated!/n");

 

            //关闭设备

            DRV_DeviceClose(ref pDrvHandle);

 

            Console.WriteLine("Press any key to exit....");

            Console.ReadLine();

        }

    }

}

 

 

推荐:C# 调用Dll中非托管C++代码时,函数参数的类型对照

[在上一篇blog(工具(Tray Friend):将任何程序,最小化到系统托盘 )中。 使用C#调用了很多非托管的C++代码。   现在就把“C# 调用Dll中非托管C++代码时� ...]

相关推荐