记录第五六周的C++作业


作业要求

/*用Visual Studio 编写一个MFC界面,功能如下:
一. 仿照RW编写一个界面,枚举本机PCI的信息。*/

RW界面

设计思路

首先打开VS2019,新建基于对话框的MFC控制台程序,点击资源文件,双击后缀为.rc的文件进入资源视图,在Dialog中选中面板,删除已有的三个组件,然后添加三个按钮(Button),一个下拉选择框(Combo Box),一个列表(List Contorl),一个次级面板(Tab Control)。如下所示:

代码部分

//主要修改末尾为Dlg的文件,其完整代码如下

// MFCApplication2Dlg.cpp: 实现文件
//

#include "pch.h"
#include "framework.h"
#include "MFCApplication2.h"
#include "MFCApplication2Dlg.h"
#include "afxdialogex.h"
//以下为需要用到的额外头文件
#include<iostream>
//存储读取信息的函数
#include "IO.h"
#include "string"
//访问次级对话框
#include "my_tab1.h"
//连接动态链接库
#pragma comment (lib,"IO.lib")
//声明一个次级对话框变量
my_tab1 m_page1;
//用于记录按钮的活动状态,初始时为0
int button_active = 0;
//创建数组用于遍历所有可能的PCI设备,并存储总数
int bdfs[256*16*8][3], total_pci = 0;
//定义union结构体,方便分割8位16进制数据
typedef union _INT_2_CHAR_
{
    struct _Data_16
    {
        UINT16 FIRST_16;
        UINT16 SECOND_16;
    }HIGH_LOW_16;
    struct _Data_8
    {
        UINT8 FIRST_8;
        UINT8 SECOND_8;
        UINT8 THIRD_8;
        UINT8 FOUTH_8;
    }HIGH_LOW_8;
    UINT32 Data32;
}INT_CHAR;
//声明结构体变量
INT_CHAR data_poor;
//这部分是创建程序时自带的
#ifdef _DEBUG
#define new DEBUG_NEW
#endif

// 用于应用程序“关于”菜单项的 CAboutDlg 对话框

class CAboutDlg : public CDialogEx
{
public:
    CAboutDlg();

// 对话框数据
#ifdef AFX_DESIGN_TIME
    enum { IDD = IDD_ABOUTBOX };
#endif

    protected:
    virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV 支持

// 实现
protected:
    DECLARE_MESSAGE_MAP()
};

CAboutDlg::CAboutDlg() : CDialogEx(IDD_ABOUTBOX)
{
}

void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
    CDialogEx::DoDataExchange(pDX);
}

BEGIN_MESSAGE_MAP(CAboutDlg, CDialogEx)
END_MESSAGE_MAP()


// CMFCApplication2Dlg 对话框

CMFCApplication2Dlg::CMFCApplication2Dlg(CWnd* pParent /*=nullptr*/)
    : CDialogEx(IDD_MFCAPPLICATION2_DIALOG, pParent)
{
    m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

void CMFCApplication2Dlg::DoDataExchange(CDataExchange* pDX)
{
    CDialogEx::DoDataExchange(pDX);
    DDX_Control(pDX, IDC_COMBO1, m_cmb_comm);
    DDX_Control(pDX, IDC_LIST5, m_mylist);
    DDX_Control(pDX, IDC_TAB2, my_tab);
}
//这里保存了所有的交互事件,并指向事件发生时的处理程序,为组件添加处理程序时会自动创建
BEGIN_MESSAGE_MAP(CMFCApplication2Dlg, CDialogEx)
    ON_WM_SYSCOMMAND()
    ON_WM_PAINT()
    ON_WM_QUERYDRAGICON()
    //处理按钮1被单击的事件
    ON_BN_CLICKED(IDC_BUTTON1, &CMFCApplication2Dlg::OnBnClickedButton1)
    //处理按钮2被单击的事件
    ON_BN_CLICKED(IDC_BUTTON2, &CMFCApplication2Dlg::OnBnClickedButton2)
    //处理按钮3被单击的事件
    ON_BN_CLICKED(IDC_BUTTON3, &CMFCApplication2Dlg::OnBnClickedButton3)
    //处理下拉选择框变动时的事件
    ON_CBN_SELCHANGE(IDC_COMBO1, &CMFCApplication2Dlg::OnCbnSelchangeCombo1)
END_MESSAGE_MAP()


// CMFCApplication2Dlg 消息处理程序

BOOL CMFCApplication2Dlg::OnInitDialog()
{
    CDialogEx::OnInitDialog();

    // 将“关于...”菜单项添加到系统菜单中。

    // IDM_ABOUTBOX 必须在系统命令范围内。
    ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
    ASSERT(IDM_ABOUTBOX < 0xF000);

    CMenu* pSysMenu = GetSystemMenu(FALSE);
    if (pSysMenu != nullptr)
    {
        BOOL bNameValid;
        CString strAboutMenu;
        bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX);
        ASSERT(bNameValid);
        if (!strAboutMenu.IsEmpty())
        {
            pSysMenu->AppendMenu(MF_SEPARATOR);
            pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
        }
    }

    // 设置此对话框的图标。  当应用程序主窗口不是对话框时,框架将自动
    //  执行此操作
    SetIcon(m_hIcon, TRUE);         // 设置大图标
    SetIcon(m_hIcon, FALSE);        // 设置小图标

    // TODO: 在此添加额外的初始化代码
    //以下为启动程序时面板中的初始化内容
    //在tab组件中插入一个分页
    my_tab.InsertItem(0, _T("Summary"), LVCFMT_CENTER);
    //将次级对话框与tab组件联系起来
    m_page1.Create(IDD_PAPA1, GetDlgItem(IDC_TAB2));
    //定义一个变量用于保存组件的坐标
    CRect rs;
    my_tab.GetClientRect(&rs);
    //调整子对话框在父窗口中的位置
    rs.top += 25;
    rs.bottom -= 0;
    rs.left += 0;
    rs.right -= 0;

    //设置子对话框尺寸并移动到指定位置
    m_page1.MoveWindow(&rs);

    //分别设置隐藏和显示
    m_page1.ShowWindow(true);
    //设置默认的选项卡
    my_tab.SetCurSel(0);
    //获取表格的原样式
    DWORD dwStyle = m_mylist.GetExtendedStyle(); 
    //整行选取
    dwStyle |= LVS_EX_FULLROWSELECT; 
    //添加网格线
    dwStyle |= LVS_EX_GRIDLINES; 
    //为每行添加选择框
    //dwStyle |= LVS_EX_CHECKBOXES; 
    //设置背景板颜色
    //m_mylist.SetBkColor(RGB(255, 0, 0));
    //设置表格颜色
    //m_mylist.SetTextBkColor(RGB(0, 250, 12));
    //设置文本颜色
    m_mylist.SetTextColor(RGB(0, 100, 10));
    //重新设定列表样式
    m_mylist.SetExtendedStyle(dwStyle);
    //重置下拉选择框
    m_cmb_comm.ResetContent();
    //遍历所有可能的PCI设备,如果存在则将主要信息存入bdfs数组
    for (int b = 0; b < 256; b++)
    {
        for (int d = 0; d < 32; d++)
        {
            for (int f = 0; f < 8; f++)
            {
                if (ReadPCIConfig(MAKE_CONFIG_ADDRESS(b, d, f, 0)) != 0xFFFFFFFF)
                {
                    bdfs[total_pci][0] = b;
                    bdfs[total_pci][1] = d;
                    bdfs[total_pci][2] = f;
                    //每有一个存在的设备则总数加1
                    total_pci++;
                }
            }
        }
    }
    //根据bfds数组中的数据为下拉选择框添加存在的设备
    for (int i = 0; i < total_pci; i++)
    {
        TCHAR s[1000] = { 0 };
        _stprintf_s(s, TEXT("Bus %02X,Device %02X,Function %02X"), bdfs[i][0],bdfs[i][1],bdfs[i][2]);
        m_cmb_comm.AddString(s);
    }
    //设定默认的选项
    m_cmb_comm.SetCurSel(0);
    //调用函数设定表格样式(button_active为按钮活动状态,初始为0,即8字节)
    CMFCApplication2Dlg::clear_item(button_active);
    //将第“0”个设备的PCI8字节信息写入表格中
    CMFCApplication2Dlg::write_item(button_active,bdfs[0][0], bdfs[0][1],bdfs[0][2]);
    //将第“0”个设备的Device ID等信息写入次级对话框中
    CMFCApplication2Dlg::write_tab(bdfs[0][0], bdfs[0][1], bdfs[0][2]);
    return TRUE;  // 除非将焦点设置到控件,否则返回 TRUE
}

void CMFCApplication2Dlg::OnSysCommand(UINT nID, LPARAM lParam)
{
    if ((nID & 0xFFF0) == IDM_ABOUTBOX)
    {
        CAboutDlg dlgAbout;
        dlgAbout.DoModal();
    }
    else
    {
        CDialogEx::OnSysCommand(nID, lParam);
    }
}

// 如果向对话框添加最小化按钮,则需要下面的代码
//  来绘制该图标。  对于使用文档/视图模型的 MFC 应用程序,
//  这将由框架自动完成。

void CMFCApplication2Dlg::OnPaint()
{
    if (IsIconic())
    {
        CPaintDC dc(this); // 用于绘制的设备上下文

        SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);

        // 使图标在工作区矩形中居中
        int cxIcon = GetSystemMetrics(SM_CXICON);
        int cyIcon = GetSystemMetrics(SM_CYICON);
        CRect rect;
        GetClientRect(&rect);
        int x = (rect.Width() - cxIcon + 1) / 2;
        int y = (rect.Height() - cyIcon + 1) / 2;

        // 绘制图标
        dc.DrawIcon(x, y, m_hIcon);
    }
    else
    {
        CDialogEx::OnPaint();
    }
}

//当用户拖动最小化窗口时系统调用此函数取得光标
//显示。
HCURSOR CMFCApplication2Dlg::OnQueryDragIcon()
{
    return static_cast<HCURSOR>(m_hIcon);
}
//以下为下拉选择框发生变动时的事件处理程序
void CMFCApplication2Dlg::OnCbnSelchangeCombo1()
{
    // TODO: 在此添加控件通知处理程序代码
    //首先传递当前按钮活动状态,设定表格的格式
    CMFCApplication2Dlg::clear_item(button_active);
    //捕捉下拉选择框的选项
    int info = m_cmb_comm.GetCurSel();
    //获取对应的Bus、Device、Function参数
    int b = bdfs[info][0],d = bdfs[info][1],f = bdfs[info][2];
    //传递参数将对应信息写入表格
    CMFCApplication2Dlg::write_item(button_active, b,d,f);
    //传递参数将对应信息写入次级对话框
    CMFCApplication2Dlg::write_tab(b,d,f);
}
//当按钮1被单击时的事件处理程序
void CMFCApplication2Dlg::OnBnClickedButton1()
{
    // TODO: 在此添加控件通知处理程序代码
    //设定按钮活动状态
    button_active = 0;
    //改变表格格式
    CMFCApplication2Dlg::clear_item(button_active);
    //捕捉选择框中的选项
    int info = m_cmb_comm.GetCurSel();
    //获取对应的参数
    int b = bdfs[info][0], d = bdfs[info][1], f = bdfs[info][2];
    //将对应的信息写入表格
    CMFCApplication2Dlg::write_item(button_active, b, d, f);
}
//当按钮2被单击时的事件处理程序
void CMFCApplication2Dlg::OnBnClickedButton2()
{
    // TODO: 在此添加控件通知处理程序代码
    //改变按钮活动状态
    button_active = 1;
    //根据按钮活动状态设定表格格式
    CMFCApplication2Dlg::clear_item(button_active);
    //捕捉选择框选项
    int info = m_cmb_comm.GetCurSel();
    //获取设备对应的参数
    int b = bdfs[info][0], d = bdfs[info][1], f = bdfs[info][2];
    //将对应的信息写入表格
    CMFCApplication2Dlg::write_item(button_active, b, d, f);
}
//当按钮3被单击时的事件处理程序
void CMFCApplication2Dlg::OnBnClickedButton3()
{
    //改变按钮活动状态
    button_active = 2;
    // TODO: 在此添加控件通知处理程序代码
    //根据按钮活动状态设定表格格式
    CMFCApplication2Dlg::clear_item(button_active);
    //捕捉选择框中的选项
    int info = m_cmb_comm.GetCurSel();
    //获取对应的参数
    int b = bdfs[info][0], d = bdfs[info][1], f = bdfs[info][2];
    //将对应的信息写入表格
    CMFCApplication2Dlg::write_item(button_active, b, d, f);
}
//以下为自定义函数,用于设定表格的格式,由活动按钮决定
void CMFCApplication2Dlg::clear_item(int a)
{
    //每次运行时先清空表格
    m_mylist.DeleteAllItems();
    //删除所有列,因为删除第“0”列后第一列变为第“0”列,所以只需循环删除第“0”列即可
    //17为表格中出现的最大列数
    for (int i = 0; i < 17; i++)
    {
        m_mylist.DeleteColumn(0);
    }
    //获取表格的宽度
    CRect mRect;
    m_mylist.GetWindowRect(&mRect);
    int width = mRect.Width();
    //插入第“0”列,居中,宽度为30
    m_mylist.InsertColumn(0, _T("0"), LVCFMT_CENTER, 30);
    //根据参数(即按钮活动状态)设定对应的格式
    switch (a)
    {
    //当第一个按钮活动时,表格为17x17(连同索引)
    case 0: 
    {
        for (int i = 0; i < 16; i++)
        {
            TCHAR s[3] = { 0 };
            _stprintf_s(s, TEXT("0%X"), i);
            m_mylist.InsertColumn(i + 1, s, LVCFMT_CENTER, (width - 30) / 16);
        }
        for (int i = 0; i < 16; i++)
        {
            TCHAR s[3] = { 0 };
            _stprintf_s(s, TEXT("%X0"), i);
            m_mylist.InsertItem(i, s, LVCFMT_CENTER);
        }
        break;
    }
    //当第二个按钮活动时,表格为17x9(连同索引)
    case 1:
    {
        for (int i = 0; i < 16; i += 2)
        {
            TCHAR s[5] = { 0 };
            _stprintf_s(s, TEXT("0%X0%X"), i + 1, i);
            m_mylist.InsertColumn(i / 2 + 1, s, LVCFMT_CENTER, (width - 30) / 8);
        }
        for (int i = 0; i < 16; i++)
        {
            TCHAR s[5] = { 0 };
            _stprintf_s(s, TEXT("%X0"), i);
            m_mylist.InsertItem(i, s, LVCFMT_CENTER);
        }
        break;
    }
    //当第三个按钮活动时,表格为17x5(连同索引)
    case 2:
    {
        for (int i = 0; i < 16; i += 4)
        {
            TCHAR s[9] = { 0 };
            _stprintf_s(s, TEXT("0%X0%X0%X0%X"), i + 3, i + 2, i + 1, i);
            m_mylist.InsertColumn(i / 4 + 1, s, LVCFMT_CENTER, (width - 30) / 4);
        }
        for (int i = 0; i < 16; i++)
        {
            TCHAR s[9] = { 0 };
            _stprintf_s(s, TEXT("%X0"), i);
            m_mylist.InsertItem(i, s, LVCFMT_CENTER);
        }
        break;
    }
    };
}
//以下为自定义函数,用于写入对应设备及对应格式的信息,由活动按钮和选择框共同决定
//参数b、d、f用于读取设备信息
void CMFCApplication2Dlg::write_item(int a,int b,int d,int f)
{
    //参数a为按钮活动状态
    switch (a)
    {
    //写入8字节信息
    case 0:
    {
        //用于访问要写入的列
        int j = 0;
        for (int i = 0; i <= 252; i++)
        {
            TCHAR s[5] = { 0 };
            if (i % 4 == 0)
            {
                //调用ReaPCIConfig函数读取信息并存入结构体
                data_poor.Data32 = ReadPCIConfig(MAKE_CONFIG_ADDRESS(b, d, f, i));
                //获取第一个8位信息,将其转换为相应格式,并写入表格
                _stprintf_s(s, TEXT("%02X"), data_poor.HIGH_LOW_8.FIRST_8);
                m_mylist.SetItemText(i / 16, j + 1, s);
                //获取第二个8位信息,将其转换为相应格式,并写入表格
                _stprintf_s(s, TEXT("%02X"), data_poor.HIGH_LOW_8.SECOND_8);
                m_mylist.SetItemText(i / 16, j + 2, s);
                //获取第三个8位信息,将其转换为相应格式,并写入表格
                _stprintf_s(s, TEXT("%02X"), data_poor.HIGH_LOW_8.THIRD_8);
                m_mylist.SetItemText(i / 16, j + 3, s);
                //获取第四个8位信息,将其转换为相应格式,并写入表格
                _stprintf_s(s, TEXT("%02X"), data_poor.HIGH_LOW_8.FOUTH_8);
                m_mylist.SetItemText(i / 16, j + 4, s);
                //每循环一次,将列数加4
                j += 4;
            }
            //如果已经写到了行尾,则重置变量j
            if (j == 16)
                j = 0;
        }
        break;
    };
    //写入16字节信息
    case 1:
    {
        //用于访问要写入的列
        int j = 0;
        for (int i = 0; i <= 252; i++)
        {
            TCHAR s[9] = { 0 };
            if (i % 4 == 0)
            {
                //调用函数获取信息
                data_poor.Data32 = ReadPCIConfig(MAKE_CONFIG_ADDRESS(b, d, f, i));
                //写入第一个16位信息
                _stprintf_s(s, TEXT("%04X"), data_poor.HIGH_LOW_16.FIRST_16);
                m_mylist.SetItemText(i / 16, j + 1, s);
                //写入第二个16位信息
                _stprintf_s(s, TEXT("%04X"), data_poor.HIGH_LOW_16.SECOND_16);
                m_mylist.SetItemText(i / 16, j + 2, s);
                //每循环一次,列数加2
                j += 2;
            }
            //如果已经写到行尾,则重置
            if (j == 8)
                j = 0;
        }
        break;
    };
    //用于写入32位数据
    case 2:
    {
        //用于访问要写入的列
        int j = 0;
        for (int i = 0; i <= 252; i++)
        {
            TCHAR s[17] = { 0 };
            if (i % 4 == 0)
            {
                //调用函数获取信息
                data_poor.Data32 = ReadPCIConfig(MAKE_CONFIG_ADDRESS(b, d, f, i));
                //将32位信息写入表格
                _stprintf_s(s, TEXT("%08X"), data_poor.Data32);
                m_mylist.SetItemText(i / 16, j + 1, s);
                //每写入一条列数加1
                j++;
            }
            //如果已经写到行尾,则重置
            if (j == 4)
                j = 0;
        }
        break;
    };
    };
}
//以下为自定义函数,用于将部分设备信息写入次级对话框,由选择框决定(与按钮活动状态无关)
void CMFCApplication2Dlg::write_tab(int b,int d,int f)
{
    TCHAR s[17] = { 0 };
    //每个设备信息的第一个32字节数据
    data_poor.Data32 = ReadPCIConfig(MAKE_CONFIG_ADDRESS(b, d, f, 0));
    _stprintf_s(s, TEXT("0x%08X"), data_poor.Data32);
    m_page1.SetDlgItemText(IDC_STATIC_1r, s);
    //设备信息的第3个32字节数据的末尾两位
    data_poor.Data32 = ReadPCIConfig(MAKE_CONFIG_ADDRESS(b, d, f, 11));
    _stprintf_s(s, TEXT("0x%02X"), data_poor.HIGH_LOW_8.FIRST_8);
    m_page1.SetDlgItemText(IDC_STATIC_2r, s);
    //设备信息的第3个32字节数据的前6位
    data_poor.Data32 = ReadPCIConfig(MAKE_CONFIG_ADDRESS(b, d, f, 11));
    _stprintf_s(s, TEXT("0x%04X%02X"), data_poor.HIGH_LOW_16.SECOND_16,data_poor.HIGH_LOW_8.THIRD_8);
    m_page1.SetDlgItemText(IDC_STATIC_3r, s);
    //设备信息的第4个32字节数据的末尾两位
    data_poor.Data32 = ReadPCIConfig(MAKE_CONFIG_ADDRESS(b, d, f, 15));
    _stprintf_s(s, TEXT("0x%02X"), data_poor.HIGH_LOW_8.FIRST_8);
    m_page1.SetDlgItemText(IDC_STATIC_4r, s);
    //设备信息的第12个32字节数据
    data_poor.Data32 = ReadPCIConfig(MAKE_CONFIG_ADDRESS(b, d, f, 44));
    _stprintf_s(s, TEXT("0x%08X"), data_poor.Data32);
    m_page1.SetDlgItemText(IDC_STATIC_5r, s);
}

自定义函数

添加自定义函数时,需要修改Dlg结尾的头文件,即xxxDlg.h,本文中添加了3个自定义函数,则需添加如下内容:

public:
    afx_msg void clear_item(int a);
    afx_msg void write_tab(int b,int d,int f);
    afx_msg void write_item(int a,int b,int d,int f);

次级对话框

在资源视图-Dialog,右键,选择添加资源,选择Dialog-新建,即可创建一个次级对话框,在次级对话框中,右键,选择属性,可以修改对话框的ID。修改完之后,再次右键次级对话框,选择”添加类“,输入类名,单击完成,会自动生成对应的头文件和cpp文件,再为次级对话框添加10个Static Text组件(静态文本),接下来修改cpp文件中的以下内容:

//函数是已经存在的,只需要设定前五个静态文本的内容即可,另外会跟随主对话框的选择框选项而变动
void my_tab1::DoDataExchange(CDataExchange* pDX)
{
    CDialog::DoDataExchange(pDX);
    SetDlgItemText(IDC_STATIC_1, _T("Device/Vendor ID"));
    SetDlgItemText(IDC_STATIC_2, _T("Revision ID"));
    SetDlgItemText(IDC_STATIC_3, _T("Class Code"));
    SetDlgItemText(IDC_STATIC_4, _T("Cacheline Size"));
    SetDlgItemText(IDC_STATIC_5, _T("Subsystem ID"));
}

注意事项

1 IO.lib、IO.DLL、IO.h为公司内部文件,无法提供,文中的ReadPCIConfig函数即存储在该动态链接库中,如果你有可以读取信息的函数,那么本文可以为你提供一定的参考。 2 为事件添加处理程序时,会自动创建对应函数,并在xxxDlg.h的头文件里声明,如果有不需要的事件处理程序,请务必在类向导中删除,编辑器会自动注释关联代码。 3 需要修改表格的view属性,将其设置为Report(报表格式),不然插入条目时无法显示(本文中是如此)。 4 MFC使用已经相对较少,网上的可用信息多是五年前的,但仍然具有一定的参考性。 5 部分windows10下编译的exe文件无法在windows7中运行,具体原因请查询。 6 本文代码需以管理员身份运行VS2019,并选择X64,方可顺利Debug或Release

编译结果