C#导入DLL时,参数怎么定义是一个比较头痛的问题。特别是指针类型的参数,关于此问题本人有点不成熟的经验。
以 GetComputerName这个函数为例。
函数原型如下
BOOL GetComputerName(
LPTSTR lpBuffer,
LPDWORD lpnSize
);
这个lpBuffer就是下个string型的指针,其实无论是什么类型的指针,对于Windows来说都是一个32位的无符号的整数,也就是一个内在地址,函数之所以使用指针就是要向指针所指向的内存空间写入数据。
我们用C#调用时也要给它传递一个指针,还要对应一块分配的空间 。
下面是代码:
Code
using System.Runtime.InteropServices;
[DllImport("kernel32.dll")]
static extern bool GetComputerName(IntPtr p , ref int lpnSize);
//分配空间
IntPtr p = Marshal.AllocHGlobal(128);
//Console.WriteLine(p); 指针内容 也就是内存地址
int len = 128;
GetComputerName(p, ref len);
//Console.WriteLine(p); 和上次的内容一样
//下面是关键
//p所指向的是一块非托管的空间 要将其转化成托管下的string
string str = Marshal.PtrToStringAnsi(p);
Console.WriteLine(str);
其实和C++在调用的本质是一样的,都要分配空间并将空间的地址传给函数
。只是C#是运行在托管环境,所以对空间的分配的数据的读取都要特殊处理。
还是更简单的方法,就是用StringBuilder。本质上还是和上面一样的,只不过是C#替你做了得多工作。
Code
[DllImport("kernel32.dll")]
static extern bool GetComputerName(StringBuilder lpBuffer, ref int lpnSize);
对于结构体也是一样,以
BOOL GetVersionEx(
LPOSVERSIONINFO lpVersionInformation
);
typedef struct _OSVERSIONINFO{
DWORD dwOSVersionInfoSize;
DWORD dwMajorVersion;
DWORD dwMinorVersion;
DWORD dwBuildNumber;
DWORD dwPlatformId;
TCHAR szCSDVersion[128];
} OSVERSIONINFO;
为例说明一下。
参数是一个结构体的指针,函数会填充结构体的各个字段,其实就是向这块内存空间的不同位置写入不同的数据。
Code
[DllImport("kernel32.dll", EntryPoint = "GetVersionEx")]
static extern bool GetVersionEx(IntPtr p);
IntPtr pv = Marshal.AllocHGlobal(148);
Marshal.WriteInt32(pv, 148);
if (GetVersionEx(pv))
Console.WriteLine("OK");
Console.WriteLine("MajorVersion:" + Marshal.ReadInt32(pv, 4));
Console.WriteLine("BuildNumber:" + Marshal.ReadInt32(pv, 12));
Console.WriteLine(Marshal.PtrToStringAnsi((IntPtr)(pv.ToInt32() + 20)));
根据结构体的定义有5个DWORD和一个128位的CHAR数组,所以要给这个结构体分配148位空间。结构体的第一个字段这是个结构体的大小,我们用 Marshal.WriteInt32(pv, 148);写入结构体的大小(这也是Windows API在使用结构体的一个特点,就是大部分结构体在传给函数填充前要指定其大小。),然后执行函数。
如果函数正常返回,就可以根据结构体的定义从相应的位置读出数据。
Marshal.ReadInt32(pv, 4);读取第二个整数也就是MajorVersion,Marshal.ReadInt32(pv, 12);第四个是BuildNumber,Marshal.PtrToStringAnsi((IntPtr)(pv.ToInt32() + 20));五个整数之后是CSDVersion。
写上面的内容只是想分析一下机理,如果学习过汇编就很好理解了。
下面是通常的做法:
Code
[StructLayout(LayoutKind.Sequential)]
public struct OSVersionInfo
{
public int OSVersionInfoSize;
public int majorVersion;
public int minorVersion;
public int buildNumber;
public int platformId;
[MarshalAs(UnmanagedType.ByValTStr,SizeConst=128)]
public string versionString;
}
[DllImport("kernel32.dll", EntryPoint = "GetVersionEx")]
static extern bool GetVersionEx(ref OSVersionInfo osinfo);
OSVersionInfo info = new OSVersionInfo();
//结构体的大小的指定
info.OSVersionInfoSize = Marshal.SizeOf(typeof(OSVersionInfo));
if (GetVersionEx(ref info))
Console.WriteLine("OK");