最近用到研华的一款高精度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();
}
}
}