WIN32中添加上下文菜单

我们经常看到在界面上点击右键出现一个菜单,这个菜单就是上下文菜单,有些人也把它叫做“右键菜单”。

在win32中我们创建菜单只能创建顶部菜单,那么这种上下文菜单是如何实现的呢,其实就是拿这个顶部菜单的一部分,也就是这个顶部菜单的子菜单。

单我们点击鼠标右键的时候窗口会有产生两个消息分别是“WM_RBUTTONUP” 和 “WM_CONTEXTMENU”。在这里我们用WM_CONTEXTMENU,因为WM_CONTEXTMENU消息附带了以屏幕为准的鼠标的x,y坐标。

WM_CONTEXTMENU中定义一个处理函数OnContentMenu,接收两个参数(HWND hwnd, LPARAM lParam)

VOID OnContentMenu(HWND hwnd, LPARAM lParam) {
	HMENU MainhMenu = LoadMenu(g_hInstance, (char*)IDR_MENU1); //加载顶部菜单
	HMENU hMenu = GetSubMenu(MainhMenu, 0);//获取顶部菜单的子菜单,0是索引。
        //TrackPopupMenu是创建一个上下文菜单。可以参考msdn文档。
	TrackPopupMenu(hMenu, TPM_LEFTALIGN|TPM_TOPALIGN, LOWORD(lParam), HIWORD(lParam),0,hwnd,NULL);
}

这样就可以在鼠标点击的时候弹出菜单了。点击菜单项之后去 WM_COMMAND 消息处理。

WIN32挂载顶部菜单的三种方式

第一种:在注册窗口类

给lpszMenuName属性赋值: winClass.lpszMenuName = (char*)IDR_MENU1

第二种:在创建窗口的时候赋值参数

在执行CreateWindow的时候,传递菜单句柄。

HMENU hMenu = LoadMenu(hInstance, (char*)IDR_MENU1);
HWND hWnd = CreateWindow(CLASS_NAME, "JishugeApp", WS_OVERLAPPEDWINDOW, 100, 100, 800, 500, NULL, hMenu, hInstance, NULL);

第三种:在WM_CREATE消息挂载菜单

定义一个全局进程句柄:HINSTANCE g_hInstance = NULL;

在入口函数给全局句柄赋值 : g_hInstance = hInstance;

在WM_CREATE消息的时候调用自定义函数OnCreate

VOID OnCreate(HWND hwnd) {
	HMENU hMenu = LoadMenu(g_hInstance, (char*)IDR_MENU1);
	SetMenu(hwnd, hMenu);
}

win32窗口常见的消息

WM_CREATE

  • 触发时机:在窗口创建成功但是还没有被显示时
  • 附带信息
    • wParam:0;
    • lParam:为CREATESTRUCT类似的指针,通过这个指针可以获取CreateWindow的全部参数信息
  • 使用场景:一般用于初始化窗口的参数、资源、创建子窗口等等。

WM_DESTROY

  • 触发时机:窗口被销毁的时候。
  • 附带信息
    • wParam:0;
    • lParam:0;
  • 使用场景:一般用于窗口销毁前的处理工作,比如资源、内存等等。

WM_SYSCOMMAND

  • 触发时机:点击窗口的最大化、最小化、关闭等。
  • 附带信息
    • wParam:具体点击的位置,例如关闭SC_CLOSE等;
    • lParam:鼠标光标的位置。
      • LOWORD(lParam); x 坐标
      • HIWORD(lParam); y 坐标
  • 使用场景:一般用于窗口关闭的时候,提示用户处理

WM_QUIT

  • 触发时机:程序员主动触发
  • 附带信息
    • wParam:PostQuitMessage 函数传递的参数;
    • lParam:0;
  • 使用场景:一般用于结束消息循环,当GetMessage收到该消息后,会返回FALSE,来结束while循环,退出循环消息。

WM_SIZE

  • 触发时机:在窗口的大小发生变化的时候
  • 附带信息
    • wParam:窗口大小变化的原因(一般不重要);
    • lParam:窗口变化后的大小;
      • LOWORD(lParam):变化后的宽度
      • HIWORD(lParam):变化后的高度
  • 使用场景:一般用于窗口大小变化后,调整窗口各个部分的布局。

在Win32窗口开发中通过控制台输出方式

一、定义一个全局变量,HANDLE类型

HANDLE g_outPut = 0;

二、启动console输出

在入口函数执行函数 AllocConsole();

三、在入口函数内获取标志输出句柄并赋值给全局变量

g_outPut = GetStdHandle(STD_OUTPUT_HANDLE);

四、输出内容到控制台

WriteConsole(g_outPut , "哈哈",strlen("哈哈"),NULL,NULL);

关于Linux编程中的execl参数简单说明。

    int execl(const char *pathname, const char *arg, ... /* (char  *) NULL */);
    int execlp(const char *file, const char *arg, ... /* (char  *) NULL */);

以上是从man手册中复制下来的。以下我们进行分析。

execl

比如我们使用execl执行ls进程,那么可以写成这样:

execlp("/bin/ls","ls","-al","/temp",NULL);

第一个参数就是ls命令的绝对路径,

第二个参数就是进程的名称,这个名字可以填写也可以不填写,甚至可以随便填写,你填写什么,到时候在ps中就可以显示这个进程的名称,比如你填写成LinuxC123,那么在ps中可以查看到如下

USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
jishuge     5794  0.0  0.0   2772  1280 pts/0    S    10:32   0:00 LinuxC123

剩余的参数就是命令的选项。

最后一个参数一定是NULL。文档规定的:

The const char *arg and subsequent ellipses can be thought of as arg0, arg1, …, argn. To‐
gether they describe a list of one or more pointers to null-terminated strings that represent
the argument list available to the executed program. The first argument, by convention, should
point to the filename associated with the file being executed. The list of arguments must be
terminated by a null pointer, and, since these are variadic functions, this pointer must be
cast (char *) NULL.

在SpringBoot中响应出xml格式数据。

我们在用SpringBoot开发的时候,经常响应的数据一般都是json格式或者字符串。

当时偶尔我们也要响应xml格式,比如在更新chrome 插件的时候,如下xml格式:

<?xml version='1.0' encoding='UTF-8'?>
<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>
  <app appid='aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'>
    <updatecheck codebase='https://myhost.com/mytestextension/mte_v2.crx' version='2.0' />
  </app>
</gupdate>

那么如何处理呢?手动拼接、替换?其实我们不用这么麻烦,可以用到一个开源库,一下是它的maven

     <dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-xml</artifactId>
        </dependency>

然后创建三个类:

public class UpdateCheck {

    private String codebase;
    private String version;

    @JsonProperty("codebase")
    public String getCodebase() {
        return codebase;
    }

    public void setCodebase(String codebase) {
        this.codebase = codebase;
    }

    @JsonProperty("version")
    public String getVersion() {
        return version;
    }

    public void setVersion(String version) {
        this.version = version;
    }
}
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;

@JacksonXmlRootElement(localName = "gupdate", namespace = "http://www.google.com/update2/response")
public class GUpdate {

    private App app;

    @JsonProperty("app")
    public App getApp() {
        return app;
    }

    public void setApp(App app) {
        this.app = app;
    }
}
public class App {

    private String appid;
    private UpdateCheck updateCheck;

    @JsonProperty("appid")
    public String getAppid() {
        return appid;
    }

    public void setAppid(String appid) {
        this.appid = appid;
    }

    @JsonProperty("updatecheck")
    public UpdateCheck getUpdateCheck() {
        return updateCheck;
    }

    public void setUpdateCheck(UpdateCheck updateCheck) {
        this.updateCheck = updateCheck;
    }
}

然后再Controller中定义使用。

   
    @GetMapping(value = "/xml", produces = MediaType.APPLICATION_XML_VALUE)
    public String  getXmlData() throws JsonProcessingException {
        XmlMapper xmlMapper = new XmlMapper();
        GUpdate gUpdate = new GUpdate();
        App app = new App();
        app.setAppid("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
        UpdateCheck updateCheck = new UpdateCheck();
        updateCheck.setCodebase("https://myhost.com/mytestextension/mte_v2.crx");
        updateCheck.setVersion("2.0");
        app.setUpdateCheck(updateCheck);
        gUpdate.setApp(app);
        return xmlMapper.writeValueAsString(gUpdate);
    }

以上controller响应需要用到的库。

import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;

响应结果

关于最近更新某个网站的wss协议逆向的一些想法。

由于某网站(我们这里简称M)的wss协议的一些验证参数升级,导致原来的身份验证失效,从而获取不到最新的消息,于是重新研究了下算法。

具体过程就是不断的用Chrome 浏览器面板来做断点调试,过程就不说了,但是总结了一些想法。

如果在浏览器的JavaScript中找不到加密和解密算法,那么算法一定是在服务器验证的。

如果发现在浏览器可以实现,但是模拟却实现不了,内容参数一样,那么95%以上是头部参数的原因。

如果碰见了问题就一定要记录下来,否则时间久了,就忘记了。下次接着继续踩同样的坑。(画重点)

SpringBoot项目中普通类使用Bean。

我们在SpringBoot中使用Bean的方式是@Autowired 方式自动注入的,但是在某些普通类中使用Bean的话,就不能使用这种方式,比如以下场景:

在使用 import org.java_websocket.client.WebSocketClient; 作为客户端去链接WebSocket服务器的时候,必须使用有参构造,而不能自动注入的情况下,我们要在WebSocketClient中使用自动注入过的redis、mysql等,使用 @Autowired 获取的对象是null,那么只能直接从SpringBoot的Bean对象管理器中拿取所需要的Bean对象。

操作步骤:

一、定义一个获取Bean的工具类。

定义工具类 SpringUtil (名字自定义) 实现 org.springframework.context.ApplicationContextAware 这个接口,代码如下:

@Component
public class SpringUtil implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        SpringUtil.applicationContext = applicationContext;
    }

    public static <T> T getBean(Class<T> beanClass) {
        return applicationContext.getBean(beanClass);
    }

    public static <T> T getBean(String beanName, Class<T> beanClass) {
        return applicationContext.getBean(beanName, beanClass);
    }
}

记住,这个类一定要添加 @Component 组件注解

二、使用demo

在一个普通类中使用,比如我们想获取RabbitMq的自动注入对象。

RabbitTemplate rabbitTemplate= SpringUtil.getBean(RabbitTemplate.class);
rabbitTemplate.convertAndSend("test_message","test"+new Date());
原理:就是SpringBoot的Bean都交由ApplicationContext applicationContext 来管理,我们只是间接的从 ApplicationContext applicationContext 这个对象中获取,更多的可以参考Spring的原理。

SpringBoot按每天日期来分割日志配置

SpringBoot的日志是非常重要的一项内容,因为我们可以清晰的记录错误异常以及调试。

第一步,添加依赖:

       <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <!--这个依赖可以不加,但是有时候会出现问题,建议加上。-->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
        </dependency>

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
        </dependency>

第二步,添加配置文件

在目录 /src/main/resources 中添加文件,文件名:logback-spring.xml ,文件名必须是这个,文件内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!-- 定义日志输出目录 -->
    <property name="LOG_PATH" value="/tmp/logs/jishuge" />

    <!-- 控制台输出日志 -->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <!-- 按照每天日期分割日志文件 -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_PATH}/%d{yyyy-MM-dd}.log</fileNamePattern>
            <maxHistory>30</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <!-- 设置日志输出级别 -->
    <root level="info">
        <appender-ref ref="CONSOLE" />
        <appender-ref ref="FILE" />
    </root>
</configuration>

以上是最简单的一个配置,每天 /tmp/logs/jishuge 目录下,产生日志文件,比如2022-06-12.log 这样的按日志分割。

给虚拟机的Ubuntu配置静态IP地址。

虽然现在我们开发和测试以及运维都运用了大量的docker可以解决多环境的问题,但是不可否认的是虚拟机还是有自己的用武之地,特别是做分布式学习测试等。但是一般新安装的ip虚拟机ip地址都是自动获取的,下次启动的时候ip地址会发生变化,很不方便,所以今天我们看看如何配置静态的IP地址

一、首先选择虚拟机的网络为桥接模式。
二、进入/etc/netplan目录,找到一个yaml结尾的配置文件信息。
三、修改配置信息,

你进来可能看到的是这样的配置:

network:
  version: 2
  renderer: networkd
  ethernets:
    ens33:
      dhcp4: true

现在我们把它修改成这样:

network:
  ethernets:
    ens33:
      addresses:
        - 192.168.1.111/24
      routes:
        - to: default
          via: 192.168.1.1
      nameservers:
          search: [mydomain,otherdomain]
          addresses: [192.138.1.1, 114.114.114.114]
  version: 2
  • 192.168.1.111/24:配置自己的ip地址为:192.168.1.111静态Ip地址。
  • via: 192.168.1.1:这个是当前局域网的网关地址。
  • addresses: [192.138.1.1, 114.114.114.114] :这是DNS解析。可以根据自己的来。114的是百度的。
四、重新加载网络:sudo netplan apply

更多的配置信息参考官网网络配置:https://ubuntu.com/server/docs/network-configuration