最近在写一个获取窗口标题的函数的时候,在使用API GetWindowText的时候发现这个字符串指针到底要怎么用,这个API GetWindowText的原型是如下:int GetWindowTextA( [in] HWND hWnd, [out] LPSTR lpString, [in] int nMaxCount );
首先说一下,out 关键字在有多个不同类型的返回值就要用到,这个API 除了返回一个int 类型的值表示最终得到的字节长度,同时还要返回一个字符串,这个字符串就被复制到指定的字符串指针处。 LPSTR就是表示字符串指针类型。如果要取到这个字符串你会将第二个传出参数定义成什么类型呢?我现在是使用C#托管代码写的,你会选择String类型吗?就如这样:[DllImport("User32.dll")]//获取窗口文本, public static extern int GetWindowText(IntPtr hWnd, out String lpString, int nMaxCount);
如果这样写就错了,暂不考虑获取其他进程窗口文本,就当是取得本进程的窗口,我之前也是不理解,反复测试发现的。如果这样写会直接终止调试进程,这有可能是找不到有效的内存地址造成的,暂且不说这个终止线程的错误,如果使用String 这个托管类型的化,关于字符串的Unicode 转换就无法实现,所以我们应该是得到这些字符串的字节数组,然后再将这些数组转换成字符串才是正确的过程,因为String类型和其他类型比如说int类型,String类型需要编码转换的,不同的编码转换得到的结果是不一样的。所以引用这个API的正确的重写应该如下:[DllImport("User32.dll")]//获取窗口文本, public static extern int GetWindowText(IntPtr hWnd, byte[] lpString, int nMaxCount);
看到没有,第二个传出字符串应该写成字节数组就没问题,我今天说的还有out关键字在托管代码中非托管代码中的区别,经过多次测试我才有所感悟的,先看代码:`public int GetWindowsText(IntPtr _HWind,out string _text,int nMaxCount)
{
int _su = 0;//返回消息处理的结果。
string _TextStr = string.Empty;//定义一个字符串变量用于得到转换成字符的窗口名
if (nMaxCount > 0)
{//只有当传入的标题长度不为零才会执行读取标题的操作。
int _TextLength = nMaxCount + 1;//经测试,得到的长度会比实际长度少一个空格的长度,如果标题是文本就没问题,后面是以空格结束的,如果是数字那就会少一个字节长度,因此多加一个长度。
Int64 _TextBuff = _game64.PrepareMemoryB();//在目标进程中申请一块内存,作为窗口文本的缓存区,如果只是用于存放窗口及子窗口控件的名字,最大256就够了。
byte[] _TextArry = new byte[_TextLength];//定义一个数组用于读取目标进程中缓存中的窗口文本。
try
{
byte[] _addrs = new byte[_TextLength];
int _s = MyProcAPI64.GetWindowText(_HWind, _addrs, _TextLength);
}
catch { }
_su = MyProcAPI64.SendMessage(_HWind, 13, _TextLength, _TextBuff);//发送读取窗口文本的消息给目标窗口,目标进程就会将窗口的文本保存到缓存地址,但是不能跨进程,所以缓存地址必须是在目标进程里面。
if (_game64.ReadMemory(_TextBuff, out _TextArry, (uint)_TextLength) == true)
{//如果成功读取缓存中的窗口文本。
_TextStr = Encoding.Default.GetString(_TextArry);//将读取到的窗口文本通过字符编码转换成字符串。
}
_game64.ReleaseAMemory(_TextBuff);//每次将缓存里面的数据都读取完成后就应该将申请的内存空间释放。
}
_text = _TextStr;//将字符串赋值给out 参数。因为SendMessage是没有out 类型的参数的,也就是说不能跨进程操作,所以只能通过读取目标进程的方法得到后赋值
return _su;
}`
大家可以看到API GetWindowTex是非托管代码,我托管代码写了一个同名而已,参数也是相同的,第二个参数在托管代码中我是直接定义成String类型的,因为只要最后将结果赋值给这个传出参数就没问题了。但是在非托管代码中参数都是通过压栈出栈调用赋值的。也就是说,托管代码中,out关键字后面我定义什么类型,最终就赋值什么类型就可以了,貌似这个out 关键字除了支持多个返回值没有其他不同。但是我做的多次测试就得到了不同。
在使用非托管API GetWindowTex取窗口文本的时候,我曾将第二个参数定义为int64类型,和string 类型,分别加上out 关键字和不加out关键字,得到的结果不一样。测试结果如下:
1、int 64类型参数 不加out关键字,运行成功,返回处理字节长度,但得不到结果。
2、int 64类型参数加了out 关键字,运行成功,返回8个字节,可以看到整数,其实就是将字符串字节赋值到了参数指针处。
3、String 类型参数不加out关键字,运行成功,返回字符串长度,看到的是空字符串。
4、String类型参数加out关键字,运行不成功,引发错误进程强行被终止了。
5、byte[] 类型参数不加out 关键字,运行成功,返回字符串长度,数组里面得到正确的字节数组。
6、byte[] 类型参数加上out 关键字,运行不成功,进程被强行终止。
从测试结果可以看出,原型分明有out 关键字,为何不同的参数类型,不加out关键字却能成功,加了out关键字后反而会引发错误强行终止进程呢?
这里就是托管和非托管中out 关键字的不同,且托管代码中不同类型之间的区别。简单的说,不管你是否加了out关键字,非托管代码中只会影响压栈参数,不会影响给出的值。如果说int64 、String、byte[] 类型不加out关键字能成功,说明压入的是正确的缓存指针,也就是内存地址是有效的。或者说是存在的。但是加了out关键字后却引发错误进程被强行终止,那么就说明加入out后压入的指针是无效的,或者是空的。但是int64类型加了Out关键字却没关系,说明给出的指针是真实存在的。你应该感觉到了吧,托管代码中String 类型 、数组类型参数对应的是一级指针,也就是说指针就是字符串或者数组的起始地址。但是int64参数却是二级指针,参数的指针指向的值也是一个指针,而这个指针才指向内存中字节数据,同一个道理,结构体参数或其他类参数都是多级指针,比如说实列化一个对象后,这个对象后面加一个点,然后后面就会显示下一级指针了。所以说在非托管代码中out关键字就是在压栈前先取参数的下一级指针。因此在引用非托管API的时候不能死套,要根据使用参数的不同类型匹配。大神略过,新手朋友们可以参考一下,祝大家代码畅通无阻。