调用GetWindowText时感悟托管代码中的关键字out和非托管中的用法和区别

最近在写一个获取窗口标题的函数的时候,在使用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的时候不能死套,要根据使用参数的不同类型匹配。大神略过,新手朋友们可以参考一下,祝大家代码畅通无阻。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zhoumingongheguo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值