结构数组
dwRet
=
DirectSoundEnumerate(
new
LPDSENUMCALLBACK(DSoundEnumCallback),
ref
dwFlag);
/**/
///<summary>
///原形:BOOL ReadFile(HANDLE hFile, LPVOID lpBuffer, DWORD nNumberOfBytesToRead, LPDWORD lpNumberOfBytesRead, LPOVERLAPPED lpOverlapped);
///</summary>
[DllImport(
"
kernel32.dll
"
)]
public
extern
static
Int32 ReadFile(IntPtr hFile, Byte[] buffer,Int32 nNumberOfBytesToRead,
ref
Int32 lpNumberOfBytesRead,
ref
OVERLAPPED lpOverlapped);
/**/
///<summary>
///原型是: FARPROC GetProcAddress(HMODULE hModule,LPCSTR lpProcName);
///</summary>
[DllImport(
"
kernel32.dll
"
, EntryPoint
=
"
GetProcAddress
"
)]
private
extern
static
IntPtr GetProcAddress(IntPtr hModule, String sFuncName);
[DllImport(
"
kernel32.dll
"
, EntryPoint
=
"
GetProcAddress
"
)]
private
extern
static
IntPtr GetProcAddressByIndex(IntPtr hModule, Int32 dwIndex);
结构数组就比较复杂了,就我个人的经验,不同厂商提供的API,实现不同,需要采用的处理方式也不相同的。
一般情况下,参照基本类型数组的调用方式即可,例如:
///
<summary>
/// 原形:
/// typedef struct tagACCEL {
/// BYTE fVirt;
/// WORD key;
/// WORD cmd;
/// } ACCEL, *LPACCEL;
/// </summary>
public struct ACCEL
{
public Byte fVirt;
public UInt16 key;
public UInt16 cmd;
}
/// <summary>
/// 原形:int CopyAcceleratorTable(HACCEL hAccelSrc,LPACCEL lpAccelDst,int cAccelEntries);
/// </summary>
/// <returns></returns>
[DllImport( " user32 " )]
public static extern Int32 CopyAcceleratorTable(IntPtr hAccelSrc, ACCEL[] lpAccelDst, Int32 cAccelEntries);
/// 原形:
/// typedef struct tagACCEL {
/// BYTE fVirt;
/// WORD key;
/// WORD cmd;
/// } ACCEL, *LPACCEL;
/// </summary>
public struct ACCEL
{
public Byte fVirt;
public UInt16 key;
public UInt16 cmd;
}
/// <summary>
/// 原形:int CopyAcceleratorTable(HACCEL hAccelSrc,LPACCEL lpAccelDst,int cAccelEntries);
/// </summary>
/// <returns></returns>
[DllImport( " user32 " )]
public static extern Int32 CopyAcceleratorTable(IntPtr hAccelSrc, ACCEL[] lpAccelDst, Int32 cAccelEntries);
但是也有特殊情况,对些厂商提供的API中,不知是否和内存复制的方式有关,类似的函数,如果采用上面相同的定义方法调用的话,调用正确,但是应该返回的数据没有被改写。这个时候就需要另一种方法来解决了。
众所周知,在逻辑上结构是一段连续的内存,数组也是一段连续内存,我们可以从堆中直接申请一段内存,调用API,然后将返回的数据再转换成结构即可。具体可以参看下面的例子。
结构定义以及API声明:
[StructLayout(LayoutKind.Sequential, Pack
=
8
)]
private struct CmBoxInfo
{
public static CmBoxInfo Empty = new CmBoxInfo();
public byte MajorVersion;
public byte MinorVersion;
public ushort BoxMask;
public uint SerialNumber;
public ushort BoxKeyId;
public ushort UserKeyId;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = CM_PUBLIC_KEY_LEN)] public byte [] BoxPublicKey;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = CM_PUBLIC_KEY_LEN)] public byte [] SerialPublicKey;
public uint Reserve;
public void Init()
{
BoxPublicKey = new byte [CM_PUBLIC_KEY_LEN];
Debug.Assert(BoxPublicKey != null );
SerialPublicKey = new byte [CM_PUBLIC_KEY_LEN];
Debug.Assert(SerialPublicKey != null );
}
}
/// <summary>
/// 原型:int CMAPIENTRY CmGetBoxes(HCMSysEntry hcmse, unsigned long idPort, CMBOXINFO *pcmBoxInfo, unsigned int cbBoxInfo)
/// </summary>
[DllImport( " xyz.dll " )]
private static extern Int32 CmGetBoxes(IntPtr hcmse, CmGetBoxesOption idPort,IntPtr pcmBoxInfo, Int32 cbBoxInfo);
private struct CmBoxInfo
{
public static CmBoxInfo Empty = new CmBoxInfo();
public byte MajorVersion;
public byte MinorVersion;
public ushort BoxMask;
public uint SerialNumber;
public ushort BoxKeyId;
public ushort UserKeyId;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = CM_PUBLIC_KEY_LEN)] public byte [] BoxPublicKey;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = CM_PUBLIC_KEY_LEN)] public byte [] SerialPublicKey;
public uint Reserve;
public void Init()
{
BoxPublicKey = new byte [CM_PUBLIC_KEY_LEN];
Debug.Assert(BoxPublicKey != null );
SerialPublicKey = new byte [CM_PUBLIC_KEY_LEN];
Debug.Assert(SerialPublicKey != null );
}
}
/// <summary>
/// 原型:int CMAPIENTRY CmGetBoxes(HCMSysEntry hcmse, unsigned long idPort, CMBOXINFO *pcmBoxInfo, unsigned int cbBoxInfo)
/// </summary>
[DllImport( " xyz.dll " )]
private static extern Int32 CmGetBoxes(IntPtr hcmse, CmGetBoxesOption idPort,IntPtr pcmBoxInfo, Int32 cbBoxInfo);
调用示例
IntPtr hcmBoxes
=
IntPtr.Zero;
CmAccess cma = new CmAccess();
CmBoxInfo[] aBoxList = null ;
Int32 dwBoxNum = 0 , dwLoop = 0 ,dwBoxInfoSize = 0 ;
IntPtr pBoxInfo = IntPtr.Zero;
dwBoxNum = m_pCmGetBoxes(hcmBoxes, CmGetBoxesOption.AllPorts, IntPtr.Zero, 0 );
if (dwBoxNum > 0 )
{
aBoxList = new CmBoxInfo[dwBoxNum];
if (aBoxList != null )
{
dwBoxInfoSize = Marshal.SizeOf(aBoxList[ 0 ]);
pBoxInfo = Marshal.AllocHGlobal(dwBoxInfoSize * dwBoxNum);
if (pBoxInfo != IntPtr.Zero)
{
dwBoxNum = m_pCmGetBoxes(hcmBoxes, CmGetBoxesOption.AllPorts, pBoxInfo, dwBoxNum);
for (dwLoop = 0 ; dwLoop < dwBoxNum; dwLoop ++ )
{
aBoxList[dwLoop] = (CmBoxInfo)Marshal.PtrToStructure((IntPtr)((UInt32)pBoxInfo + dwBoxInfoSize * dwLoop), CmBoxInfo.Empty.GetType());
}
Marshal.FreeHGlobal(pBoxInfo);
pBoxInfo = IntPtr.Zero;
}
else
{
aBoxList = null ;
}
}
}
CmAccess cma = new CmAccess();
CmBoxInfo[] aBoxList = null ;
Int32 dwBoxNum = 0 , dwLoop = 0 ,dwBoxInfoSize = 0 ;
IntPtr pBoxInfo = IntPtr.Zero;
dwBoxNum = m_pCmGetBoxes(hcmBoxes, CmGetBoxesOption.AllPorts, IntPtr.Zero, 0 );
if (dwBoxNum > 0 )
{
aBoxList = new CmBoxInfo[dwBoxNum];
if (aBoxList != null )
{
dwBoxInfoSize = Marshal.SizeOf(aBoxList[ 0 ]);
pBoxInfo = Marshal.AllocHGlobal(dwBoxInfoSize * dwBoxNum);
if (pBoxInfo != IntPtr.Zero)
{
dwBoxNum = m_pCmGetBoxes(hcmBoxes, CmGetBoxesOption.AllPorts, pBoxInfo, dwBoxNum);
for (dwLoop = 0 ; dwLoop < dwBoxNum; dwLoop ++ )
{
aBoxList[dwLoop] = (CmBoxInfo)Marshal.PtrToStructure((IntPtr)((UInt32)pBoxInfo + dwBoxInfoSize * dwLoop), CmBoxInfo.Empty.GetType());
}
Marshal.FreeHGlobal(pBoxInfo);
pBoxInfo = IntPtr.Zero;
}
else
{
aBoxList = null ;
}
}
}
最后提一句,Marshal类非常有用,其中包括了大量内存申请、复制和类型转换的函数,灵活运用的话,基本上可以避免unsafe code。
2.6、 函数指针(回调函数)
C#中采用委托(delegate)和函数指针等同的功能,当API函数的参数为回调函数时,我们通常使用委托来替代。与C和C++ 中的函数指针相比,委托实际上是具体一个Delegate派生类的实例,它还包括了对参数和返回值,类型安全的检查。
先看一下下面的例子:
///
<summary>
/// 原形:typedef BOOL (CALLBACK *LPDSENUMCALLBACKA)(LPGUID, LPCSTR, LPCSTR, LPVOID);
/// </summary>
public delegate Boolean LPDSENUMCALLBACK(IntPtr guid, String sDesc, String sDevName, ref Int32 dwFlag);
/// <summary>
/// 原形:HRESULT WINAPI DirectSoundCaptureEnumerateA(LPDSENUMCALLBACKA pDSEnumCallback, LPVOID pContext);
/// </summary>
[DllImport( " Dsound.dll " )]
public static extern Int32 DirectSoundCaptureEnumerate(LPDSENUMCALLBACK pDSEnumCallBack, ref Int32 dwFlag);
/// 原形:typedef BOOL (CALLBACK *LPDSENUMCALLBACKA)(LPGUID, LPCSTR, LPCSTR, LPVOID);
/// </summary>
public delegate Boolean LPDSENUMCALLBACK(IntPtr guid, String sDesc, String sDevName, ref Int32 dwFlag);
/// <summary>
/// 原形:HRESULT WINAPI DirectSoundCaptureEnumerateA(LPDSENUMCALLBACKA pDSEnumCallback, LPVOID pContext);
/// </summary>
[DllImport( " Dsound.dll " )]
public static extern Int32 DirectSoundCaptureEnumerate(LPDSENUMCALLBACK pDSEnumCallBack, ref Int32 dwFlag);
具体调用方法如下:
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
这里需要特别注意的就是委托实际上是一个实例,和普通的类实例一样,是被DotNET Framework垃圾收集机制所管理,有生存周期的。上文例子的定义方式其实函数级别的局部变量,当函数结束时,将被释放,如果回调仍然在继续的话,就会产生诸如非法访问的错误。所以在使用回调函数的时候一定要比较清楚的了解,回调的作用周期是多大,如果回调是全局的,那么定义一个全局的委托变量作为参数。
2.7、 表示多种类型的指针—LPVOID以及其它
指针是C/C++的精髓所在,一个void能够应付所有的问题,我们遇到最多的可能就是LPVOID这样的参数。LPVOID最常用的有两种情况,一种就是表示一个内存块,另一种情况可能是根据其它参数的定义指向不同的数据结构。
第一种情况很好处理,如果是一个内存块,我们可以他当作一个Byte数组就可以了,例如:
![](https://i-blog.csdnimg.cn/blog_migrate/a41954a27d6ad96fa2c2cf816e677448.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/1327ab569c1ae82736693a50b8e33378.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/0196c3df5ea9e936f21e9932cca91014.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
第二种情况比较复杂,C#中类型转换是有限制的,一个Int32是没法直接转换成为Point的,这个时候之能够根据不同的参数类型定义不同的重载函数了。例如GetProcAddress函数的lpProcName既可以是一个字符串表示函数名,又可以是一个高字为0的Int32类型,表示函数的序号,我们可以这样分别定义:
![](https://i-blog.csdnimg.cn/blog_migrate/a41954a27d6ad96fa2c2cf816e677448.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/1327ab569c1ae82736693a50b8e33378.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/0196c3df5ea9e936f21e9932cca91014.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
在这里总结了调用API时有关指针的一些常见问题,你会发现大多数情况下C#依靠自身的能力就能解决问题,希望对大家有帮助。