大家在实际工作学习C#的时候,可能会问:为什么我们要为一些已经存在的功能(比如Windows中的一些功能,C++中已经编写好的一些方法)要重新编写代码,C#有没有方法可以直接都用这些原本已经存在的功能呢?答案是肯定的,大家可以通过C#中的DllImport直接调用这些功能。
DllImport所在的名字空间usingSystem.Runtime.InteropServices;
MSDN中对DllImportAttribute的解释是这样的:可将该属性应用于方法。DllImportAttribute属性提供对从非托管DLL导出的函数进行调用所必需的信息。作为最低要求,必须提供包含入口点的DLL的名称。
DllImport属性定义如下:
namespaceSystem.Runtime.InteropServices
{
[AttributeUsage(AttributeTargets.Method)]
publicclassDllImportAttribute:System.Attribute
{
publicDllImportAttribute(stringdllName){...}
publicCallingConventionCallingConvention;
publicCharSetCharSet;
publicstringEntryPoint;
publicboolExactSpelling;
publicboolPreserveSig;
publicboolSetLastError;
publicstringValue{get{...}}
}
}
说明:
1、DllImport只能放置在方法声明上。
2、DllImport具有单个定位参数:指定包含被导入方法的dll名称的dllName参数。
3、DllImport具有五个命名参数:
a、CallingConvention参数指示入口点的调用约定。如果未指定CallingConvention,则使用默认值CallingConvention.Winapi。
b、CharSet参数指示用在入口点中的字符集。如果未指定CharSet,则使用默认值CharSet.Auto。
c、EntryPoint参数给出dll中入口点的名称。如果未指定EntryPoint,则使用方法本身的名称。
d、ExactSpelling参数指示EntryPoint是否必须与指示的入口点的拼写完全匹配。如果未指定ExactSpelling,则使用默认值false。
e、PreserveSig参数指示方法的签名应当被保留还是被转换。当签名被转换时,它被转换为一个具有HRESULT返回值和该返回值的一个名为retval的附加输出参数的签名。如果未指定PreserveSig,则使用默认值true。
f、SetLastError参数指示方法是否保留Win32"上一错误"。如果未指定SetLastError,则使用默认值false。
4、它是一次性属性类。
5、此外,用DllImport属性修饰的方法必须具有extern修饰符。
DllImport的用法:
DllImport("MyDllImport.dll")]
privatestaticexternintmySum(inta,intb);
一在C#程序设计中使用Win32类库
常用对应类型:
1、DWORD是4字节的整数,因此我们可以使用int或uint作为C#对应类型。
2、bool类型与BOOL对应。
示例一:调用Beep()API来发出声音
Beep()是在kernel32.lib中定义的,在MSDN中的定义,Beep具有以下原型:
BOOLBeep(DWORDdwFreq,//声音频率
DWORDdwDuration//声音持续时间);
用C#编写以下原型:
[DllImport("kernel32.dll")]
publicstaticexternboolBeep(intfrequency,intduration);
示例二:枚举类型和常量
MessageBeep()是在user32.lib中定义的,在MSDN中的定义,MessageBeep具有以下原型:
BOOLMessageBeep(UINTuType//声音类型
);
用C#编写一下原型:
publicenumBeepType
{
SimpleBeep=-1,
IconAsterisk=0x00000040,
IconExclamation=0x00000030,
IconHand=0x00000010,
IconQuestion=0x00000020,
Ok=0x00000000,
}
uType参数实际上接受一组预先定义的常量,对于uType参数,使用enum类型是合乎情理的。
[DllImport("user32.dll")]
publicstaticexternboolMessageBeep(BeepTypebeepType);
示例三:处理结构
有时我需要确定我笔记本的电池状况。Win32为此提供了电源管理函数,搜索MSDN可以找到GetSystemPowerStatus()函数。
BOOLGetSystemPowerStatus(
LPSYSTEM_POWER_STATUSlpSystemPowerStatus
);
此函数包含指向某个结构的指针,我们尚未对此进行过处理。要处理结构,我们需要用C#定义结构。我们从非托管的定义开始:
typedefstruct_SYSTEM_POWER_STATUS{
BYTE ACLineStatus;
BYTE BatteryFlag;
BYTE BatteryLifePercent;
BYTE Reserved1;
DWORD BatteryLifeTime;
DWORD BatteryFullLifeTime;
}SYSTEM_POWER_STATUS,*LPSYSTEM_POWER_STATUS;
然后,通过用C#类型代替C类型来得到C#版本。
structSystemPowerStatus
{
byteACLineStatus;
bytebatteryFlag;
bytebatteryLifePercent;
bytereserved1;
intbatteryLifeTime;
intbatteryFullLifeTime;
}
这样,就可以方便地编写出C#原型:
[DllImport("kernel32.dll")]
publicstaticexternboolGetSystemPowerStatus(
refSystemPowerStatussystemPowerStatus);
在此原型中,我们用“ref”指明将传递结构指针而不是结构值。这是处理通过指针传递的结构的一般方法。
此函数运行良好,但是最好将ACLineStatus和batteryFlag字段定义为enum:
enumACLineStatus:byte
{
Offline=0,
Online=1,
Unknown=255,
}
enumBatteryFlag:byte
{
High=1,
Low=2,
Critical=4,
Charging=8,
NoSystemBattery=128,
Unknown=255,
}
请注意,由于结构的字段是一些字节,因此我们使用byte作为该enum的基本类型
示例四:处理字符串
二C#中调用C++代码
int类型
[DllImport(“MyDLL.dll")]
//返回个int类型
publicstaticexternintmySum(inta1,intb1);
//DLL中申明
extern“C”__declspec(dllexport)intWINAPImySum(inta2,intb2)
{
//a2b2不能改变a1b1
//a2=..
//b2=...
returna+b;
}
//参数传递int类型
publicstaticexternintmySum(refinta1,refintb1);
//DLL中申明
extern“C”__declspec(dllexport)intWINAPImySum(int*a2,int*b2)
{
//可以改变a1,b1
*a2=...
*b2=...
returna+b;
}
DLL需传入char*类型
[DllImport(“MyDLL.dll")]
//传入值
publicstaticexternintmySum(stringastr1,stringbstr1);
//DLL中申明
extern“C”__declspec(dllexport)intWINAPImySum(char*astr2,char*bstr2)
{
//改变astr2bstr2,astr1bstr1不会被改变
returna+b;
}
DLL需传出char*类型
[DllImport(“MyDLL.dll")]
//传出值
publicstaticexternintmySum(StringBuilderabuf,StringBuilderbbuf);
//DLL中申明
extern“C”__declspec(dllexport)intWINAPImySum(char*astr,char*bstr)
{
//传出char* 改变astr bstr-->abuf,bbuf可以被改变
returna+b;
}
DLL回调函数
BOOLEnumWindows(WNDENUMPROClpEnumFunc,LPARAMlParam)
usingSystem;
usingSystem.Runtime.InteropServices;
publicdelegateboolCallBack(inthwnd,intlParam);//定义委托函数类型
publicclassEnumReportApp
{
[DllImport("user32")]
publicstaticexternintEnumWindows(CallBackx,inty);
publicstaticvoidMain(){
CallBackmyCallBack=newCallBack(EnumReportApp.Report);EnumWindows(myCallBack,0);
}
publicstaticboolReport(inthwnd,intlParam)
{
Console.Write("Windowhandleis");
Console.WriteLine(hwnd);returntrue;
}
}
DLL传递结构
BOOLPtInRect(constRECT*lprc,POINTpt);
usingSystem.Runtime.InteropServices;
[StructLayout(LayoutKind.Sequential)]
publicstructPoint{
publicintx;
publicinty;
}
[StructLayout(LayoutKind.Explicit)]
publicstructRect
{
[FieldOffset(0)]publicintleft;
[FieldOffset(4)]publicinttop;
[FieldOffset(8)]publicintright;
[FieldOffset(12)]publicintbottom;
}
ClassXXXX{
[DllImport("User32.dll")]
publicstaticexternboolPtInRect(refRectr,Pointp);
}
---------------------------------------------------------------
(2)关于unsignedchar*pBuffer,这个unsignedchar*其实有2个转换可选,有时可以使用byte[],有时则是StringBuilder,这就要集体问题具体分析了。例如:
intGetErrorMessage_ex6(intErrorNr,unsignedlongBufLen,unsignedchar*pBuffer);
voidcopy_buffer_ex6(unsignedchar*pTargetBuffer,unsignedchar*pSourceBuffer,unsignedlongAmount);
前者就转换成StringBuilder后者是byte[]。
(3)有些变量虽是整型但是可以用枚举,而且用枚举感觉更合适
例如
intdb_read_ex6(unsignedshortBlkNr,unsignedcharDatType,unsigned
shortStartNr,unsignedlong*pAmount,unsignedlongBufLen,unsigned
char*pReadBuffer,unsignedlong*pDatLen)中,unsignedcharDatType其实指的是“0x02=BYTE,0x04=WORD,0x06=DWORDdefault:DatType=0x02”等数据类型
,因此可以翻译成
publicenumDatType:byte//PLC数据类型
{
BYTE=0x02,
WORD=0x04,
DWORD=0x06,
}
(4)对了,如果是对象型的引用,比如unsignedchar*转成byte[],是不需要加ref,但如果是c++int转c#int则要加ref关键字。
要说明的就是这些,下面请各位看官看看我的转换代码吧,还请见教:
publicclassProdave6
{
#region常值定义(用于极限值)
publicconstintMAX_CONNECTIONS=64;//64isdefaultinPRODAVE
publicconstintMAX_DEVNAME_LEN=128;//e.g."S7ONLINE"
publicconstintMAX_BUFFERS=64;//64forblk_read()andblk_write()
publicconstintMAX_BUFFER=65536;//Transferbufferforerrortext)
#endregion
#region结构体定义
publicstructCON_TABLE_TYPE//待连接plc地址属性表
{
[MarshalAs(UnmanagedType.ByValArray,SizeConst=6)]
//publicCON_ADR_TYPEAdr;//connectionaddress
publicbyte[]Adr;//connectionaddress
//MPI/PBstationaddress(2)
//IPaddress(192.168.0.1)
//MACaddress(08-00-06-01-AA-BB)
publicbyteAdrType;//Typeofaddress:MPI/PB(1),IP(2),MAC(3)
publicbyteSlotNr;//Slotnumber
publicbyteRackNr;//Racknumber
}
publicenumDatType:byte//PLC数据类型
{
BYTE=0x02,
WORD=0x04,
DWORD=0x06,
}
publicenumFieldType:byte//PLC区域类型
{
//ValuetypesasASCIIcharacters区域类型对应的ASCII字符
//databyte(d/D)
d=100,
D=68,
//inputbyte(e/E)
e=101,
E=69,
//outputbyte(a/A)
a=97,
A=65,
//memorybyte(m/M)
m=109,
M=77,
//timerword(t/T),
t=116,
T=84,
}
#endregion
#regionPLC基本函数
[DllImport("Prodave6.dll")]//连接PLC操作
//参数:连接号(0-63)、常值"S7ONLINE"、待连接plc地址属性表长度(字节为单位,常值9)、待连接plc地址属性表
publicexternstaticintLoadConnection_ex6(intConNr,stringpAccessPoint,intConTableLen,ref CON_TABLE_TYPEpConTable);
[DllImport("Prodave6.dll")]//断开PLC操作
//参数:连接号(0-63)
publicexternstaticintUnloadConnection_ex6(UInt16ConNr);
[DllImport("Prodave6.dll")]//激活PLC连接操作
//参数:连接号(0-63)
publicexternstaticintSetActiveConnection_ex6(UInt16ConNr);
[DllImport("Prodave6.dll")]//PLCdb区读取操作
//参数:datablock号、要读取的数据类型、起始地址号、需要读取类型的数量、缓冲区长度(字节为单位)、缓冲区、缓冲区数据交互的长度
publicexternstaticintdb_read_ex6(UInt16BlkNr,DatTypeDType,UInt16StartNr,refUInt32pAmount,UInt32BufLen,
byte[]pBuffer,refUInt32pDatLen);
[DllImport("Prodave6.dll")]//PLCdb区写入操作
//参数:datablock号、要写入的数据类型、起始地址号、需要写入类型的数量、缓冲区长度(字节为单位)、缓冲区
publicexternstaticintdb_write_ex6(UInt16BlkNr,DatTypeType,UInt16StartNr,refUInt32pAmount,UInt32BufLen,
byte[]pBuffer);
[DllImport("Prodave6.dll")]//PLC任意区读取操作
//参数:要读取的区类型、datablock号(DB区特有,默认为0)、起始地址号、需要读取类型的数量、
//缓冲区长度(字节为单位)、缓冲区、缓冲区数据交互的长度
publicexternstaticintfield_read_ex6(FieldTypeFType,UInt16BlkNr,UInt16StartNr,UInt32pAmount,UInt32BufLen,
byte[]pBuffer,refUInt32pDatLen);
[DllImport("Prodave6.dll")]//PLC任意区写入操作
//参数:要写入的区类型、datablock号(DB区特有,默认为0)、起始地址号、需要写入类型的数量、
//缓冲区长度(字节为单位)、缓冲区
publicexternstaticintfield_write_ex6(FieldTypeFType,UInt16BlkNr,UInt16StartNr,UInt32pAmount,UInt32BufLen,
byte[]pBuffer);
[DllImport("Prodave6.dll")]//PLCM区某字节的某位读取操作
//参数:M区字节号、位号、当前的值(0/1)
publicexternstaticintmb_bittest_ex6(UInt16MbNr,UInt16BitNr,refintpValue);
[DllImport("Prodave6.dll")]//PLCM区某字节的某位写入操作
//参数:M区字节号、位号、要写入的值(0/1)
publicexternstaticintmb_setbit_ex6(UInt16MbNr,UInt16BitNr,byteValue);
#endregion
#regionPLC200用数据传输函数
[DllImport("Prodave6.dll")]//200系列PLC任意区读取操作
//参数:要读取的区类型、datablock号(DB区特有,默认为0)、起始地址号、需要读取类型的数量、
//缓冲区长度(字节为单位)、缓冲区、缓冲区数据交互的长度
publicexternstaticintas200_field_read_ex6(FieldTypeFType,UInt16BlkNr,UInt16StartNr,UInt32pAmount,UInt32BufLen,
byte[]pBuffer,refUInt32pDatLen);
[DllImport("Prodave6.dll")]//200系列PLC任意区写入操作
//参数:要写入的区类型、datablock号(DB区特有,默认为0)、起始地址号、需要写入类型的数量、
//缓冲区长度(字节为单位)、缓冲区
publicexternstaticintas200_field_write_ex6(FieldTypeFType,UInt16BlkNr,UInt16StartNr,UInt32pAmount,UInt32BufLen,
byte[]pBuffer);
[DllImport("Prodave6.dll")]//200系列PLCM区某字节的某位读取操作
//参数:M区字节号、位号、当前的值(0/1)
publicexternstaticintas200_mb_bittest_ex6(UInt16MbNr,UInt16BitNr,refintpValue);
[DllImport("Prodave6.dll")]//200系列PLCM区某字节的某位写入操作
//参数:M区字节号、位号、要写入的值(0/1)
publicexternstaticintas200_mb_setbit_ex6(UInt16MbNr,UInt16BitNr,byteValue);
#endregion
#regionPLC数据转换函数
[DllImport("Prodave6.dll")]//诊断错误信息操作
//参数:错误代号、缓冲区大小(字节为单位)、缓冲区
publicexternstaticintGetErrorMessage_ex6(intErrorNr,UInt32BufLen,[MarshalAs(UnmanagedType.LPStr)]StringBuilderpBuffer);
[DllImport("Prodave6.dll")]//S7浮点数转换成PC浮点数
//参数:S7浮点数、PC浮点数
publicexternstaticintgp_2_float_ex6(UInt32gp,reffloatpieee);
[DllImport("Prodave6.dll")]//PC浮点数转换成S7浮点数
//参数:PC浮点数、S7浮点数
publicexternstaticintfloat_2_gp_ex6(floatieee,refUInt32pgp);
[DllImport("Prodave6.dll")]//检测某字节的某位的值是0或1
//参数:字节值、位号
publicexternstaticinttestbit_ex6(byteValue,intBitNr);
[DllImport("Prodave6.dll")]//检测某字节的byte值转换成int数组
//参数:byte值、int数组(长度为8)
publicexternstaticvoidbyte_2_bool_ex6(byteValue,int[]pBuffer);
[DllImport("Prodave6.dll")]//检测某字节的int数组转换成byte值
//参数:int数组(长度为8)
publicexternstaticbytebool_2_byte_ex6(int[]pBuffer);
[DllImport("Prodave6.dll")]//交换数据的高低字节——16位数据
//参数:待交换的数据
publicexternstaticUInt16kf_2_integer_ex6(UInt16wValue);//16位数据——WORD
[DllImport("Prodave6.dll")]//交换数据的高低字节——32位数据
//参数:待交换的数据
publicexternstaticUInt32kf_2_long_ex6(UInt32dwValue);//32位数据——DWORD
[DllImport("Prodave6.dll")]//交换数据缓冲区的的高低字节区,例如pBuffer[0]与pBuffer[1],pBuffer[2]与pBuffer[3]交换
//参数:待交换的数据缓冲区,要交换的字节数,如Amount=pBuffer.Length,则交换全部缓冲
publicexternstaticvoidswab_buffer_ex6(byte[]pBuffer,UInt32Amount);
[DllImport("Prodave6.dll")]//复制数据缓冲区
//参数:目的数据缓冲区,源数据缓冲区,要复制的数量(字节为单位)
publicexternstaticvoidcopy_buffer_ex6(byte[]pTargetBuffer,byte[]pSourceBuffer,UInt32Amount);
[DllImport("Prodave6.dll")]//把二进制数组传换成BCD码的数组——16位数据
//参数:要处理的数组,要处理的字节数,转换前是否先交换高低字节,转换后是否要交换高低字节
//InBytechange为1则转换BCD码之前,先交换高低字节
//OutBytechange为1则转换BCD码之后,再交换高低字节
//如果InBytechange和OutBytechange都没有置1,则不发生高低位的交换
//16位数据BCD码值的许可范围是:+999——-999
publicexternstaticvoidushort_2_bcd_ex6(UInt16[]pwValues,UInt32Amount,intInBytechange,intOutBytechange);//16位数据——WORD
[DllImport("Prodave6.dll")]//把二进制数组传换成BCD码的数组——32位数据
//参数:要处理的数组,要处理的字节数,转换前是否先交换高低字节,转换后是否要交换高低字节
//InBytechange为1则转换BCD码之前,先交换高低字节
//OutBytechange为1则转换BCD码之后,再交换高低字节
//如果InBytechange和OutBytechange都没有置1,则不发生高低位的交换
//32位数据BCD码值的许可范围是:+9999999——-9999999
publicexternstaticvoidulong_2_bcd_ex6(UInt32[]pdwValues,UInt32Amount,intInBytechange,intOutBytechange);//32位数据——DWORD
[DllImport("Prodave6.dll")]//把BCD码的数组传换成二进制数组——16位数据
//参数:要处理的数组,要处理的字节数,转换前是否先交换高低字节,转换后是否要交换高低字节
//InBytechange为1则转换BCD码之前,先交换高低字节
//OutBytechange为1则转换BCD码之后,再交换高低字节
//如果InBytechange和OutBytechange都没有置1,则不发生高低位的交换
//16位数据BCD码值的许可范围是:+999——-999
publicexternstaticvoidbcd_2_ushort_ex6(UInt16[]pwValues,UInt32Amount,intInBytechange,intOutBytechange);//16位数据——WORD
[DllImport("Prodave6.dll")]//把BCD码的数组传换成二进制数组——32位数据
//参数:要处理的数组,要处理的字节数,转换前是否先交换高低字节,转换后是否要交换高低字节
//InBytechange为1则转换BCD码之前,先交换高低字节
//OutBytechange为1则转换BCD码之后,再交换高低字节
//如果InBytechange和OutBytechange都没有置1,则不发生高低位的交换
//32位数据BCD码值的许可范围是:+9999999——-9999999
publicexternstaticvoidbcd_2_ulong_ex6(UInt32[]pdwValues,UInt32Amount,intInBytechange,intOutBytechange);//32位数据——DWORD
[DllImport("Prodave6.dll")]//查看64个连接中哪些被占用,哪些已经建立
//参数:传输缓冲的字节长度,64位长度的数组(0或1)
publicexternstaticvoidGetLoadedConnections_ex6(UInt32BufLen,int[]pBuffer);
#endregion
#region自定义辅助函数
public staticUInt16bytes_2_word(bytedbb0,bytedbb1)//将高低2个byte转换成1个word
{
UInt16dbw0;
dbw0=(UInt16)(dbb0*256+dbb1);
returndbw0;
}
publicstaticUInt32bytes_2_dword(bytedbb0,bytedbb1,bytedbb2,bytedbb3)//将高低4个byte转换成1个dword
{
UInt32dbd0;
dbd0=(UInt32)(dbb0*16777216+dbb1*65536+dbb2*256+dbb3);
returndbd0;
}
publicstaticUInt32words_2_dword(UInt16dbw0,UInt16dbw2)//将高低2个word转换成1个dword
{
UInt32dbd0;
dbd0=(UInt32)(dbw0*65536+dbw2);
returndbd0;
}
publicstaticbyte[]word_2_bytes(UInt16dbw0)//将word拆分为2个byte
{
byte[]bytes=newbyte[2];
bytes[0]=(byte)(dbw0/256);
bytes[1]=(byte)(dbw0%256);
returnbytes;
}
publicstaticbyte[]dword_2_bytes(UInt32dbd0)//将dword拆分为4个byte
{
byte[]bytes=newbyte[4];
bytes[0]=(byte)(dbd0/16777216);
dbd0=dbd0%16777216;
bytes[1]=(byte)(dbd0/65536);
dbd0=dbd0%65536;
bytes[2]=(byte)(dbd0/256);
bytes[3]=(byte)(dbd0%256);
returnbytes;
}
publicstaticUInt16[]dword_2_words(UInt32dbd0)//将dword拆分为2个word
{
UInt16[]words=newUInt16[2];
words[0]=(UInt16)(dbd0/65536);
words[1]=(UInt16)(dbd0%65536);
returnwords;
}
#endregion
}