分类

类型:
不限 游戏开发 计算机程序开发 Android开发 网站开发 笔记总结 其他
评分:
不限 10 9 8 7 6 5 4 3 2 1
原创:
不限
年份:
不限 2018 2019

文章列表

  • 【Cocos Creator实战教程(7)】——UI组件(1)ScrollView 组件

    1. 知识点讲解ScrollView 是一种带滚动功能的容器,它提供一种方式可以在有限的显示区域内浏览更多的内容。通常 ScrollView 会与Mask组件配合使用,同时也可以添加ScrollBar组件来显示浏览内容的位置。
    1.1 ScrollView 属性


    属性
    功能说明




    content
    它是一个节点引用,用来创建 ScrollView 的可滚动内容,通常这可能是一个包含一张巨大图片的节点。


    Horizontal
    布尔值,是否允许横向滚动。


    Vertical
    布尔值,是否允许纵向滚动。


    Inertia
    滚动的时候是否有加速度。


    Brake
    浮点数,滚动之后的减速系数。取值范围是 0-1,如果是 1 则立马停止滚动,如果是 0,则会一直滚动到 content 的边界。


    Elastic
    布尔值,是否回弹。


    Bounce Duration
    浮点数,回弹所需要的时间。取值范围是 0-10。


    Horizontal ScrollBar
    它是一个节点引用,用来创建一个滚动条来显示 content 在水平方向上的位置。


    Vertical ScrollBar
    它是一个节点引用,用来创建一个滚动条来显示 content 在垂直方向上的位置


    ScrollView Events
    列表类型,默认为空,用户添加的每一个事件由节点引用,组件名称和一个响应函数组成。详情见 ‘Scrollview 事件’ 章节


    CancelInnerEvents
    如果这个属性被设置为 true,那么滚动行为会取消子节点上注册的触摸事件,默认被设置为 true。



    1.2 ScrollView 事件


    属性
    功能说明




    Target
    带有脚本组件的节点。


    Component
    脚本组件名称。


    Handler
    指定一个回调函数,当 ScrollView 的事件发生的时候会调用此函数。


    CustomEventData
    用户指定任意的字符串作为事件回调的最后一个参数传入。



    Scrollview 的事件回调有两个参数,第一个参数是 ScrollView 本身,第二个参数是 ScrollView 的事件类型。
    1.3 详细说明ScrollView组件必须有指定的content节点才能起作用,通过指定滚动方向和content节点再此方向长度来计算滚动时的位置信息,content节点也可以通过UIwidget设置自动resize。
    通常的一个ScrollView的节点树如下:

    这里的View用来定义一个可以显示的滚动区域,所以通常Mask组件会被添加到View上面,滚动的内容可以直接放到content节点或者添加到content中的子节点上。
    1.4 ScrollBar 设置ScrollBar 是可选的,你可以选择只设置水平或者垂直 ScrollBar,当然也可以两者都设置。建立关联可以通过在 层级管理器 里面拖拽一个带有 ScrollBar 组件的节点到 ScrollView 的相应字段完成。
    1.5 通过脚本代码添加回调1.5.1 方法一这种方法添加的事件回调和使用编辑器添加的事件回调是一样的,通过代码添加, 你需要首先构造一个 cc.Component.EventHandler 对象,然后设置好对应的 target, component, handler 和 customEventData 参数。
    cc.Class({ extends: cc.Component, properties: { }, onLoad () { var scrollViewEventHandler = new cc.Component.EventHandler(); scrollViewEventHandler.target = this.node; //这个 node 节点是你的事件处理代码组件所属的节点 scrollViewEventHandler.component = "ScrollView1Script";//这个是脚本文件名 scrollViewEventHandler.handler = "callback"; scrollViewEventHandler.customEventData = "foobar"; var scrollview = this.node.getComponent(cc.ScrollView); scrollview.scrollEvents.push(scrollViewEventHandler); }, //注意参数的顺序和类型是固定的 callback: function (scrollview, eventType, customEventData) { //这里 scrollview 是一个 Scrollview 组件对象实例 //这里的 eventType === cc.ScrollView.EventType enum 里面的值 //这里的 customEventData 参数就等于你之前设置的 "foobar" console.log(customEventData); }});
    1.5.2 方法二通过 scrollview.node.on(‘scroll-to-top’, …) 的方式来添加
    //假设我们在一个组件的 onLoad 方法里面添加事件处理回调,在 callback 函数中进行事件处理:cc.Class({ extends: cc.Component, properties: { scrollview: cc.ScrollView }, onLoad: function () { this.scrollview.node.on('scroll-to-top', this.callback, this); }, callback: function (event) { //这里的 event 就是这个ScrollView 组件 //另外,注意这种方式注册的事件,也无法传递 customEventData }});
    2. 步骤2.1 建立节点树操作
    在层级管理器中添加一个新的UI组件ScrollView名为NewScrollView,节点树如下
    将content其中的item删除,也可以直接在item上修改,我是直接删除,添加一个Scrite(精灵),将下载的图片拖到资源管理器,然后在将图片拖入到Scrite(精灵)中的Scrite Frame中(原谅我放上了我最爱的偶吧——允浩)
    复制一份ScrollBar分别命名为scrollBarV、scrollBarH,横向滚动条、竖向滚动条
    调整New ScrollView子节点的位置

    节点树完成图如下

    2.2 创建Widget对齐组件、自动布局
    在New ScrollView节点上添加widget对齐挂件,设置全屏幕


    View节点设置全屏幕



    属性





    left
    0 px


    right
    0 px


    top
    0 px


    bottom
    0 px




    content节点的widget以左上角对齐,并添加Layout组件设置Resize Mode



    属性





    left
    0 px


    top
    0 px



    Layout组件中设置



    属性

    说明




    Resize Mode
    CONTAINER
    对容器的大小进行缩放,就是content根据子节点的大小来自动调整大小全部显示出子节点



    2.3 设置content中节点树中的的属性检查器
    设置ScrollBarV的属性,如图


    设置ScrollBarH的属性,如图

    2.4 设置New ScrollView的属性检查器
    3. 总结这里我们的游戏的菜单运用了这种方法,滑动可以玩两个游戏。请试试如何创建吧。
    提示:

    想想每个节点的大小都是多少,有做出来的可以在评论区写出来哦。
    本资源部分素材来源于网络。
    1 回答 2018-11-27 19:30:17
  • 【Cocos Creator实战教程(5)】——打砖块(物理引擎,碰撞检测)

    1. 知识点
    物理引擎碰撞检测
    2. 步骤2.1 准备工作搭一个游戏背景
    2.2 小球运动再建一个物理层,用来装游戏里的带有物理属性的东西,设置锚点为左下角

    wall:墙//小球碰到就会反弹的那种墙 ground:地面//球碰到地面,这局游戏就结束了 brick_layout:砖块布局//这个单词我们之前讲过了就不讲了 ball:球//就是球 paddle:桨//这里特指那个可以控制移动的白色长方形

    这个wall肯定是要有碰撞属性的,在属性面板,添加一个物理组件 (物理->rigidbody)。
    因为我们的墙有上,左,右三面,所以再添加三个碰撞组件(一个节点可以有多个碰撞组件)。

    编辑一下

    地面同理,小球同理,托盘同理 。(这里把地面和墙分开是为了后面墙和地面可能有不同的逻辑)
    现在已经编辑了几个物理节点的碰撞包围盒,但还没有编辑他们的物理属性(cc.RigidBody)
    先从小球开始,点击ball节点,在属性检查器可以看到

    Cocos Creator从1.5版本开始支持Box2D物理游戏引擎,Box2D是一个优秀的刚体模拟框架,关于Box2D的知识可以去网络上自行了解。
    把第一个参数勾选,代表启用碰撞回调,可以在脚本里写回调函数
    Bullet:高速运动的物体(子弹)开启,避免穿透,这里不用勾选
    type选择Dynamic,

    static:不会受到力的影响,不会受到速度影响,指的是物理引擎,我们依然可以通过移动节点来改变位置 。
    kinematic:不受力的影响,会受到速度影响 。
    dynamic:受力影响,受速度影响 。
    animated:和动画结合使用。

    Gravity Scale设置为0(标准是1,数值代表比例),也就是没有重力。
    设置线速度(1000,1000)
    在下面的碰撞组件里,设置Friction (摩擦系数)等于0(没有摩擦力),Restitution(弹性系数)等于1(没有动量损耗)

    因为小球是我们的主角,左右的碰撞都是对球来说的,所以碰撞属性都在小球这一方设置就可以了。
    另外要设置wall,ground,paddle,brick的type为staticbrick的tag为1,ground的tag为2,paddle的tag为3,wall的tag位4
    下面来看脚本
    BrickLayout.js
    cc.Class({ extends: cc.Component, properties: { padding: 0, spacing: 0, cols: 0, brickPrefab: cc.Prefab, bricksNumber: 0, }, init(bricksNumber) { this.node.removeAllChildren(); this.bricksNumber = bricksNumber; for (let i = 0; i < this.bricksNumber; i++) { let brickNode = cc.instantiate(this.brickPrefab); brickNode.parent = this.node; brickNode.x = this.padding + (i % this.cols) * (brickNode.width + this.spacing) + brickNode.width / 2; brickNode.y = -this.padding - Math.floor(i / this.cols) * (brickNode.height + this.spacing) - brickNode.height / 2; } }});
    2.3 添加砖块自己写了一个动态添加砖块的布局脚本,传入需要添加的砖块数量就可以动态加入的布局节点中。
    2.4 结束界面完善好游戏逻辑,我使用了MVC模式编写脚本。

    3. 总结至此,我便给大家简要的介绍了一下物理引擎,更多的功能需要大家自己探索实践。
    列出几个大家常问的问题:
    3.1如何移动刚体?当我们的一个节点上有一个刚体,我们要进行移动。一般我们都会通过节点的setPosition进行移动,但是刚体不会被影响,不管是Static、还是Dynamic还是Kinematic都不会被影响我们可以通过1、瞬时动作cc.place来进行移动而且不会影响刚体原本的运动轨迹2、Action的所有动作。cc.moveBy;cc.moveTo;等等
    3.2 碰撞组件和物理组件有什么不同?碰撞组件没有类型之分,只要相交就会发生碰撞事件,如果不对碰撞进行处理,那就没有任何影响。物理碰撞组件分类型,因为他们先会绑定刚体。如果刚体类型不同则会有不同的效果。和Dynamtic类型刚体绑定的PhysicsBoxCollider会受重力影响,可以设置速度和Static类型刚体绑定的物理组件,不会受重力影响,不可以设置速度,可以通过设置位置让其移动和Kinematic类型刚体绑定的物理组件,不受重力影响,可以设置速度
    例如,本文中我们就是用了物理碰撞组件,所以刚体类型选择上要有一定技巧。
    在现实开发情况下,拾取道具和横像动作例如进攻多用碰撞组件 ,而竖向动作例如弹跳多用物理碰撞组件。
    3.3 三种物理组件有什么不同?绑定了Dynamic(运动)类型的物理组件不能穿透绑定了Static(静态)类型的物理组件绑定了Dynamic类型的物理组件不能穿透绑定了Kinematic类型的物理组件Static和Kinematic不会触发碰撞事件,Static和Static;Kinematic和Kinematic不会触发碰撞事件;
    所以,因为我们不能将ball选为kinematic,尽管此游戏我们忽略了重力。
    3.4 物理组件如何进行碰撞回调?首先RigidBody要开启碰撞监听然后当前节点下有如下函数
    在函数碰撞体刚开始接触时调用一次onBeginContatct:function(contact,selfCollider,otherCollider){}在两个碰撞体结束接触时被调用一次onEndContact:fucntion(contact,setCollider,otherCollider){}每次要处理碰撞体接触逻辑是被调用onPreSolve:function(contact,selfCollider,otherCollider){}每次处理完碰撞体接触时被调用onPostSolve:fucntion(contact,selfCollider,otherCollider){}
    3.5 碰撞组件的回调var manager = cc.director.getCollisionManager();manager.enabled = true;脚本里面先开启碰撞监听,因为默认是关闭然后有以下函数://当碰撞产生时调用onCollisionEnter:function(other,self){}//在碰撞产生后,在碰撞结束前,每次计算完碰撞结果后调用onCollisionStay:function(other,self){}//当碰撞结束后调用onCollisionExit;function(other,self){}
    部分素材来源于网络,欢迎提问
    4 回答 2018-11-25 22:52:54
  • 【Cocos Creator实战教程(4)】——炸弹人(TiledMap相关)

    1. 相关知识点
    虚拟手柄(新)
    地图制作
    碰撞检测
    动画制作

    2. 步骤2.1 制作地图2.1.1.新建19x19的地图,Tile大小32x32,导入图块资源2.1.2 建立三个图层(ground,hide,main)和一个对象层(objects)ground是背景层,用绿色的草坪图块填充满

    hide是道具层,挑选道具图块(包括出口的门)放在几个位置

    main是障碍物层,还原经典炸弹人的地图布局,每隔一个位置放一个钢块,用土块覆盖道具层的道具,在其他位置再放几个土块

    objects层有两个对象(player,enemy)标记玩家和敌人的位置信息
    最终效果如图

    2.1.3 将除了草坪图块的其他图块添加type属性,属性值分别为,soil(土块),steel(钢块),door(门),prop1(道具1)…
    2.1.4 保存地图文件和图集文件放在一起2.2 绑定地图2.2.1 新建工程Bomberman,将所需资源全部导入,项目结构如下(有些是后来添加的)
    2.2.2 将地图拖入场景(自动生成map和layer节点),将锚点改为(0,0),下面出现的player等节点也都是(0,0)为锚点2.2.3 新建脚本game.js添加为map节点组件,声明全局变量,在属性面板中拖入相关层节点
    2.3 虚拟手柄2.3.1 我们准备了上下左右炸弹五个按钮的原始状态和按下状态共十张图片素材,在场景编辑器中看着顺眼的地方摆放这五个按钮(将label删除),并将按钮的相应状态的按钮图片添加到右侧属性面板中2.3.2 为每个按钮添加点击事件,(map->game->btnBomb, btnUp, btnDown, btnLeft, btnRight)
    2.4 炸弹动画2.4.1 将炸弹图片集中的任意一个拖入场景中,将其宽高改为32x32,再拖回资源管理器中制作一个炸弹的prefab bomb,为其添加Animation组件2.4.2 新建炸弹爆炸动画clip explode,拖入bomb的属性面板2.4.3 在动画播放完成的最后一帧加入一个帧事件,响应事件方法名为exploded
    2.5 主角和敌人2.5.1 添加player和enemy为map节点的两个子节点,锚点0,0,大小32x32 (位置随意,一会我们要在代码中动态设置他们的位置)
    3. 总结
    虚拟手柄是一个比较热点的控制,现实生活中还会有圆圈控制各个角度的,也有开源项目大家可以参考
    注意节点前后层的遮挡关系

    注意:本教程部分素材来源于网络,另请大家在评论区踊跃提问发言。
    6 回答 2018-11-24 23:12:26
  • 【Cocos Creator实战教程(3)】——TiledMap(瓦片地图)组件

    1. 前言瓦片地图是由一张一张的正方形小图片拼接成的地图,例如炸弹人,QQ堂都是非常典型的瓦片游戏。瓦片地图(Tile Map) 不但生成简单,并且可以灵活的用于Cocos2d-x引擎。不论你的游戏是角色扮演游戏, 平台动作游戏或仿打砖块游戏,这些游戏地图可以使用开源的瓦片地图编辑器Tiled Map Editor生成并保存为TMX文件格式,被Cocos2d-x支持。
    2. 步骤2.1 安装Tiled Map Editoredit(编辑)->preferences(参数)里面可以设置语言
    2.2 TiledMap制作新建一张地图

    建立三个图层和一个对象层并将资源图块导入 (最下面的图层将显示在最下面)



    ground层:用ground图块填充满(按住鼠标左键)
    barriers层:用barrier图块
    stars层:用star图块

    最终效果如下图

    选择players对象层,在如图位置插入两个图块对象,并更改名称如图

    给星星添加一个属性

    保存为tmx文件,和图片文件放在一起
    2.3 Cocos Creator中使用TiledMap:
    新建一个TiledMapTest工程,创建一个Game场景
    将刚才生成的tmx文件和相关图片一起添加到工程
    将map.tmx文件拖入层级管理器或者属性编辑器,就会自动生成map节点以及其子节点(也就是图层节点)【没有对象层节点】
    最后将player(安卓小机器人)图片拖入(位置随意),创建player节点,并使其为map节点的子节点。
    调整map和player节点的锚点都为(0,0)也就是左下角
    创建map.js脚本添加到map节点

    cc.Class({ extends: cc.Component, properties: { }, // use this for initialization onLoad: function () { this.player = this.node.getChildByName('player'); this.loadMap(); cc.systemEvent.on(cc.SystemEvent.EventType.KEY_DOWN, this.onKeyDown, this); }, onKeyDown:function(event){ var newTile = cc.p(this.playerTile.x, this.playerTile.y); switch(event.keyCode) { case cc.KEY.up: newTile.y -= 1; break; case cc.KEY.down: newTile.y += 1; break; case cc.KEY.left: newTile.x -= 1; break; case cc.KEY.right: newTile.x += 1; break; default: return; } this.tryMoveToNewTile(newTile); }, //加载地图文件时调用 loadMap: function () { //初始化地图位置 this.node.setPosition(cc.visibleRect.bottomLeft); //地图 this.tiledMap = this.node.getComponent(cc.TiledMap); //players对象层 let players = this.tiledMap.getObjectGroup('players'); //startPoint和endPoint对象 let startPoint = players.getObject('startPoint'); let endPoint = players.getObject('endPoint'); //像素坐标 let startPos = cc.p(startPoint.offset.x, startPoint.offset.y); let endPos = cc.p(endPoint.offset.x, endPoint.offset.y); //障碍物图层和星星图层 this.barriers = this.tiledMap.getLayer('barriers'); this.stars = this.tiledMap.getLayer('stars'); //出生Tile和结束Tile this.playerTile = this.startTile = this.getTilePos(startPos); this.endTile = this.getTilePos(endPos); //更新player位置 this.updatePlayerPos(); }, tryMoveToNewTile: function(newTile) { let width = this.tiledMap.node.width; let height = this.tiledMap.node.height; if (newTile.x < 0 || newTile.x >= width) return; if (newTile.y < 0 || newTile.y >= height) return; if (this.barriers.getTileGIDAt(newTile)) {//GID=0,则该Tile为空 cc.log('This way is blocked!'); return false; } this.tryCatchStar(newTile); this.playerTile = newTile; this.updatePlayerPos(); if (cc.pointEqualToPoint(this.playerTile, this.endTile)) { cc.log('succeed'); } }, tryCatchStar: function(newTile){ let GID = this.stars.getTileGIDAt(newTile); let prop = this.tiledMap.getPropertiesForGID(GID); if (this.stars.getTileGIDAt(newTile)) {//GID=0,则该Tile为空 this.stars.removeTileAt(newTile); } }, //将像素坐标转化为瓦片坐标 getTilePos: function(posInPixel) { let mapSize = this.node.getContentSize(); let tileSize = this.tiledMap.getTileSize(); let x = Math.floor(posInPixel.x / tileSize.width); let y = Math.floor(posInPixel.y / tileSize.height); return cc.p(x, y); }, updatePlayerPos: function() { let pos = this.barriers.getPositionAt(this.playerTile); this.player.setPosition(pos); },});
    最终如下图:

    3. 总结#CC.TiledMap:~properties:tmxFile//地图文件mapLoaded//地图加载是调用的函数~function:getMapSize()//setMapSize()//getTileSize()//setTileSize()//getLayer(name)//returns TieldLayergetObjectGroup(name)//returns TMXObjectGroupgetPropertiesForGID(GID)//returns Object(属性字典)#CC.TieldLayer getPositionAt(pos)//returns Vec2(像素坐标) 参数是瓦片坐标removeTileAt(pos)//瓦片坐标getTileGIDAt(pos)//returns Number(全局唯一标识,0为空)getTileAt(pos)//returns _ccsg.Sprite //removeChild(sprite);setTileGID(gid,pos)//相当于在pos位置添加GID的图块(原来的图块删除)getTileSize()//setTleSize()//getMapTileSize()#TMXObjectGroup:~properties:~function:var getObject(var objectName)//返回属性字典#_ccsg.Sprite://cocos js 里的Sprite,继承自CC.Node,而不是组件~properties:xywidthheightopacity...//节点的属性都有~function:var setSpriteFrame(var spriteFrameName)var runAction(var action) ...//节点的方法都有

    图块放置的位置是基于像素坐标,而图块在map中的索引是基于瓦片坐标(整数),所以在脚本文件中要适时转变
    对象层的对象(比如我们上面做的两个player point),在Cocos Creator中是不会显示的
    对象层记录的是位置信息,图层记录的是图片信息
    .tmx文件其实保存的图块的一种映射关系,所以图块文件和地图文件要始终放在一起,不然可能会显示出错
    GID是图块在一个地图文件中的唯一ID,(图块指的是右下角的图块文件,每一个图块都不相同,瓦片指的指地图中的块,可以是相同的图块),GID为0说明该Tile的图块为空
    当利用getPropertiesForGID(GID)之类的方法时,得到的返回值是属性字典,可以直接通过点方法得到其属性值,属性值都是字符串类型
    当用到getTileAt(pos)时,得到的返回值是Cocos2d 的Sprite,而不是Cocos Creator的Sprite,相关方法可以查找Cocos2d js的API,需要注意的一点是,Cocos2d的Sprite是继承自节点的,而不是组件式,所以我们可以直接这样写“mySprite.x = 100”而不是“mySprite.node.x = 100”
    地图中同一层只能使用一张图集里的图块

    注意:本教程资源部分来源于网络
    2 回答 2018-11-24 18:06:17
  • 创建系统服务实现开机自启动

    背景想必实现程序开机自启动,是很常见的功能了。无论是恶意程序,还是正常的应用软件,都会提供这个功能,方便用户的使用。程序开机自启动,顾名思义,就是计算机开机后,不用人为地去运行程序,程序就可以自己运行起来。对于这个功能的,一直都是杀软重点监测的地方。因为,对于病毒来说,重要的不是如何被破坏,而是如何启动。
    在过去写的大大小小的程序中,我也实现过程序自启动的功能。现在,我把这些自启动功能的实现方式进行下总结。常见的方式有:修改开机自启动注册表、开机自启动目录、创建开机自启计划任务、创建开机自启系统服务等方式。现在对这些技术一一进行分析,并形成文档分享给大家。本文介绍创建系统服务实现开机自启动的方式,其它的实现方式可以搜索我写的相关系列的文档。
    函数介绍OpenSCManager 函数
    建立了一个到服务控制管理器的连接,并打开指定的数据库。
    函数声明
    SC_HANDLE OpenSCManager( LPCTSTR lpMachineName, // computer name LPCTSTR lpDatabaseName, // SCM database name DWORD dwDesiredAccess // access type);
    参数

    lpMachineName指向零终止字符串,指定目标计算机的名称。如果该指针为NULL ,或者它指向一个空字符串,那么该函数连接到本地计算机上的服务控制管理器。
    lpDatabaseName指向零终止字符串,指定将要打开的服务控制管理数据库的名称。此字符串应被置为 SERVICES_ACTIVE_DATABASE。如果该指针为NULL ,则打开默认的 SERVICES_ACTIVE_DATABASE数据库。
    dwDesiredAccess指定服务访问控制管理器的权限。在授予要求的权限前,系统会检查调用进程的权限令牌,该令牌针对与服务控制管理器相关的安全描述符的权限控制列表。此外,该函数的调用将隐式地指定SC_MANAGER_CONNECT 的访问权限。此外,下列服务控制管理器对象的访问类型可以被指定:




    VALUE
    MEANING




    SC_MANAGER_ALL_ACCESS
    除了所有此表中列出的访问类型,还包括STANDARD_RIGHTS_REQUIRED


    SC_MANAGER_CONNECT
    可以连接到服务控制管理器


    SC_MANAGER_CREATE_SERVICE
    使要求的CreateService函数创建一个服务对象,并将其添加到数


    SC_MANAGER_ENUMERATE_SERVICE
    使要求的EnumServicesStatus功能清单的服务,这是在数据库


    SC_MANAGER_LOCK
    使要求的LockServiceDatabase功能获得锁定数据库


    SC_MANAGER_QUERY_LOCK_STATUS
    使要求的QueryServiceLockStatus检索功能锁定状态信息的数据



    返回值

    如果函数成功,返回值是一个指定的服务控制管理器数据库的句柄。如果函数失败,返回值为NULL 。要获得更详细的错误信息,可以使用GetLastError 获得错误代码。

    CreateService 函数
    创建一个服务对象,并将其添加到指定的服务控制管理器数据库的函数。
    函数声明
    SC_HANDLE CreateService( SC_HANDLE hSCManager, //服务控制管理程序的数据库句柄 LPCTSTR lpServiceName, //以NULL 结尾的服务名 LPCTSTR lpDisplayName, //以NULL 结尾的服务名 DWORD dwDesiredAccess, //指定服务返回类型 DWORD dwServiceType, //指定服务类型 DWORD dwStartType, //指定何时启动服务 DWORD dwErrorControl, //指定服务启动失败的严重程度 LPCTSTR lpBinaryPathName,//指定服务程序二进制文件的路径 LPCTSTR lpLoadOrderGroup,//指定顺序装入的服务组名 LPDWORD lpdwTagId, //忽略,NULL LPCTSTR lpDependencies, //指定启动该服务前必先启动的服务 LPCTSTR lpServiceStartName,//指定服务帐号 LPCTSTR lpPassword //指定对应的口令);
    参数

    hSCManager服务控制管理器数据库的句柄。 此句柄由OpenSCManager函数返回,并且必须 具有 SC_MANAGER_CREATE_SERVICE 的访问权限。 有关更多的信息请参阅Service安全和访问权限。
    lpServiceName要安装该服务的名称。 最大字符串长度为 256 个字符。 服务控制管理器数据库将保留在的字符的大小写,但是服务名称比较总是区分大小写。 正斜杠和一个反斜线不是有效的服务名称字符。
    lpDisplayName对被用户界面程序用来识别服务的显示名称。 此字符串具有最大长度为 256 个字符。 服务控制管理器中的情况下保留名称。 显示名称比较总是不区分大小写。
    dwDesiredAccess对服务的访问。 请求的访问之前,系统将检查调用进程的访问令牌。 一个值列表请参阅服务安全和访问权限。
    dwServiceType服务类型。 此参数可以是下列值之一:




    VALUE
    MEANING




    SERVICE_ADAPTER
    保留


    SERVICE_FILE_SYSTEM_DRIVER
    文件系统驱动服务程序


    SERVICE_KERNEL_DRIVER
    驱动服务程序


    SERVICE_RECOGNIZER_DRIVER
    保留


    SERVICE_WIN32_OWN_PROCESS
    运行于独立进程的服务程序


    SERVICE_WIN32_SHARE_PROCESS
    被多个进程共享的服务程序




    若使用了SERVICE_WIN32_OWN_PROCESS 或 SERVICE_WIN32_SHARE_PROCESS且使用LocalSystem帐号来运行该服务程序,则还可以附加使用下面的值:



    VALUE
    MEANING




    SERVICE_INTERACTIVE_PROCESS
    该服务可以与桌面程序进行交互操作




    dwStartType服务启动选项。此参数可以是下列值之一:



    VALUE
    MEANING




    SERVICE_AUTO_START
    系统启动时由服务控制管理器自动启动该服务程序


    SERVICE_BOOT_START
    用于由系统加载器创建的设备驱动程序, 只能用于驱动服务程序


    SERVICE_DEMAND_START
    由服务控制管理器(SCM)启动的服务


    SERVICE_DISABLED
    表示该服务不可启动


    SERVICE_SYSTEM_START
    用于由IoInitSystem函数创建的设备驱动程序




    dwErrorControl当该启动服务失败时产生错误的严重程度以及采取的保护措施。
    lpBinaryPathName服务程序二进制文件,完全限定路径。 如果路径中包含空格它必须被引用,以便它正确的解析。
    lpLoadOrderGroup在加载顺序此服务所属的组的名称。
    lpdwTagId指向接收 lpLoadOrderGroup 参数中指定的组中唯一的标记值的变量。
    lpDependencies空分隔名称的服务或加载顺序组系统必须在这个服务开始之前的双空终止数组的指针。 如果服务没有任何依赖关系,请指定为 NULL 或空字符串。
    lpServiceStartName该服务应在其下运行的帐户的名称。
    lpPassword由lpServiceStartName参数指定的帐户名的密码。

    返回值

    如果函数成功,返回值将是该服务的句柄。如果函数失败,则返回值为 NULL。 若要扩展的错误了解调用GetLastError。

    OpenService 函数
    打开一个已经存在的服务。
    函数声明
    SC_HANDLE WINAPI OpenService( _In_ SC_HANDLE hSCManager, _In_ LPCTSTR lpServiceName, _In_ DWORD dwDesiredAccess);
    参数

    hSCManager
    SCM数据库句柄。
    lpServiceName
    要打开服务的名字,这和CreateService形参lpServiceName一样,不是服务显示名称。
    dwDesiredAccess
    服务权限。

    返回值

    成功,返回服务句柄;失败返回NULL,可以通过GetLastError获取错误码。

    StartService 函数
    启动服务。
    函数声明
    BOOL WINAPI StartService( _In_ SC_HANDLE hService, _In_ DWORD dwNumServiceArgs, _In_opt_ LPCTSTR *lpServiceArgVectors);
    参数

    hService
    OpenService 或CreateService function返回的服务句柄,需要有SERVICE_START权限。
    dwNumServiceArgs
    下一个形参lpServiceArgVectors的字符串个数,如果lpServiceArgVectors为空,那么该参数设为0。
    lpServiceArgVectors
    传递给服务ServiceMain的参数,如果没有,可以设为NULL;否则,第一个形参lpServiceArgVectors[0]为服务的名字,其他则为需要传入的参数。

    注意

    驱动不接受这些参数,即lpServiceArgVectors为空,dwNumServiceArgs为0。
    返回值

    成功,返回非0;失败,返回0,错误码使用GetLastError获得。

    ControlService函数
    给指定的服务发送一个控制码。
    函数声明
    BOOL WINAPI ControlService( _In_ SC_HANDLE hService, _In_ DWORD dwControl, _Out_ LPSERVICE_STATUS lpServiceStatus);
    参数

    hService
    OpenService 或CreateService function返回的服务句柄;
    dwControl
    要发送的控制码,控制码可以是下列系统定义的控制码;也可以是用户自定义的控制码,范围是128-255,需要有SERVICE_USER_DEFINED_CONTROL权限。
    lpServiceStatus
    返回一个指向存储服务最新状态的结构体ServiceStatus,返回的服务信息来自SCM中最近的服务状态报告。

    返回值

    成功,返回非0;失败,返回0,错误码使用GetLastError获得。

    DeleteService 函数
    从服务控制管理器数据库中删除指定的服务。
    函数声明
    BOOL WINAPI DeleteService( _In_ SC_HANDLE hService);
    参数

    hService
    [输入]服务的句柄,可以由OpenService 或者 CreateService 函数返回,而且该服务必须要有删除权限。

    返回值

    成功,返回非0;失败,返回0,错误码使用GetLastError获得。

    StartServiceCtrlDispatcher 函数
    将服务进程的主线程连接到服务控制管理器,该线程将线程作为调用过程的服务控制分派器线程。
    函数声明
    BOOL WINAPI StartServiceCtrlDispatcher( _In_ const SERVICE_TABLE_ENTRY *lpServiceTable);
    参数

    lpServiceTable
    [输入]指向一系列SERVICE_TABLE_ENTRY结构的指针,其中包含可在调用进程中执行的每个服务的一个条目。 表中最后一个条目的成员必须具有NULL值来指定表的结尾。

    返回值

    成功,返回非0;失败,返回0,错误码使用GetLastError获得。

    RegisterServiceCtrlHandler 函数实现原理创建自启动服务的原理是:

    打开服务控制管理器数据库并获取数据库的句柄

    若要创建服务的操作,则开始创建服务,设置SERVICE_AUTO_START开机自启动;若是其他操作,则打开服务,获取服务句柄
    然后,根据服务句柄,若操作是启动,则使用StartService启动服务;若服务是停止,则使用ControlService停止服务;若操作是删除,则使用DeleteService删除服务
    关闭服务句柄和服务控制管理器数据库句柄

    其中,设置自启动服务启动的程序,并不是普通的程序,而是要求程序要创建了服务入口点函数,否则,不能创建系统服务。创建服务程序的原理是是:
    服务程序主函数(main)调用系统函数StartServiceCtrlDispatcher连接程序主线程到服务控制管理程序(其中定义了服务入口点函数是ServiceMain)。服务控制管理程序启动服务程序后,等待服务程序主函数调用StartServiceCtrlDispatcher。如果没有调用该函数,设置服务入口点,则会报错。
    执行服务初始化任务(同时执行多个服务的服务有多个入口点函数),首先调用RegisterServiceCtrlHandler定义控制处理程序函数(本例中是ServiceCtrlHandle),初始化后通过SetServiceStatus设定进行运行状态,然后运行服务代码。
    控制处理程序(Handler)在服务收到控制请求时由控制分发线程引用(最少要有停止服务的能力)。
    编码实现创建系统服务// 0 加载服务 1 启动服务 2 停止服务 3 删除服务BOOL SystemServiceOperate(char *lpszDriverPath, int iOperateType){ BOOL bRet = TRUE; char szName[MAX_PATH] = { 0 }; ::lstrcpy(szName, lpszDriverPath); // 过滤掉文件目录,获取文件名 ::PathStripPath(szName); SC_HANDLE shOSCM = NULL, shCS = NULL; SERVICE_STATUS ss; DWORD dwErrorCode = 0; BOOL bSuccess = FALSE; // 打开服务控制管理器数据库 shOSCM = ::OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS); if (!shOSCM) { ShowError("OpenSCManager"); return FALSE; } if (0 != iOperateType) { // 打开一个已经存在的服务 shCS = OpenService(shOSCM, szName, SERVICE_ALL_ACCESS); if (!shCS) { ShowError("OpenService"); ::CloseServiceHandle(shOSCM); shOSCM = NULL; return FALSE; } } switch (iOperateType) { case 0: { // 创建服务 // SERVICE_AUTO_START 随系统自动启动 // SERVICE_DEMAND_START 手动启动 shCS = ::CreateService(shOSCM, szName, szName, SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS, SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, lpszDriverPath, NULL, NULL, NULL, NULL, NULL); if (!shCS) { ShowError("CreateService"); bRet = FALSE; } break; } case 1: { // 启动服务 if (!::StartService(shCS, 0, NULL)) { ShowError("StartService"); bRet = FALSE; } break; } case 2: { // 停止服务 if (!::ControlService(shCS, SERVICE_CONTROL_STOP, &ss)) { ShowError("ControlService"); bRet = FALSE; } break; } case 3: { // 删除服务 if (!::DeleteService(shCS)) { ShowError("DeleteService"); bRet = FALSE; } break; } default: break; } // 关闭句柄 if (shCS) { ::CloseServiceHandle(shCS); shCS = NULL; } if (shOSCM) { ::CloseServiceHandle(shOSCM); shOSCM = NULL; } return bRet;}
    创建服务程序int _tmain(int argc, _TCHAR* argv[]){ // 注册服务入口函数 SERVICE_TABLE_ENTRY stDispatchTable[] = { { g_szServiceName, (LPSERVICE_MAIN_FUNCTION)ServiceMain }, { NULL, NULL } }; ::StartServiceCtrlDispatcher(stDispatchTable); return 0;}
    void __stdcall ServiceMain(DWORD dwArgc, char *lpszArgv){ g_ServiceStatus.dwServiceType = SERVICE_WIN32; g_ServiceStatus.dwCurrentState = SERVICE_START_PENDING; g_ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP; g_ServiceStatus.dwWin32ExitCode = 0; g_ServiceStatus.dwServiceSpecificExitCode = 0; g_ServiceStatus.dwCheckPoint = 0; g_ServiceStatus.dwWaitHint = 0; g_ServiceStatusHandle = ::RegisterServiceCtrlHandler(g_szServiceName, ServiceCtrlHandle); g_ServiceStatus.dwCurrentState = SERVICE_RUNNING; g_ServiceStatus.dwCheckPoint = 0; g_ServiceStatus.dwWaitHint = 0; ::SetServiceStatus(g_ServiceStatusHandle, &g_ServiceStatus); // 自己程序实现部分代码放在这里 // !!注意!! 此处一定要为死循环, 否则在关机再开机的情况(不是点击重启), 不能创建用户进程 while (TRUE) { Sleep(5000); DoTask(); }}
    void __stdcall ServiceCtrlHandle(DWORD dwOperateCode){ switch (dwOperateCode) { case SERVICE_CONTROL_PAUSE: { // 暂停 g_ServiceStatus.dwCurrentState = SERVICE_PAUSED; break; } case SERVICE_CONTROL_CONTINUE: { // 继续 g_ServiceStatus.dwCurrentState = SERVICE_RUNNING; break; } case SERVICE_CONTROL_STOP: { // 停止 g_ServiceStatus.dwWin32ExitCode = 0; g_ServiceStatus.dwCurrentState = SERVICE_STOPPED; g_ServiceStatus.dwCheckPoint = 0; g_ServiceStatus.dwWaitHint = 0; ::SetServiceStatus(g_ServiceStatusHandle, &g_ServiceStatus); break; } case SERVICE_CONTROL_INTERROGATE: { // 询问 break; } default: break; }}
    程序测试在 main 函数中调用上述封装好的函数接口,进行测试。main 函数为:
    int _tmain(int argc, _TCHAR* argv[]){ BOOL bRet = FALSE; char szFileName[MAX_PATH] = "C:\\Users\\DemonGan\\Desktop\\AutoRun_Service_Test\\Debug\\ServiceTest.exe"; // 创建并启动服务 bRet = SystemServiceOperate(szFileName, 0); if (FALSE == bRet) { printf("Create Error!\n"); } bRet = SystemServiceOperate(szFileName, 1); if (FALSE == bRet) { printf("Start Error!\n"); } printf("Create and Start OK.\n"); system("pause"); // 停止并删除服务 bRet = SystemServiceOperate(szFileName, 2); if (FALSE == bRet) { printf("Stop Error!\n"); } bRet = SystemServiceOperate(szFileName, 3); if (FALSE == bRet) { printf("Delete Error!\n"); } printf("Stop and Delete OK.\n"); system("pause"); return 0;}
    测试结果:
    “以管理员身份运行程序”打开程序,提示系统服务创建并启动成功。

    查看任务管理器,发现“ServiceTest.exe”进程已经启动。

    接着,打开服务管理器,查看服务,“ServiceTest.exe”服务成功创建。

    查看服务属性,路径和启动类型正确。

    执行服务停止删除,程序提示执行成功。检查任务管理器和服务管理器,均为发现“ServiceTest.exe”服务,所以,删除成功。

    总结注意,创建系统服务,要求要有管理员权限。其中,创建系统服务启动的程序,它需要额外创建服务入口点函数ServiceMain,这样才能创建服务成功,否则会报错。
    系统服务是运行在Session 0,系统服务的Session 0隔离,阻断了系统服务和用户桌面进程之间进行交互和通信的桥梁。各个Session之间是相互独立的。在不同Session中运行的实体,相互之间不能发送Windows消息、共享UI元素或者是在没有指定他们有权限访问全局名字空间(并且提供正确的访问控制设置)的情况下,共享核心对象。这样也就是为什么,在系统服务不能显示程序界面,也不能用常规的方式创建有界面的进程。要想创建带界面的进程,要使用另外的方式,我已经写成相应的文档分享给大家了,大家可以搜索我写的相关文档来参考。
    参考参考自《Windows黑客编程技术详解》一书
    1 回答 2018-11-24 17:08:53
  • 内存快速搜索遍历

    背景相比很多人都用过内存搜索软件 Cheat Engine 吧,它里面提供了强大进程内存搜索功能,搜索速度很快,搜索结果也很精确。我之前对内存搜索也稍微专研了一下,是因为当时需要写一个小程序,那个小程序的功能就是可以搜索出指定进程指定值的内存地址,这个CE就能做,只不过是要在自己的程序里实现内存的搜索。
    内存的遍历搜索,说难也不难,说容易也不容易。因为你可以做得比较简单,也可以做得比较完美,这主要是在搜索效率上的区别而已。简单的搜索方法就是直接暴力搜索内存,直接从0地址搜索到0x7FFFFFFF地址处,因为低 2GB 进程空间是用户空间。然后,匹配值就可以了。难点的,就是过滤掉一些内存地址,不用搜索。例如,进程加载基址之前的地址空间,就可以不用搜索等等,以此来缩小搜索的范围,提升搜索效率。
    本文就是对内存遍历实现快速搜索,当然,这肯定不会是最快的搜索方式,只是相对的快速。我们也是通过加载基址,以及内存空间地址信息,缩小搜索的范围,提升搜索效率。现在,就把实现过程整理成文档,分享给大家。
    函数介绍VirtualQueryEx 函数
    查询地址空间中内存地址的信息。
    函数声明
    DWORD VirtualQueryEx( HANDLE hProcess, LPCVOID lpAddress, PMEMORY_BASIC_INFORMATION lpBuffer, DWORD dwLength);
    参数

    hProcess:进程句柄。lpAddress:查询内存的地址。lpBuffer:指向MEMORY_BASIC_INFORMATION结构的指针,用于接收内存信息。dwLength:MEMORY_BASIC_INFORMATION结构的大小。
    返回值

    返回值是信息缓冲区中返回的实际字节数。如果函数失败,返回值是 0。为了获得更多的错误信息,调用GetLastError。

    MEMORY_BASIC_INFORMATION 结构体
    结构体声明
    typedef struct _MEMORY_BASIC_INFORMATION { PVOID BaseAddress; // 区域基地址 PVOID AllocationBase; // 分配基地址 DWORD AllocationProtect; // 区域被初次保留时赋予的保护属性 SIZE_T RegionSize; // 区域大小(以字节为计量单位) DWORD State; // 状态(MEM_FREE、MEM_RESERVE或 MEM_COMMIT) DWORD Protect; // 保护属性 DWORD Type; // 类型} MEMORY_BASIC_INFORMATION, *PMEMORY_BASIC_INFORMATION;
    成员

    BaseAddress:与lpAddress参数的值相同,但是四舍五入为页面的边界值。AllocationBas:指明用VirtualAlloc函数分配内存区域的基地址。lpAddress在该区域之内。AllocationProtect:指明该地址空间区域被初次保留时赋予该区域的保护属性。PAGE_READONLY:只读属性,如果试图进行写操作,将引发访问违规。如果系统区分只读、执行两种属性,那么试图在该区域执行代码也将引发访问违规。PAGE_READWRITE:允许读写。PAGE_EXECUTE:只允许执行代码,对该区域试图进行读写操作将引发访问违规。PAGE_EXECUTE_READ:允许执行和读取。PAGE_EXECUTE_READWRITE:允许读写和执行代码。PAGE_EXECUTE_WRITECOPY:对于该地址空间的区域,不管执行什么操作,都不会引发访问违规。如果试图在该页面上的内存中进行写入操作,就会将它自己的私有页面(受页文件的支持)拷贝赋予该进程。PAGE_GUARD:在页面上写入一个字节时使应用程序收到一个通知(通过一个异常条件)。PAGE_NOACCESS:禁止一切访问。PAGE_NOCACHE:停用已提交页面的高速缓存。一般情况下最好不要使用该标志,因为它主要是供需要处理内存缓冲区的硬件设备驱动程序的开发人员使用的。RegionSize 用于指明内存块从基地址即BaseAddress开始的所有页面的大小(以字节为计量单位)这些页面与含有用LpAddress参数设定的地址的页面拥有相同的保护属性、状态和类型。State:用于指明所有相邻页面的状态。MEM_COMMIT:指明已分配物理内存或者系统页文件。MEM_FREE:空闲状态。该区域的虚拟地址不受任何内存的支持。该地址空间没有被保留。该状态下AllocationBase、AllocationProtect、Protect和Type等成员均未定义。MEM_RESERVE:指明页面被保留,但是没有分配任何物理内存。该状态下Protect成员未定。Protect:用于指明所有相邻页面(内存块)的保护属性。这些页面与含有拥有相同的保属性、状态和类型。意义同AllocationProtect。Type:用于指明支持所有相邻页面的物理存储器的类型(MEM_IMAGE,MEM_MAPPED或MEM_PRIVATE)。这些相邻页面拥有相同的保护属性、状态和类型。如果是Windows 98,那么这个成员将总是MEM_PRIVATE 。MEM_IMAGE:指明该区域的虚拟地址原先受内存映射的映像文件(如.exe或DLL文件)的支持,但也许不再受映像文件的支持。例如,当写入模块映像中的全局变量时,“写入时拷贝”的机制将由页文件来支持特定的页面,而不是受原始映像文件的支持。MEM_MAPPED:该区域的虚拟地址原先是受内存映射的数据文件的支持,但也许不再受数据文件的支持。例如,数据文件可以使用“写入时拷贝”的保护属性来映射。对文件的任何写入操作都将导致页文件而不是原始数据支持特定的页面。MEM_PRIVATE:指明该内存区域是私有的。不被其他进程共享。

    ReadProcessMemory 函数
    在指定的进程中从内存区域读取数据。 要读取的整个区域必须可访问或操作失败。
    函数声明
    BOOL WINAPI ReadProcessMemory( _In_ HANDLE hProcess, _In_ LPCVOID lpBaseAddress, _Out_ LPVOID lpBuffer, _In_ SIZE_T nSize, _Out_ SIZE_T *lpNumberOfBytesRead);
    参数

    hProcess [in]具有正在读取的内存的进程的句柄。 句柄必须具有进程的PROCESS_VM_READ访问权限。lpBaseAddress [in]指向从中读取的指定进程中的基地址的指针。 在发生任何数据传输之前,系统将验证指定大小的基地址和内存中的所有数据都可访问以进行读取访问,如果不可访问,则该函数将失败。lpBuffer [out]指向从指定进程的地址空间接收内容的缓冲区的指针。nSize [in]要从指定进程读取的字节数。lpNumberOfBytesRead [out]指向一个变量的指针,该变量接收传输到指定缓冲区的字节数。 如果lpNumberOfBytesRead为NULL,则该参数将被忽略。
    返回值

    如果函数成功,则返回值不为零。如果函数失败,返回值为0(零)。 要获取扩展错误信息,请调用GetLastError。

    实现原理实现内存的快速搜索,我们主要是缩小搜索的范围,来提升的搜索效率。缩小搜索范围的方法有,一是过滤掉进程加载基址之前的内存地址;二是获取内存空间地址信息,把内存状态不是 MEM_COMMIT 以及保护属性没有读权限的内存区域都过滤掉。
    所以,获取进程加载基址以及获取内存空间地址信息比较关键。
    大概分为以下 7 个步骤:

    首先,我们调用 OpenProcess 函数根据进程 PID 打开进程,并获取进程的句柄,进程句柄的权限为 PROCESS_ALL_ACCESS
    然后,我们根据进程句柄,获取指定进程的加载基址。对于进程加载基址的获取,我们使用的是 EnumProcessModules 函数来获取。其它的的进程基址获取方法,可以参考本站上其他网友写的“获取指定进程的加载基址”这篇文章
    接着,以进程加载基址作为内存搜索的起始地址,调用 VirtualQueryEx 函数查询地址空间中内存地址的信息,然后将内存页面状态不是MEM_COMMIT 过滤掉,即过滤掉没有分配物理内存或者系统页文件。同时,也把没有读权限的页面属性保护都过滤掉
    通过内存地址的信息过滤之后,我们就可以调用 ReadProcessMemory 函数把对应的内存区域读取到自己进程的缓冲区中
    接着,我们就可以匹配内存,搜索指定的内存,并获取制定进程内存地址
    然后,获取下一块内存区域的起始地址,继续重复上面3、4、5步操作,直到满足退出条件
    最后,我们就释放内存,并关闭进程句柄

    这样,就是先了内存的快速遍历搜索。
    编码实现// 搜索内存BOOL SearchMemory(DWORD dwProcessId, PVOID pSearchBuffer, DWORD dwSearchBufferSize){ // 根据PID, 打开进程获取进程句柄 HANDLE hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId); if (NULL == hProcess) { ShowError("OpenProcess"); return FALSE; } // 获取进程加载基址 HMODULE hModule = NULL; ::EnumProcessModules(hProcess, &hModule, sizeof(HMODULE), NULL); // 把加载基址作为遍历内存的起始地址, 开始遍历 BYTE *pSearchAddress = (BYTE *)hModule; MEMORY_BASIC_INFORMATION mbi = {0}; DWORD dwRet = 0; BOOL bRet = FALSE; BYTE *pTemp = NULL; DWORD i = 0; BYTE *pBuf = NULL; while (TRUE) { // 查询地址空间中内存地址的信息 ::RtlZeroMemory(&mbi, sizeof(mbi)); dwRet = ::VirtualQueryEx(hProcess, pSearchAddress, &mbi, sizeof(mbi)); if (0 == dwRet) { break; } // 过滤内存空间, 根据内存的状态和保护属性进行过滤 if ((MEM_COMMIT == mbi.State) && (PAGE_READONLY == mbi.Protect || PAGE_READWRITE == mbi.Protect || PAGE_EXECUTE_READ == mbi.Protect || PAGE_EXECUTE_READWRITE == mbi.Protect)) { // 申请动态内存 pBuf = new BYTE[mbi.RegionSize]; ::RtlZeroMemory(pBuf, mbi.RegionSize); // 读取整块内存 bRet = ::ReadProcessMemory(hProcess, mbi.BaseAddress, pBuf, mbi.RegionSize, &dwRet); if (FALSE == bRet) { ShowError("ReadProcessMemory"); break; } // 匹配内存 for (i = 0; i < (mbi.RegionSize - dwSearchBufferSize); i++) { pTemp = (BYTE *)pBuf + i; if (RtlEqualMemory(pTemp, pSearchBuffer, dwSearchBufferSize)) { // 显示搜索到的地址 printf("0x%p\n", ((BYTE *)mbi.BaseAddress + i)); } } // 释放内存 delete[]pBuf; pBuf = NULL; } // 继续对下一块内存区域进行遍历 pSearchAddress = pSearchAddress + mbi.RegionSize; } // 释放内存, 关闭句柄 if (pBuf) { delete[]pBuf; pBuf = NULL; } ::CloseHandle(hProcess); return TRUE;}
    程序测试我们直接运行程序,对 520.exe 进程进行搜索,搜索值为 0x00905A4D 的地址都哪些,程序成功列举所有的地址。

    总结这个程序,通过以进程加载基址为搜索起始地址、判断地址内存空间信息过滤一些内存状态不是 MEM_COMMIT 以及保护属性没有读权限的内存区域,以此来缩小搜索的范围,提升搜索的效率。
    大家注意理解 VirtualQueryEx 函数以及 ReadProcessMemory 函数的参数使用方式,同时也要注意不同进程内存空间的转换。
    参考参考自《Windows黑客编程技术详解》一书
    2 回答 2018-11-23 18:38:50
  • 【Cocos Creator 实战教程(2)】——天天酷跑(动画、动作相关)

    一、涉及知识点
    添加背景动画添加人物动作碰撞检测
    二、步骤2.1 准备工作新建一个项目,这回在资源管理器新建一个animation文件夹,用来存放节点动画。
    同时,将背景图拉上,这样背景图动起来时,就相当于人物相对位置再移动,跑了起来。

    2.2 背景动画——让背景动起来为背景节点添加滚动动画 。
    首先在animation新建一个animation clip取名为bg_roll。
    其次为背景节点添加animation组件(在其他组件中),并将bg_roll添加进去。

    然后选择动画编辑器编辑动画。


    这样我们就得到了一个关键帧。
    这里将一下关键帧的作用。
    动画是由一帧一帧的图片构成的,而我们指定了属性的关键帧后,就知道如何控制动画序列的中间步骤。
    例如背景滚动,只需要指定初始位置和结束位置作为关键帧,而人物动作则有多个关键帧。

    最后选上play on load默认自动播放,这样背景就滚动起来了。(记得时间间隔久一点,可以滚动的慢一点)
    2.4 大圣动画这里我们要使用的的是动态加载图片,属性选择cc.spriteframe

    然后我们把要加载的图片拖进来,这里需要注意的是因为是一系列的图片,所以我们最后是生成了一张plist图集,制作方法https://docs.cocos.com/creator/manual/zh/asset-workflow/atlas.html
    接着我们创建关键帧并把大圣跑动的一张一张图片拖入到动画区,最后调整wrapMode为loop让这个动画循环播放。

    2.3 导弹动画这里我们要添加两个Clip,一个是高空导弹,一个是低空导弹 。这时属性就要选择position因为位置(x,y)不同。这节课我们一共用到了三种动画属性,其实还有很多种属性,大家可以查询官网自己学习。



    2.4 为碰撞检测添加事件这里要先讲一下概念。
    虽然我们选择背景为静止坐标系时,大圣和导弹都在相对移动,但是其实在制作过程中,我们的大圣并没有移动,而是背景和导弹在移动。
    所以我们想要选择导弹在大圣附近那几帧图片时,只需把大圣作为静止坐标系让导弹移动。
    具体做法便是选中Bomb然后播放动画等到导弹在大圣附近时停止,插入事件,添加碰撞检测。

    2.5 编写脚本添加动作Game.js
    cc.Class({ extends: cc.Component, properties: { king:{ default:null, type:cc.Node, } }, onLoad: function () { var self = this; //左侧蹲,右侧跳 this.node.on('touchstart',function(event){ var visibleSize = cc.director.getVisibleSize(); if(event.getLocationX()<visibleSize.width/2){ self.king.getComponent('King').down(); }else{ self.king.getComponent('King').jump(); } }); //左侧松手就恢复跑的状态 this.node.on('touchend',function(event){ var visibleSize = cc.director.getVisibleSize(); if(event.getLocationX()<visibleSize.width/2){ self.king.getComponent('King').downRelease(); }else{ // self.king.getComponent('King').jump(); } }); },});
    King.js
    cc.Class({ extends: cc.Component, properties: { // 主角跳跃高度 jumpHeight: 0, // 主角跳跃持续时间 jumpDuration: 0, //主角状态 state:'run', }, //跑 run:function(){ this.getComponent(cc.Animation).play('king_run'); this.state = 'run'; }, //跳 jump:function(){ if(this.state == 'run'){ this.state = 'jump'; this.getComponent(cc.Animation).stop(); this.node.runAction(cc.sequence(cc.jumpBy(this.jumpDuration, cc.p(0,0), this.jumpHeight, 1), cc.callFunc(function() { this.run(); }, this))); } }, //弯腰跑 down:function(){ if(this.state == 'run'){ this.state = 'down'; this.node.runAction(cc.scaleTo(0.05, 1, 0.5)); } }, //腰累了 downRelease:function(){ if(this.state == 'down'){ this.node.runAction(cc.sequence(cc.scaleTo(0.05, 1, 1), cc.callFunc(function() { this.run(); }, this))); } },});
    2.6 完善碰撞检测脚本Bomb.js
    cc.Class({ extends: cc.Component, properties: { king:{ default:null, type:cc.Node, } }, //判断高空导弹来时,猴哥是否蹲下(响应之前设置的帧事件) judgeDown:function(){ if(this.king.getComponent('King').state == 'down'){ console.log("down---------------------"); }else{ cc.director.loadScene('Over'); } }, //判断低空导弹来时,猴哥是否跳起 judgeJump:function(){ if(this.king.getComponent('King').state == 'jump'){ console.log("jump---------------------"); }else{ cc.director.loadScene('Over'); } }, onLoad: function () { let self = this; //每隔2秒随机发射高空和低空导弹 this.schedule(function(){ if(Math.random()>0.5){ this.getComponent(cc.Animation).play('bomb_high'); }else{ this.getComponent(cc.Animation).play('bomb_low'); } },3); },});
    2.7 游戏结束
    Over.js
    cc.Class({ extends: cc.Component, properties: { }, reTry: function(){ cc.director.loadScene('Game'); }, onLoad: function () { },});
    3. 注意
    部分资料素材来源于网络。要自学事件响应的代码,最好有js基础,多参考文档。跳跃和下蹲在现实中会另做一套动画,这里直接使用了改变位置和压缩图片的方法。想想如何替换成另一套动画吧。(提示:动画该开始并不加载(play on road),触屏事件响应时再加载,欢迎在评论区附上心得)这次的碰撞检测做的很简单,实际上还有蒙版法,像素点法等一系列方法,之后会给大家详细介绍。初次写博客请大家多砸评论。
    3 回答 2018-11-22 14:10:27
  • 【Cocos Creator 实战教程(1)】——人机对战五子棋(节点事件相关)

    一、涉及知识点
    场景切换按钮事件监听节点事件监听节点数组循环中闭包的应用动态更换sprite图片定时器预制资源
    二、步骤2.1 准备工作首先,我们要新建一个空白工程,并在资源管理器中新建几个文件夹

    在这些文件夹中,我们用来存放不同的资源,其中

    Scene用来存放场景,我们可以把场景看作一个关卡,当关卡切换时,场景就切换了
    Script用来存放脚本文件
    Texture用来存放显示的资源,例如音频,图片
    Prefab用来存放预制资源,接下来我会详细的介绍

    接下来,我们在Scene文件夹中新建一个场景(右击文件夹->新建->Scene),命名为Menu,接着导入背景图片(直接拖拽即可)。最后调整图片大小使图片铺满背景,效果如图。

    2.2 按钮监听与场景切换接下来我们来学习此次实战的第一个难点,按钮监听与场景切换。
    首先,创建一个Button节点,并删除label,放在人机博弈的按钮上,并在属性上调成透明样式。

    接下来,新建一个Game场景,并添加一个棋盘节点,并把锚点设为(0,0)。

    这里,讲一下锚点的作用。
    anchor point 究竟是怎么回事? 之所以造成不容易理解的是因为我们平时看待一个图片是以图片的中心点这一个维度来决定图片的位置的。而在cocos2d中决定一个图片的位置是由两个维度一个是 position 另外一个是anchor point。只要我们搞清楚他们的关系,自然就迎刃而解。默认情况下,anchor point在图片的中心位置(0.5, 0.5),取值在0到1之间的好处就是,锚点不会和具体物体的大小耦合,也即不用关注物件大小,而应取其对应比率,如果把锚点改成(0,0),则进行放置位置时,以图片左下角作为起始点。也就是说,把position设置成(x,y)时,画到屏幕上需要知道:到底图片上的哪个点放在屏幕的(x,y)上,而anchor point就是这个放置的点,anchor point是可以超过图片边界的,比如下例中的(-1,-1),表示从超出图片左下角一个宽和一个高的地方放置到屏幕的(0,0)位置(向右上偏移10个点才开始到图片的左下角,可以认为向右上偏移了10个点的空白区域)
    他们的关系是这样的(假设actualPosition.x,actualPosition.y是真实图片位置的中点):actualPosition.x = position.x + width*(0.5 - anchor_point.x);acturalPosition.y = position.y + height*(0.5 - anchor_point.y)actualPosition 是sprite实际上在屏幕显示的位置, poistion是 程序设置的, achor_point也是程序设置的。
    然后,我们需要新建一个脚本,Menu.js,并添加开始游戏方法。
    cc.Class({ extends: cc.Component, startGame:function(){ cc.director.loadScene('Game');//这里便是运用导演类进行场景切换的代码 }});
    这里提示以下,编辑脚本是需要下载插件的,我选择了VScode,还是很好用的。
    最后我们将其添加为Menu场景的Canvas的组件(添加组件->脚本组件->menu),并在BtnP2C节点上添加按钮监听响应。


    这样,按钮监听就完成了。现在我们在Menu场景里点击一下人机按钮就会跳转到游戏场景了。
    2.3 预制资源预制资源一般是在场景里面创建独立的子界面或子窗口,即预制资源是存放在资源中,并不是节点中,例如本节课中的棋子。
    现在我们就来学习一下如何制作预制资源。

    再将black节点改名为Chess拖入下面Prefab文件夹使其成为预制资源。
    这其中,SpriteFrame 是核心渲染组件 Sprite 所使用的资源,设置或替换 Sprite 组件中的 spriteFrame 属性,就可以切换显示的图像,将其去掉防止图片被预加载,即棋子只是一个有大小的节点不显示图片也就没有颜色。
    2.4 结束场景直接在Game场景上制作结束场景。

    2.5 游戏脚本制作人机对战算法参考了这里https://blog.csdn.net/onezeros/article/details/5542379
    具体步骤便是初始化棋盘上225个棋子节点,并为每个节点添加事件,点击后动态显示棋子图片。
    代码如下:
    cc.Class({ extends: cc.Component, properties: { overSprite:{ default:null, type:cc.Sprite, }, overLabel:{ default:null, type:cc.Label }, chessPrefab:{//棋子的预制资源 default:null, type:cc.Prefab }, chessList:{//棋子节点的集合,用一维数组表示二维位置 default: [], type: [cc.node] }, whiteSpriteFrame:{//白棋的图片 default:null, type:cc.SpriteFrame }, blackSpriteFrame:{//黑棋的图片 default:null, type:cc.SpriteFrame }, touchChess:{//每一回合落下的棋子 default:null, type:cc.Node, visible:false//属性窗口不显示 }, gameState:'white', fiveGroup:[],//五元组 fiveGroupScore:[]//五元组分数 }, //重新开始 startGame(){ cc.director.loadScene("Game"); }, //返回菜单 toMenu(){ cc.director.loadScene("Menu"); }, onLoad: function () { this.overSprite.node.x = 10000;//让结束画面位于屏幕外 var self = this; //初始化棋盘上225个棋子节点,并为每个节点添加事件 for(var y = 0;y<15;y++){ for(var x = 0;x < 15;x++){ var newNode = cc.instantiate(this.chessPrefab);//复制Chess预制资源 this.node.addChild(newNode); newNode.setPosition(cc.p(x*40+20,y*40+20));//根据棋盘和棋子大小计算使每个棋子节点位于指定位置 newNode.tag = y*15+x;//根据每个节点的tag就可以算出其二维坐标 newNode.on(cc.Node.EventType.TOUCH_END,function(event){ self.touchChess = this; if(self.gameState === 'black' && this.getComponent(cc.Sprite).spriteFrame === null){ this.getComponent(cc.Sprite).spriteFrame = self.blackSpriteFrame;//下子后添加棋子图片使棋子显示 self.judgeOver(); if(self.gameState == 'white'){ self.scheduleOnce(function(){self.ai()},1);//延迟一秒电脑下棋 } } }); this.chessList.push(newNode); } } //开局白棋(电脑)在棋盘中央下一子 this.chessList[112].getComponent(cc.Sprite).spriteFrame = this.whiteSpriteFrame; this.gameState = 'black'; //添加五元数组 //横向 for(var y=0;y<15;y++){ for(var x=0;x<11;x++){ this.fiveGroup.push([y*15+x,y*15+x+1,y*15+x+2,y*15+x+3,y*15+x+4]); } } //纵向 for(var x=0;x<15;x++){ for(var y=0;y<11;y++){ this.fiveGroup.push([y*15+x,(y+1)*15+x,(y+2)*15+x,(y+3)*15+x,(y+4)*15+x]); } } //右上斜向 for(var b=-10;b<=10;b++){ for(var x=0;x<11;x++){ if(b+x<0||b+x>10){ continue; }else{ this.fiveGroup.push([(b+x)*15+x,(b+x+1)*15+x+1,(b+x+2)*15+x+2,(b+x+3)*15+x+3,(b+x+4)*15+x+4]); } } } //右下斜向 for(var b=4;b<=24;b++){ for(var y=0;y<11;y++){ if(b-y<4||b-y>14){ continue; }else{ this.fiveGroup.push([y*15+b-y,(y+1)*15+b-y-1,(y+2)*15+b-y-2,(y+3)*15+b-y-3,(y+4)*15+b-y-4]); } } } }, //电脑下棋逻辑 ai:function(){ //评分 for(var i=0;i<this.fiveGroup.length;i++){ var b=0;//五元组里黑棋的个数 var w=0;//五元组里白棋的个数 for(var j=0;j<5;j++){ this.getComponent(cc.Sprite).spriteFrame if(this.chessList[this.fiveGroup[i][j]].getComponent(cc.Sprite).spriteFrame == this.blackSpriteFrame){ b++; }else if(this.chessList[this.fiveGroup[i][j]].getComponent(cc.Sprite).spriteFrame == this.whiteSpriteFrame){ w++; } } if(b+w==0){ this.fiveGroupScore[i] = 7; }else if(b>0&&w>0){ this.fiveGroupScore[i] = 0; }else if(b==0&&w==1){ this.fiveGroupScore[i] = 35; }else if(b==0&&w==2){ this.fiveGroupScore[i] = 800; }else if(b==0&&w==3){ this.fiveGroupScore[i] = 15000; }else if(b==0&&w==4){ this.fiveGroupScore[i] = 800000; }else if(w==0&&b==1){ this.fiveGroupScore[i] = 15; }else if(w==0&&b==2){ this.fiveGroupScore[i] = 400; }else if(w==0&&b==3){ this.fiveGroupScore[i] = 1800; }else if(w==0&&b==4){ this.fiveGroupScore[i] = 100000; } } //找最高分的五元组 var hScore=0; var mPosition=0; for(var i=0;i<this.fiveGroupScore.length;i++){ if(this.fiveGroupScore[i]>hScore){ hScore = this.fiveGroupScore[i]; mPosition = (function(x){//js闭包 return x; })(i); } } //在最高分的五元组里找到最优下子位置 var flag1 = false;//无子 var flag2 = false;//有子 var nPosition = 0; for(var i=0;i<5;i++){ if(!flag1&&this.chessList[this.fiveGroup[mPosition][i]].getComponent(cc.Sprite).spriteFrame == null){ nPosition = (function(x){return x})(i); } if(!flag2&&this.chessList[this.fiveGroup[mPosition][i]].getComponent(cc.Sprite).spriteFrame != null){ flag1 = true; flag2 = true; } if(flag2&&this.chessList[this.fiveGroup[mPosition][i]].getComponent(cc.Sprite).spriteFrame == null){ nPosition = (function(x){return x})(i); break; } } //在最最优位置下子 this.chessList[this.fiveGroup[mPosition][nPosition]].getComponent(cc.Sprite).spriteFrame = this.whiteSpriteFrame; this.touchChess = this.chessList[this.fiveGroup[mPosition][nPosition]]; this.judgeOver(); }, judgeOver:function(){ var x0 = this.touchChess.tag % 15; var y0 = parseInt(this.touchChess.tag / 15); //判断横向 var fiveCount = 0; for(var x = 0;x < 15;x++){ if((this.chessList[y0*15+x].getComponent(cc.Sprite)).spriteFrame === this.touchChess.getComponent(cc.Sprite).spriteFrame){ fiveCount++; if(fiveCount==5){ if(this.gameState === 'black'){ this.overLabel.string = "你赢了"; this.overSprite.node.x = 0; }else{ this.overLabel.string = "你输了"; this.overSprite.node.x = 0; } this.gameState = 'over'; return; } }else{ fiveCount=0; } } //判断纵向 fiveCount = 0; for(var y = 0;y < 15;y++){ if((this.chessList[y*15+x0].getComponent(cc.Sprite)).spriteFrame === this.touchChess.getComponent(cc.Sprite).spriteFrame){ fiveCount++; if(fiveCount==5){ if(this.gameState === 'black'){ this.overLabel.string = "你赢了"; this.overSprite.node.x = 0; }else{ this.overLabel.string = "你输了"; this.overSprite.node.x = 0; } this.gameState = 'over'; return; } }else{ fiveCount=0; } } //判断右上斜向 var f = y0 - x0; fiveCount = 0; for(var x = 0;x < 15;x++){ if(f+x < 0 || f+x > 14){ continue; } if((this.chessList[(f+x)*15+x].getComponent(cc.Sprite)).spriteFrame === this.touchChess.getComponent(cc.Sprite).spriteFrame){ fiveCount++; if(fiveCount==5){ if(this.gameState === 'black'){ this.overLabel.string = "你赢了"; this.overSprite.node.x = 0; }else{ this.overLabel.string = "你输了"; this.overSprite.node.x = 0; } this.gameState = 'over'; return; } }else{ fiveCount=0; } } //判断右下斜向 f = y0 + x0; fiveCount = 0; for(var x = 0;x < 15;x++){ if(f-x < 0 || f-x > 14){ continue; } if((this.chessList[(f-x)*15+x].getComponent(cc.Sprite)).spriteFrame === this.touchChess.getComponent(cc.Sprite).spriteFrame){ fiveCount++; if(fiveCount==5){ if(this.gameState === 'black'){ this.overLabel.string = "你赢了"; this.overSprite.node.x = 0; }else{ this.overLabel.string = "你输了"; this.overSprite.node.x = 0; } this.gameState = 'over'; return; } }else{ fiveCount=0; } } //没有输赢交换下子顺序 if(this.gameState === 'black'){ this.gameState = 'white'; }else{ this.gameState = 'black'; } }});
    这里便用到了节点监听,节点数组,定时器和动态更换sprite图片。
    新建Game脚本添加到ChessBoard节点下

    3 注意课程到这里就结束了,本课程部分资源来源于网络。
    第一次写技术分享,如果大家有什么好的建议和问题请在下方留言区留言。
    4 回答 2018-11-21 20:32:10
  • 创建计划任务实现开机自启动

    背景想必实现程序开机自启动,是很常见的功能了。无论是恶意程序,还是正常的应用软件,都会提供这个功能,方便用户的使用。程序开机自启动,顾名思义,就是计算机开机后,不用人为地去运行程序,程序就可以自己运行起来。对于这个功能的,一直都是杀软重点监测的地方。因为,对于病毒来说,重要的不是如何被破坏,而是如何启动。
    在过去写的大大小小的程序中,我也实现过程序自启动的功能。现在,我把这些自启动功能的实现方式进行下总结。常见的方式有:修改开机自启动注册表、开机自启动目录、创建开机自启计划任务、创建开机自启系统服务等方式。现在对这些技术一一进行分析,并形成文档分享给大家。本文介绍的是创建自启动任务计划实现开机自启动的方式,其它的实现方式可以搜索我写的相关系列的文档。
    实现原理我是使用Windows Shell编程实现创建任务计划,所以会涉及COM组件相关知识。
    现在,为方便大家的理解,我把整个程序的逻辑概括为 3 各部分,分别是:初始化操作、创建任务计划以及删除任务计划。现在,我们一一对每一个部分进行解析。
    初始化操作初始化操作的目的就是获取 ITaskService 对象以及 ITaskFolder 对象,我们创建任务计划,主要是对这两个指针进行操作。具体流程是:

    首先初始化COM接口环境,因为我们接下来会使用到COM组件
    然后,创建 ITaskService 对象,并连接到任务服务上
    接着,从 ITaskService 对象中获取 ITaskFolder 对象

    这样,初始化操作就完成了,接下来就是直接使用 ITaskService 对象以及 ITaskFolder 对象进行操作了。
    创建任务计划现在,解析下任务计划的具体创建过程:

    首先,从 ITaskService 对象中创建一个任务定义对象 ITaskDefinition,用来来创建任务
    接着,就是开始对任务定义对象 ITaskDefinition进行设置:

    设置注册信息,包括设置作者的信息
    设置主体信息,包括登陆类型、运行权限
    设置设置信息,包括设置在使用电池运行时是否停止、在使用电池是是否允许运行、是否允许手动运行、是否设置多个实例
    设置操作信息,包括启动程序,并设置运行程序的路径和参数
    设置触发器信息,包括用户登录时触发

    最后,使用 ITaskFolder 对象根据任务定义对象 ITaskDefinition的设置,注册任务计划

    这样,任务计划创建的操作就完成了,只要满足设置的触发条件,那么就会启动指定程序。
    删除任务计划 ITaskFolder 对象存储着已经注册成功的任务计划的信息,我们只需要将任务计划的名称传入其中,调用DeleteTask接口函数,就可以删除指定的任务计划了。
    编码实现创建任务计划的初始化CMyTaskSchedule::CMyTaskSchedule(void){ m_lpITS = NULL; m_lpRootFolder = NULL; // 初始化COM HRESULT hr = ::CoInitialize(NULL); if(FAILED(hr)) { ShowError("CoInitialize", hr); } // 创建一个任务服务(Task Service)实例 hr = ::CoCreateInstance(CLSID_TaskScheduler, NULL, CLSCTX_INPROC_SERVER, IID_ITaskService, (LPVOID *)(&m_lpITS)); if(FAILED(hr)) { ShowError("CoCreateInstance", hr); } // 连接到任务服务(Task Service) hr = m_lpITS->Connect(_variant_t(), _variant_t(), _variant_t(), _variant_t()); if(FAILED(hr)) { ShowError("ITaskService::Connect", hr); } // 获取Root Task Folder的指针,这个指针指向的是新注册的任务 hr = m_lpITS->GetFolder(_bstr_t("\\"), &m_lpRootFolder); if(FAILED(hr)) { ShowError("ITaskService::GetFolder", hr); }}
    创建任务计划BOOL CMyTaskSchedule::NewTask(char *lpszTaskName, char *lpszProgramPath, char *lpszParameters, char *lpszAuthor){ if(NULL == m_lpRootFolder) { return FALSE; } // 如果存在相同的计划任务,则删除 Delete(lpszTaskName); // 创建任务定义对象来创建任务 ITaskDefinition *pTaskDefinition = NULL; HRESULT hr = m_lpITS->NewTask(0, &pTaskDefinition); if(FAILED(hr)) { ShowError("ITaskService::NewTask", hr); return FALSE; } /* 设置注册信息 */ IRegistrationInfo *pRegInfo = NULL; CComVariant variantAuthor(NULL); variantAuthor = lpszAuthor; hr = pTaskDefinition->get_RegistrationInfo(&pRegInfo); if(FAILED(hr)) { ShowError("pTaskDefinition::get_RegistrationInfo", hr); return FALSE; } // 设置作者信息 hr = pRegInfo->put_Author(variantAuthor.bstrVal); pRegInfo->Release(); /* 设置登录类型和运行权限 */ IPrincipal *pPrincipal = NULL; hr = pTaskDefinition->get_Principal(&pPrincipal); if(FAILED(hr)) { ShowError("pTaskDefinition::get_Principal", hr); return FALSE; } // 设置登录类型 hr = pPrincipal->put_LogonType(TASK_LOGON_INTERACTIVE_TOKEN); // 设置运行权限 // 最高权限 hr = pPrincipal->put_RunLevel(TASK_RUNLEVEL_HIGHEST); pPrincipal->Release(); /* 设置其他信息 */ ITaskSettings *pSettting = NULL; hr = pTaskDefinition->get_Settings(&pSettting); if(FAILED(hr)) { ShowError("pTaskDefinition::get_Settings", hr); return FALSE; } // 设置其他信息 hr = pSettting->put_StopIfGoingOnBatteries(VARIANT_FALSE); hr = pSettting->put_DisallowStartIfOnBatteries(VARIANT_FALSE); hr = pSettting->put_AllowDemandStart(VARIANT_TRUE); hr = pSettting->put_StartWhenAvailable(VARIANT_FALSE); hr = pSettting->put_MultipleInstances(TASK_INSTANCES_PARALLEL); pSettting->Release(); /* 创建执行动作 */ IActionCollection *pActionCollect = NULL; hr = pTaskDefinition->get_Actions(&pActionCollect); if(FAILED(hr)) { ShowError("pTaskDefinition::get_Actions", hr); return FALSE; } IAction *pAction = NULL; // 创建执行操作 hr = pActionCollect->Create(TASK_ACTION_EXEC, &pAction); pActionCollect->Release(); /* 设置执行程序路径和参数 */ CComVariant variantProgramPath(NULL); CComVariant variantParameters(NULL); IExecAction *pExecAction = NULL; hr = pAction->QueryInterface(IID_IExecAction, (PVOID *)(&pExecAction)); if(FAILED(hr)) { pAction->Release(); ShowError("IAction::QueryInterface", hr); return FALSE; } pAction->Release(); // 设置程序路径和参数 variantProgramPath = lpszProgramPath; variantParameters = lpszParameters; pExecAction->put_Path(variantProgramPath.bstrVal); pExecAction->put_Arguments(variantParameters.bstrVal); pExecAction->Release(); /* 创建触发器,实现用户登陆自启动 */ ITriggerCollection *pTriggers = NULL; hr = pTaskDefinition->get_Triggers(&pTriggers); if (FAILED(hr)) { ShowError("pTaskDefinition::get_Triggers", hr); return FALSE; } // 创建触发器 ITrigger *pTrigger = NULL; hr = pTriggers->Create(TASK_TRIGGER_LOGON, &pTrigger); if (FAILED(hr)) { ShowError("ITriggerCollection::Create", hr); return FALSE; } /* 注册任务计划 */ IRegisteredTask *pRegisteredTask = NULL; CComVariant variantTaskName(NULL); variantTaskName = lpszTaskName; hr = m_lpRootFolder->RegisterTaskDefinition(variantTaskName.bstrVal, pTaskDefinition, TASK_CREATE_OR_UPDATE, _variant_t(), _variant_t(), TASK_LOGON_INTERACTIVE_TOKEN, _variant_t(""), &pRegisteredTask); if(FAILED(hr)) { pTaskDefinition->Release(); ShowError("ITaskFolder::RegisterTaskDefinition", hr); return FALSE; } pTaskDefinition->Release(); pRegisteredTask->Release(); return TRUE;}
    删除任务计划BOOL CMyTaskSchedule::Delete(char *lpszTaskName){ if(NULL == m_lpRootFolder) { return FALSE; } CComVariant variantTaskName(NULL); variantTaskName = lpszTaskName; HRESULT hr = m_lpRootFolder->DeleteTask(variantTaskName.bstrVal, 0); if(FAILED(hr)) { return FALSE; } return TRUE;}
    程序测试在 main 函数中调用上述封装好的函数,进行测试。main 函数为:
    int _tmain(int argc, _TCHAR* argv[]){ CMyTaskSchedule task; BOOL bRet = FALSE; // 创建 任务计划 bRet = task.NewTask("520", "C:\\Users\\DemonGan\\Desktop\\520.exe", "", ""); if (FALSE == bRet) { printf("Create Task Schedule Error!\n"); } // 暂停 printf("Create Task Schedule OK!\n"); system("pause"); // 卸载 任务计划 bRet = task.Delete("520"); if (FALSE == bRet) { printf("Delete Task Schedule Error!\n"); } printf("Delete Task Schedule OK!\n"); system("pause"); return 0;}
    测试结果:
    “以管理员身份运行程序”方式打开程序,提示任务计划创建成功。

    打开任务计划列表进行查看,发现“520”任务计划成功创建。



    然后,删除创建的任务计划,程序提示删除成功。

    查看任务计划列表,发现“520”任务计划已经成功删除。

    所以,测试成功。
    总结这个功能的实现涉及到COM组件的相关知识,所以初学者对此可能感到陌生,这是很正常的。其实,对于这方面的理解我也没有什么好的建议,只有多动手练几遍,加深程序逻辑和印象吧。
    注意的地方就是,创建任务计划,要求程序必须要有管机员权限才行。所以,测试的时候,不要忘记以管理员身份运行程序。
    参考参考自《Windows黑客编程技术详解》一书
    1 回答 2018-11-20 14:39:47
  • U盘量产之更改U盘容量大小

    背景由于某个项目,使我无意中接触到了U盘量产方面的操作。刚开始听到“U盘量产”的词语,还以为是要生产U盘的意思。后来了解了之后发现,原来意思也是特别相近了。“U盘量产”就是指U盘生产的最后一个步骤,使用工具对U盘的主控芯片刷写数据信息,也就是刷写如:生产厂商、主控芯片型号、U盘容量、U盘类型等等。
    在熟悉了U盘量产的操作之后,恍然发现,这个操作,有利也有弊啊。利是因为我们可以对一些坏的U盘重新量产一下,还能继续使用,可以修复一些U盘问题;弊是因为这个量产技术也可以被用来做坏事,比如:假容量U盘。也就是,坏人可以使用量产工具,将一个比较小容量的U盘,比如4G,量产为32G或是64G的U盘,然后进行出售。但,U盘实际上只能使用4G容量的大小。
    所以,本文就从坏人的角度,为大家演示更改U盘容量大小这方面的操作,给大家提个醒。注意:请不要使用该方面操作做不好的事,对于造成的后果,本人概不负责。这里,只从技术的角度,与大家交流分享。
    准备工作
    芯片精灵:ChipGenius v4.00.1024版本;
    量产工具:慧荣量产工具;
    U盘:U盘主控芯片是慧荣的8GU盘一个。

    量产步骤1. 插入U盘先插入U盘到计算机,可以看到U盘的容量显示为“7.52G”,而且360也显示为“7.5G”大小。


    2. 查看U盘的主控芯片型号然后,运行芯片精灵“ChipGenius”程序,会看到插入的U盘的检测信息。其中,我们只需要关心“Controller Part-Number”部分的信息“SM3257ENLT”。意思是说,该U盘的主控芯片型号为“SM3257ENLT”。

    3. 下载相应主控芯片型号的量产工具这里需要注意的是,并不是所有U盘的主控芯片都会有公开的量产工具。也就是说,如果U盘生产商不公开量产工具的话,也基本上就很难进行量产了。因为不同的芯片型号,量产工具不一定相同,量产工具并不是通用的。
    本文测试采用的是慧荣主控芯片的U盘,而慧荣主控芯片的量产工具是公开的,所以可以轻松获取到。
    下载“SM3257ENLT”型号对应的量产工具到本地上,并运行。
    4. 量产设置运行量产工具,点击右侧的“Scan USB(F5)”,扫描U盘。

    点击“Setting”,对量产进行设置。

    我们可以看到U盘量产的很多信息,例如U盘的类型、格式、生产商的信息等等。我们都可以进行更改,但是,本文的目的是更改U盘的容量大小。

    所以,点击“Capacity Setting”,跳转到容量设置页面,进行容量大小的设置。选中“Fix”固定大小选项,然后输入U盘的大小的信息,如输入:0-15000M,也就是大约15G左右。然后点击“OK”,保存设置。

    回到主界面后,点击“Start(Space Key)”开始按设置进行U盘量产。

    此时,会弹出提示框询问是否要擦除坏块,点击“确定”,即开始进行量产。

    等待一会儿之后,U盘变量产好了。

    这是,我们点击右侧的“Quit”,退出量产工具程序,并拔出U盘,然后再重新插上,查看U盘容量是否变化。


    这时,U盘容量增大了,说明U盘量产成功了。
    总结再次提醒,千万千万不要用来做坏事哦!千万千万不要用来做坏事哦!千万千万不要用来做坏事哦!
    即使上面显示U盘容量增加了,实际上也只有8G的容量,也只能使用8G的容量。如果使用超过8G的容量,U盘写入数据的时候,便会出现问题的。
    3 回答 2018-11-12 11:28:34
  • 使用WinDbg双机调试SYS无源码驱动程序

    背景有很多学习逆向的小伙伴,逆向一些用户层的程序很熟练了,但是由于没有接触过内核驱动开发,所以对于驱动程序的逆向无从下手。
    对于驱动程序的调试可以分为有源码调试和无源码调试。本文主要讨论无源码驱动程序的调试,也就是逆向驱动程序的方法和步骤。本文演示的是使用 VMware 虚拟机和 WinDbg 程序实现双击调试。
    实现过程VMware虚拟机设置
    打开相应 WMware 虚拟机上的 “Edit virtaul machine settings”。


    “Hardware”选项中 —> 点击“Add”,添加一个串口设备 Serial Port 。如果有打印机(Printer)存在,则先移除虚拟机的 打印机 硬件,然后再添加串口设备 Serial Port,因为打印机会占用串口 COM1。


    “Next”,在“Serial Port” 里选中 “Output to named pipe”。


    “next”,然后如下设置:


    “Finish”之后,回到如下“Virtual Machine Settings”页面,在“I/O Mode” 里选中“Yield CPU on poll”。


    点击“OK”之后,WMware 虚拟机设置就完成了。接下来,我们开机,进入虚拟机系统中,并对虚拟机系统进行设置,将其设置成调试模式。
    虚拟机里的操作系统设置
    如果操作系统不是 Win10,则开机进入桌面后,在运行窗口输入 msconfig —> 引导 —> 高级选项 —> 调试 —> 确定。


    如果操作系统是 Win10,则

    在设置 —> 安全和更新 —> 针对开发人员 —> 开发人员模式;
    管理员身份运行CMD,输入 bcdedit /set testsigning on 开启测试模式;
    在运行窗口输入 msconfig —> 引导 —> 高级选项 —> 调试 —> 确定;






    关机重启,这样虚拟机里的操作系统就设置完成了。接下来,就开始在真机系统上对 WinDbg 程序进行设置并下断点调试了。
    使用 WinDbg 程序开始双机调试无源码驱动程序
    我们在真机上以管理员身份运行 WinDbg,点击 File —> Kernel Debug —> COM,然后在 Port中输入:\\.\pipe\com_1,其它都勾选上,点击“确定”。


    通常 WinDbg 就会一直显示 等待建立连接(Waiting to reconnect…) 状态,如下面所示:


    这时,需要我们点击 Break(Ctrl+Break) 暂停调试。这样,虚拟机就会停下来,我们就可以在 WinDbg 中输入命令。



    我们就可以输入命令,使用 WinDbg 程序来调试虚拟机中的操作系统内核。先来介绍些常用的 WinDbg 命令:
    lm :表示列举虚拟机加载和卸载的内核模块起始地址和结束地址。bu、bp :下断点。u、uf :反汇编指定地址处的代码。dd : 查看指定地址处的数据。dt : 查看数据类型定义。
    其中,bu 命令用来设置一个延迟的、以后再求解的断点,对未加载模块中的代码设置断点;当指定模块被加载时,才真正设置这个断点;这对动态加载模块的入口函数或初始化代码处加断点特别有用。
    在模块没有被加载的时候,bp 断点会失败(因为函数地址不存在),而 bu 断点则可以成功。新版的 WinDBG 中 bp 失败后会自动被转成 bu。
    那么,在无源码的驱动程序 .sys 的入口点函数 DriverEntry 中下断点的指令为:
    bp 驱动模块名称+驱动PE结构入口点函数偏移// 如:bp DriverEnum+0x1828

    我们演示调试无源码驱动程序 DriverEnum.sys,虚拟机系统环境是 Win10 64位。然后,在 WinDbg 程序中输入指令:bp DriverEnum+0x1828。其中,bp表示下断点;DriverEnum 表示驱动程序 DriverEnum.sys 的驱动模块名称;0x1828 表示驱动程序 DriverEnum.sys 的入口点偏移地址,这个偏移地址可以由 PE 查看工具查看得出,如下图:

    输入完下断点指令后,我们在输入 g,让系统继续执行,直到执行到断点处,便会停下。

    我们,在虚拟机系统里,加载并启动我们的驱动程序 DriverEnum.sys,在启动之后,真机上的 WinDbg 成功在 DriverEnum.sys 的入口点 DriverEntry 处停下。这时,我们可以使用快捷键 F10 或者 F10 步过或者步入来单步调试,还可以继续使用 bp 下多个断点。

    总结步骤并不复杂,只是啰嗦而已。大家细心点跟着上述教程,认真操作就可以成功对无源码的驱动程序的入口点函数 DriverEntry 下断点,实现调试。
    参考参考自《Windows黑客编程技术详解》一书
    2 回答 2018-11-11 09:00:44
  • 在DllMain中测试调用LoadLibrary和CreateThread函数可正常使用

    背景某天,在网上无意中看到一篇博文,标题是说在DLL的入口点函数DllMain中不能调用 LoadLibrary 加载DLL,因为会造成死锁。看到这里我楞了一下,因为我之前写过很多DLL程序,在入口点函数DllMain中也加载过其它的DLL,从没有出现过什么问题。然后,我便仔细阅读了这篇博文,大概理解了它的意思。它应该想表达的是在DLL的DllMain函数中谨慎使用 LoadLibrary,以防发生死锁情况。
    虽然都懂了它的意思,我还是决定自己再亲自动手写一下代码看看。现在,我就把实现的过程和测试心得整理成文档,分享给大家。
    实现过程首先,我直接创建一个名为 Dll_LoadLibrary_CreateThread_Test 的DLL项目工程,在 DllMain 的 DLL_PROCESS_ATTACH 时候直接调用 CreateThread 函数创建一个多线程,同时,也调用 LoadLibrary 加载另一个 DLL 文件。
    BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ){ switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: { // 创建多线程 ::CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadProc, NULL, 0, NULL); // 加载DLL HMODULE hDll = ::LoadLibrary("testdll.dll"); if (NULL == hDll) { ::MessageBox(NULL, "load testdll.dll error.", "error", MB_OK); } break; } … …(省略) } return TRUE;}
    其中,ThreadProc 多线程函数,执行的操作就是每个 5000 毫秒就弹窗提示一次。
    UINT ThreadProc(LPVOID lpVoid){ while (TRUE) { Sleep(5000); ::MessageBox(NULL, "this si from createthread.", "createthread", MB_OK); } return 0;}
    在 testdll.dll 这个DLL的入口点函数DllMain中,直接弹窗提示。
    BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ){ switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: { // 弹窗 ::MessageBox(NULL, "this is from testdll.dll DllMain function.", "testdll.dll", MB_OK); break; } … …(省略) } return TRUE;}
    然后,我们直接加载 Dll_LoadLibrary_CreateThread_Test.dll 文件,成功弹出下图所示的两个提示框,说明 CreateThread 函数成功创建多线程,LoadLibrary 成功加载DLL。


    总结经过上面的例子测试,说明在 DllMain 函数中,是可以使用 CreateThread 以及 LoadLibrary 函数的。只要我们避免相互在 DllMain 中相互调用,出现死锁,如下面这种相互调用的情况:

    DllB 在 DllMain 里调用 LoadLibrary 加载 DllA
    DllA 在 DllMain 里调用 LoadLibrary 加载 DllB

    这样,就会无限循环加载下去,形成死锁。
    所以,之所以说 在DllMain里不能调用LoadLibrary函数,其实,并不是说只要是在 DllMain 中,都不能调用 LoadLibrary 函数,而是说,如果这两个 Dll 如果在 DllMain 中相互调用的情况下,是会出错的,所以,为了避免这种相互调用死锁的情况发生,就不提倡在 DllMain 中调用 LoadLibrary!实际上,只要避免这种相互调用的情况,LoadLibrary 还是可以使用的!
    参考参考自《Windows黑客编程技术详解》一书
    1 回答 2018-11-07 11:31:42
  • 为程序创建任务栏右下角托盘图标

    背景我们用过很多应用程序,无论是功能复杂的大程序还是功能单一的小程序,大都会在计算机右下角显示一个图盘图标,然后我们可以直接把窗口关掉,通过点击托盘图标来控制程序的执行操作或显示窗口。使得程序使用起来比较便捷,不用显示程序主窗口就能完成一些操作,增强了程序的用户体验。
    本文要讲解的正是为程序添加这样的一个右下角托盘,并实现显示程序主窗口、退出程序等功能。本文分别在MFC程序和Windows应用程序中进行演示,其实,原理步骤都是相同的,只是为了方便大家理解,所以MFC程序和Windows程序都各写一个来演示。
    现在,我就把程序实现的过程整理成文档,分享给大家。
    函数介绍Shell_NotifyIcon 函数
    主要用于向任务栏的状态栏发送一个消息。
    函数声明
    BOOL Shell_NotifyIcon( DWORD dwMessage, PNOTIFYICONDATA lpdata);
    参数

    dwMessage为输入参数,传递发送的消息,表明要执行的操作。可选的值如下:NIM_ADD向托盘区域添加一个图标。此时第二个参数lpdata指向的NOTIFYICONDATA结构体中的hWnd和uID成员用来标示这个图标,以便以后再次使用Shell_NotifyIcon对此图标操作。NIM_DELETE删除托盘区域的一个图标。此时第二个参数lpdata指向的NOTIFYICONDATA结构体中的hWnd和uID成员用来标示需要被删除的这个图标。NIM_MODIFY修改托盘区域的一个图标。此时第二个参数lpdata指向的NOTIFYICONDATA结构体中的hWnd和uID成员用来标示需要被修改的这个图标。NIM_SETFOCUSVersion 5.0. 设置焦点。比如当用户操作托盘图标弹出菜单,而有按下ESC键将菜单消除后,程序应该使用此消息来将焦点设置到托盘图标上。NIM_SETVERSIONVersion 5.0. 设置任务栏按照第二个参数lpdata指向的NOTIFYICONDATA结构体中的uVersion成员指定的版本号来工作。此消息可以允许用户设置是否使用基于Windows2000的version 5.0的风格。uVersion的缺省值为0,默认指明了使用原始Windows 95图标消息风格。具体这两者的区别请参考msdn中的Shell_NotifyIcon函数说明的Remarks。lpdata为输入参数,是指向NOTIFYICONDATA结构体的指针,结构体内容用来配合第一个参数wMessage进行图标操作。
    返回值

    如果图标操作成功返回TRUE,否则返回FALSE。

    NOTIFYICONDATA 结构体
    结构体声明
    typedef struct _NOTIFYICONDATA { DWORD cbSize; HWND hWnd; UINT uID; UINT uFlags; UINT uCallbackMessage; HICON hIcon; TCHAR szTip[64]; DWORD dwState; DWORD dwStateMask; TCHAR szInfo[256]; union { UINT uTimeout; UINT uVersion; }; TCHAR szInfoTitle[64]; DWORD dwInfoFlags; GUID guidItem;} NOTIFYICONDATA, *PNOTIFYICONDATA;
    成员

    cbSize:结构体的大小,以字节为单位。hWnd:窗口的句柄。窗口用来接收与托盘图标相关的消息。Shell_NotifyIcon函数调用时,hWnd和uID成员用来标示具体要操作的图标。uID:应用程序定义的任务栏图标的标识符。Shell_NotifyIcon函数调用时,hWnd和uID成员用来标示具体要操作的图标。通过将多次调用,你可以使用不同的uID将多个图标关联到一个窗口。hWnd。uFlags:此成员表明具体哪些其他成员为合法数据(即哪些成员起作用)。此成员可以为以下值的组合:NIF_ICON:hIcon成员起作用。NIF_MESSAGE:uCallbackMessage成员起作用。NIF_TIP:szTip成员起作用。NIF_STATE:dwState和dwStateMask成员起作用。NIF_INFO:使用气球提示代替普通的工具提示框。szInfo, uTimeout, szInfoTitle和dwInfoFlags成员起作用。NIF_GUID:保留。uCallbackMessage:应用程序定义的消息标示。当托盘图标区域发生鼠标事件或者使用键盘选择或激活图标时,系统将使用此标示向由hWnd成员标示的窗口发送消息。消息响应函数的wParam参数标示了消息事件发生的任务栏图标,lParam参数根据事件的不同,包含了鼠标或键盘的具体消息,例如当鼠标指针移过托盘图标时,lParam将为WM_MOUSEMOVE。hIcon:增加、修改或删除的图标的句柄。注意,windows不同版本对于图标有不同要求。Windows XP可支持32位。szTip:指向一个以\0结束的字符串的指针。字符串的内容为标准工具提示的信息。包含最后的\0字符,szTip最多含有64个字符。对于Version 5.0 和以后版本,szTip最多含有128个字符(包含最后的\0字符)。dwState:Version 5.0,图标的状态,有两个可选值,如下:NIS_HIDDEN:图标隐藏NIS_SHAREDICON:图标共享dwStateMask:Version 5.0. 指明dwState成员的那些位可以被设置或者访问。比如设置此成员为NIS_HIDDEN,将导致只有hidden状态可以被获取。szInfo:Version 5.0. 指向一个以\0结束的字符串的指针。字符串的内容为气球提示内容。最多含有255个字符。如果要移除已经存在的气球提示信息,设置uFlags成员为NIF_INFO,同时将szInfo设为空。uTimeout:和uVersion成员为联合体。uTimeout表示气球提示超时的时间,单位为毫秒,此时间后气球提示将消失。系统默认气球提示的超时时间最小值为10秒,最大值为30秒。如果设置的uTimeout的值小于10将设置最小值,如果大于30将设置最大值。将超时时间分为最大最小两种,是因为解决不同图标的气球提示同时弹出的问题,详细内容请参考MSDN中NOTIFYICONDATA结构体说明的remarks。uVersion:Version 5.0. 和uTimeout成员为联合体。用来设置使用Windows 95 还是 Windows 2000风格的图标消息接口。szInfoTitle:Version 5.0. 指向一个以\0结束的字符串的指针。字符串的内容为气球提示的标题。此标题出现在气球提示框的上部,最多含有63个字符。dwInfoFlags:Version 5.0. 设置此成员用来给气球提示框增加一个图标。增加的图标出现在气球提示标题的左侧,注意如果szInfoTitle成员设为空字符串,则图标也不会显示。guidItem:Version 6.0. 保留。

    实现过程无论是对MFC程序还是Windows应用程序,实现的步骤和原理都是一样的。
    1. 设置 NOTIFYICONDATA首先,我们需要对托盘图标结构体 NOTIFYICONDATA 进行设置,设置的内容,就是我们想要托盘图标显示的内容。我们主要是对 NOTIFYICONDATA 的cbSize、hWnd、uID、uFlags、uCallbackMessage、hIcon以及szTip成员进行设置。
    其中,cbSize表示 NOTIFYICONDATA 结构体的大小;hWnd表示和托盘图标相关联的程序窗体的句柄,这个值需要被指定,因为窗口用来接收与托盘图标相关的消息;uID表示应用程序定义的任务栏图标的标识符,可以使用不同的uID将多个图标关联到一个窗口;uFlags表示哪些结构体成员起作用,NIF_ICON则hIcon成员起作用,NIF_MESSAGE则uCallbackMessage成员起作用,NIF_TIP则szTip成员起作用,所以下面我们还需要对hIcon、uCallbackMessage以及szTip成员赋值;hIcon表示托盘显示的图标句柄;uCallbackMessage表示托盘的消息类型;szTip表示托盘的提示信息。
    NOTIFYICONDATA notifyIconData = {0};::RtlZeroMemory(&notifyIconData, sizeof(notifyIconData));notifyIconData.cbSize = sizeof(NOTIFYICONDATA);notifyIconData.hWnd = hWnd;notifyIconData.uID = IDI_ICON1;notifyIconData.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;notifyIconData.uCallbackMessage = WM_MYTASK;notifyIconData.hIcon = ::LoadIcon(::GetModuleHandle(NULL), (LPCSTR)IDI_ICON1);::lstrcpy(notifyIconData.szTip, "This Is A Notify Tip.");
    2. 增加托盘显示然后,我们开始调用 Shell_NotifyIcon 函数按照上面 NOTIFYICONDATA 设置新增托盘。其中,NIM_ADD表示向托盘区域添加一个图标。notifyIconData就是上面设置的 NOTIFYICONDATA 结构体变量。
    // 增加托盘::Shell_NotifyIcon(NIM_ADD, &notifyIconData);
    3. 托盘消息处理函数经过上面两步操作,我们就可以成功为程序在窗口右下角增加一个托盘图标显示。但是,我们还需要让托盘图标对一些操作做出响应,而不是只是显示而已。例如,我们鼠标右击托盘图标的时候,显示菜单栏。
    根据上面对 NOTIFYICONDATA 结构体成员 uCallbackMessage 介绍中知道 ,当托盘图标区域发生鼠标事件或者使用键盘选择或激活图标时,系统将使用此标示向由hWnd成员标示的窗口发送消息。消息响应函数的wParam参数标示了消息事件发生的任务栏图标,lParam参数根据事件的不同,包含了鼠标或键盘的具体消息,例如当鼠标指针移过托盘图标时,lParam将为WM_MOUSEMOVE。所以,我们要响应鼠标右键的操作,需要判断 lParam 是否为 WM_RBUTTONUP ,若是,弹出菜单,否则忽略操作。
    switch (lParam){// 鼠标右键弹起时case WM_RBUTTONUP: { // 弹出菜单 PopupMyMenu(); break;}default: break;}
    就这样,菜单弹出来后,我们直接响应菜单选项的消息响应函数,在对应的选项里执行相应的操作就好。
    为程序添加托盘显示的原理就是上面 3 个步骤,剩下来的就是编码实现了。
    编码实现Windows应用程序实现首先我们把对NOTIFYICONDATA结构体的设置以及添加托盘图标的操作代码都放在程序初始化WM_INITDIALOG操作里。
    然后,我们就开始在窗口消息过程函数中添加托盘消息类型WM_MYTASK,并对实现托盘消息处理响应函数。这样,在右击托盘的时候,就可以显示菜单栏了。
    窗口消息过程函数BOOL CALLBACK ProgMainDlg(HWND hWnd, UINT uiMsg, WPARAM wParam, LPARAM lParam){ if (WM_INITDIALOG == uiMsg) { // 设置托盘显示 SetNotifyIcon(hWnd); } else if (WM_CLOSE == uiMsg) { // 隐藏窗口 ::ShowWindow(hWnd, SW_HIDE); } else if (WM_MYTASK == uiMsg) { // 处理操作托盘的消息 OnTaskMsg(hWnd, wParam, lParam); } else if (WM_COMMAND == uiMsg) { if (ID_EXIT == wParam) { // 托盘菜单 退出 ::EndDialog(hWnd, NULL); } else if (ID_SHOW == wParam) { // 托盘菜单 显示主窗口 ::ShowWindow(hWnd, SW_SHOW); } } return FALSE;}
    设置托盘显示// 设置托盘显示void SetNotifyIcon(HWND hWnd){ NOTIFYICONDATA notifyIconData = {0}; ::RtlZeroMemory(&notifyIconData, sizeof(notifyIconData)); notifyIconData.cbSize = sizeof(NOTIFYICONDATA); notifyIconData.hWnd = hWnd; notifyIconData.uID = IDI_ICON1; notifyIconData.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP; notifyIconData.uCallbackMessage = WM_MYTASK; notifyIconData.hIcon = ::LoadIcon(::GetModuleHandle(NULL), (LPCSTR)IDI_ICON1); ::lstrcpy(notifyIconData.szTip, "This Is A Notify Tip."); // 增加托盘 ::Shell_NotifyIcon(NIM_ADD, &notifyIconData);}
    托盘消息处理函数// 处理操作托盘的消息LRESULT OnTaskMsg(HWND hWnd, WPARAM wParam, LPARAM lParam){ switch (lParam) { // 鼠标右键弹起时,弹出菜单 case WM_RBUTTONUP: { // 弹出菜单 PopupMyMenu(hWnd); break; } default: break; } return 0;}
    弹出菜单栏// 弹出菜单栏void PopupMyMenu(HWND hWnd){ POINT p; ::GetCursorPos(&p); HMENU hMenu = ::LoadMenu(::GetModuleHandle(NULL), (LPCSTR)IDR_MENU1); HMENU hSubMenu = ::GetSubMenu(hMenu, 0); ::TrackPopupMenu(hSubMenu, TPM_LEFTALIGN | TPM_RIGHTBUTTON, p.x, p.y, 0, hWnd, NULL); ::DestroyMenu(hSubMenu);}
    MFC程序实现和上面的Windows应用程序一样,首先我们把对NOTIFYICONDATA结构体的设置以及添加托盘图标的操作代码都放在程序初始化,对于MFC来说,初始化消息响应函数是主对话框类的OnInitDialog函数。我们把托盘设置这部分代码,放在函数OnInitDialog中即可。
    然后,我们开始对为自定义消息类型 WM_MYTASK 创建自定义消息响应函数 OnTaskMsg。创建自定义消息响应函数操作如下:

    声明消息类型 WM_MYTASK。
    #define WM_MYTASK (WM_USER + 100)
    声明消息响应函数,函数名称可以任意,但是返回值类型和参数类型是固定的。
    // 托盘消息处理函数 LRESULT OnTaskMsg(WPARAM wParam, LPARAM lParam);
    在主对话框的窗口消息响应列表中,为上述自定义的消息类型和消息响应函数进行关联。
    BEGIN_MESSAGE_MAP(CNotifyIcon_MFC_TestDlg, CDialogEx) … …(省略) ON_MESSAGE(WM_MYTASK, OnTaskMsg) END_MESSAGE_MAP()

    这样,我们就可以直接定义响应函数OnTaskMsg就可以了。
    接下来的操作和代码,基本上和Windows应用程序中的代码是一样的,所以,就给出代码了。若有什么问题,可以直接参考本文对应的程序代码即可。
    程序测试我们直接运行程序,便可以看到窗口右下角有托盘图标显示,然后我们鼠标右击图标,便成功弹出菜单栏,点击菜单栏选项,成功实现相应的操作。

    总结这个程序原理上不是很复杂,但是实现上面,由于Windows应用程序和MFC程序框架上不同,所以,要注意它们的区别,特别是MFC的自定义消息响应函数。
    参考参考自《Windows黑客编程技术详解》一书
    2 回答 2018-11-07 11:23:55
  • 编程实现MFC程序窗口一运行立马隐藏

    背景MFC程序在启动显示窗口之前,就已经做了很多关于界面初始化的操作。如果对WIN32 API函数较为熟悉的同学就会认为,让窗口隐藏,直接调用 ShowWindow 函数,将窗口设置为隐藏就可以了。是的,ShowWindow 函数可以隐藏窗口没错。但是,这个函数要成功实现的窗口隐藏有一个前提条件,就是窗口界面初始化已经完成,而且窗口界面你已经显示完全了。这时,再调用 ShowWindow 函数去隐藏窗口,这是可以的。
    但是,本文要实现的是,窗口程序一运行就已经开始隐藏的功能,而不是显示后隐藏。也就是说,在窗口界面初始化阶段,窗口界面还没有显示的时候,就已经开始隐藏界面了。这样的隐藏,使得程序运行无声无息。
    现在,我们把这个程序的实现过程整理成文档,分享给大家。
    函数介绍MoveWindow 函数
    改变指定窗口的位置和大小。对子窗口来说,位置和大小取决于父窗口客户区的左上角;对于Owned窗口,位置和大小取决于屏幕左上角。
    函数声明
    BOOL MoveWindow( HWND hWnd, int X, int Y, int nWidth, int nHeight, BOOL bRepaint );
    参数

    hWnd [in]窗口的句柄。X [in]窗口左侧的新位置。Y [in]窗口顶部的新位置。nWidth [in]窗口的新宽度。nHeight [in]窗口的新高度。bRepaint [in]指示窗口是否要重画。 如果此参数为TRUE,窗口将收到一条消息。 如果参数为FALSE,则不会发生任何重画。 这适用于客户端区域,非客户区域(包括标题栏和滚动条),父窗口的任何部分由于移动子窗口而被覆盖。
    返回值

    如果函数成功,则返回值不为零。如果函数失败,返回值为零。 要获取扩展错误信息,请调用GetLastError。

    GetWindowLong 函数
    获取有关指定窗口的信息。 函数也获得在额外窗口内存中指定偏移位地址的32位度整型值。
    函数声明
    LONG GetWindowLong( HWND hWnd, int nlndex );
    参数

    hWnd目标窗口的句柄。它可以是窗口句柄及间接给出的窗口所属的窗口类。
    nlndex需要获得的相关信息的类型。要获得任意其他值,指定下列值之一:




    VALUE
    MEANING




    GWL_EXSTYLE
    获得扩展窗口风格。


    GWL_HINSTANCE
    获得应用实例的句柄


    GWL_HWNDPARENT
    如果父窗口存在,获得父窗口句柄


    GWL_ID
    获得窗口标识


    GWL_STYLE
    获得窗口风格


    GWL_USERDATA
    获得与窗口有关的32位值。每一个窗口均有一个由创建该窗口的应用程序使用的32位值


    GWL_WNDPROC
    获得窗口过程的地址,或代表窗口过程的地址的句柄。必须使用CallWindowProc函数调用窗口过程



    hWnd参数为对话框句柄时,还可用下列值:



    VALUE
    MEANING




    DWL_DLGPROC
    获得对话框过程的地址,或一个代表对话框过程的地址的句柄。必须使用函数CallWindowProc来调用对话框过程


    DWL_MSGRESULT
    获得在对话框过程中一个消息处理的返回值


    DWL_USER
    获得应用程序私有的额外信息,例如一个句柄或指针



    返回值

    如果函数成功,返回值是所需的32位值;如果函数失败,返回值是0。若想获得更多错误信息请调用 GetLastError函数。

    SetWindowLong 函数
    用来改变指定窗口的属性.函数也将指定的一个32位值设置在窗口的额外存储空间的指定偏移位置。
    函数声明
    LONG SetWindowLong( HWND hWnd, // handle to window int nlndex, // offset of value to set LONG dwNewLong // new value);
    参数

    hWnd窗口句柄及间接给出的窗口所属的类。nlndex设置相关信息的类型。要获得任意其他值,指定下列值之一(和上面 GetWindowLong 函数的 nIndex 值相同)。dwNewLong指定的替换值。
    返回值

    如果函数成功,返回值是指定的32位整数的原来的值。如果函数失败,返回值为0。若想获得更多错误信息,请调用GetLastError函数。

    实现原理窗口移动到显示屏幕之外要设置程序窗口显示的位置和大小,我们可以直接调用 MoveWindow 函数,设置移动后窗口显示的位置坐标、窗口大小。这时,我们只需要将窗口大小都设为 0 ,位置坐标都设为-1000(屏幕位置坐标以屏幕左上角为起点),这样窗口就在屏幕之外显示了。
    隐藏任务栏程序图标其中,我们先来了解 GWL_EXSTYLE 窗口拓展属性中的两个值的含义,这是要理解隐藏任务栏比较关键的一步:
    一是 WS_EX_APPWINDOW,表示当窗口可见时,将一个顶层窗口放置到任务条上;
    二是 WS_EX_TOOLWINDOW,表示创建工具窗口,即窗口是一个游动的工具条。工具窗口的标题条比一般窗口的标题条短,并且窗口标题以小字体显示。工具窗口不在任务栏里显示,当用户按下alt+Tab键时工具窗口不在对话框里显示。如果工具窗口有一个系统菜单,它的图标也不会显示在标题栏里,但是,可以通过点击鼠标右键或Alt+Space来显示菜单。
    由上面 WS_EX_APPWINDOW 和 WS_EX_TOOLWINDOW 可知,我们要想实现任务栏程序的隐藏。那么,就要把 WS_EX_APPWINDOW 值去掉,然后再增加值 WS_EX_TOOLWINDOW。所以:

    首先调用 GetWindowLong 函数获取窗口 GWL_EXSTYLE 属性的值 dwOldStyle;
    然后,去掉 WS_EX_APPWINDOW 值,即
    dwOldStyle & (~WS_EX_APPWINDOW)
    接着,增加 WS_EX_APPWINDOW 值,即
    dwNewStyle = dwNewStyle | WS_EX_TOOLWINDOW;
    最后,调用 SetWindowLong 将新的 GWL_EXSTYLE 属性的值重新设置,这样就完成了。

    编码实现// 隐藏窗口void HideWindow(HWND hWnd){ // 把窗口移动到屏幕显示之外, 窗口大小都设为 0 ::MoveWindow(hWnd, -1000, -1000, 0, 0, FALSE); // 先获取原来窗口的拓展风格 DWORD dwOldStyle = ::GetWindowLong(hWnd, GWL_EXSTYLE); // 设置新属性, 隐藏任务栏程序图标 DWORD dwNewStyle = dwOldStyle & (~WS_EX_APPWINDOW); dwNewStyle = dwNewStyle | WS_EX_TOOLWINDOW; ::SetWindowLong(hWnd, GWL_EXSTYLE, dwNewStyle);}
    总结我们隐藏窗口的思路就两点:一是把窗口的显示位置坐标移到屏幕之外,这样窗口显示的时候,在屏幕之内是看不到的;二是把任务栏的程序图标隐藏,因为第一步操作会因为任务栏下的图标露出马脚,所以,必须隐藏任务栏中程序的图标。这样,就可以实现一运行程序,窗口就隐藏了。
    大家要注意,虽然窗口隐藏了,我们打开任务管理器还是会看到我们程序的进程的。所以,关于进程隐藏,就是另一个不同知识点了。
    参考参考自《Windows黑客编程技术详解》一书
    2 回答 2018-11-07 11:20:19
  • 基于MuPDF库实现PDF文件转换成PNG格式图片

    背景之所以会接触MuPDF是因为,有位群友在Q群里提问,如何将PDF保存为.PNG图片格式。我一看到这个问题,就蒙了,因为我没有接触过类似的项目或程序。但是,作为一群之主的我,还是要给初学者一个答复的,所以便去网上搜索了相关信息,才了解到有MuPDF开源库的存在。
    MuPDF是一种轻量级的PDF,XPS和电子书阅读器。由各种平台的软件库,命令行工具和查看器组成。支持许多文档格式,如PDF,XPS,OpenXPS,CBZ,EPUB和FictionBook 2。
    后来,自己就根据网上搜索到的一些资料,实现了基于MuPDF库将PDF指定页转换成PNG格式图片的小程序。现在,我就把程序的实现思路和过程写成文档,分享给大家。
    实现思路对于MuPDF库的源码下载以及编译过程,可以参考本站的《使用VS2013编译MuPDF库》这篇文章。
    其实,在MuPDF库中就提供了这一个功能:将PDF指定页转换成PNG格式图片,所以,我们直接编译MuPDF提供的代码就可以了。
    示例程序代码位于MuPDF库源码的“docs”目录下的“example.c”文件,我们只需使用VS2013创建一个空项目,然后把“example.c”文件导入项目中,接着将“include”目录中的头文件以及编译出来的libmupdf.lib、libmupdf-js-none.lib、libthirdparty.lib导入文件中即可。
    其中,“example.c”中主要的函数就是 render 函数,我们主要是把参数传进render函数,就可以把pdf转换成png图片了。
    对于“example.c”的代码可以不用修改,直接编译运行即可。但是,为了方便演示,我们还是对“example.c”中的 main 函数进行修改。
    编码实现以下是“example.c”文件中 render 函数的源码:
    void render(char *filename, int pagenumber, int zoom, int rotation){ // Create a context to hold the exception stack and various caches. fz_context *ctx = fz_new_context(NULL, NULL, FZ_STORE_UNLIMITED); // Open the PDF, XPS or CBZ document. fz_document *doc = fz_open_document(ctx, filename); // Retrieve the number of pages (not used in this example). int pagecount = fz_count_pages(doc); // Load the page we want. Page numbering starts from zero. fz_page *page = fz_load_page(doc, pagenumber - 1); // Calculate a transform to use when rendering. This transform // contains the scale and rotation. Convert zoom percentage to a // scaling factor. Without scaling the resolution is 72 dpi. fz_matrix transform; fz_rotate(&transform, rotation); fz_pre_scale(&transform, zoom / 100.0f, zoom / 100.0f); // Take the page bounds and transform them by the same matrix that // we will use to render the page. fz_rect bounds; fz_bound_page(doc, page, &bounds); fz_transform_rect(&bounds, &transform); // Create a blank pixmap to hold the result of rendering. The // pixmap bounds used here are the same as the transformed page // bounds, so it will contain the entire page. The page coordinate // space has the origin at the top left corner and the x axis // extends to the right and the y axis extends down. fz_irect bbox; fz_round_rect(&bbox, &bounds); fz_pixmap *pix = fz_new_pixmap_with_bbox(ctx, fz_device_rgb(ctx), &bbox); fz_clear_pixmap_with_value(ctx, pix, 0xff); // A page consists of a series of objects (text, line art, images, // gradients). These objects are passed to a device when the // interpreter runs the page. There are several devices, used for // different purposes: // // draw device -- renders objects to a target pixmap. // // text device -- extracts the text in reading order with styling // information. This text can be used to provide text search. // // list device -- records the graphic objects in a list that can // be played back through another device. This is useful if you // need to run the same page through multiple devices, without // the overhead of parsing the page each time. // Create a draw device with the pixmap as its target. // Run the page with the transform. fz_device *dev = fz_new_draw_device(ctx, pix); fz_run_page(doc, page, dev, &transform, NULL); fz_free_device(dev); // Save the pixmap to a file. fz_write_png(ctx, pix, "out.png", 0); // Clean up. fz_drop_pixmap(ctx, pix); fz_free_page(doc, page); fz_close_document(doc); fz_free_context(ctx);}
    程序测试我们修改 main 函数直接调用上述函数接口, main 函数为:
    int main(int argc, char **argv){ // 文件路径 char filename[MAX_PATH] = "C:\\Users\\DemonGan\\Desktop\\test.pdf"; // 转换的页码数 int pagenumber = 1; // 缩放比例 int zoom = 100; // 旋转角度 int rotation = 0; // 处理 render(filename, pagenumber, zoom, rotation); system("pause"); return 0;}
    直接运行程序,目录下有 out.png 图片生成,打开图片查看内容,发现是 test.pdf 的第一页内容,所以转换成功。
    总结这个程序的实现,自己可以不用写代码就可以完成。因为MuPDF已经把接口都封装好了,而且也有示例程序可以直接调用。如果想要把界面做得更有好些,可以把程序写成界面程序,然后直接调用现在的这个 render 函数接口,进行转换即可。
    3 回答 2018-11-06 22:31:04
显示 60 到 75 ,共 15 条
eject