近期将切换服务器,因此可能出现服务器不稳定的情况,预计持续一周左右。
在此期间不会更新网站内容。
感谢支持,预祝春节快乐!
2024年1月19日,新服务器上线,相比之前增加了带宽和硬盘容量。
近期将切换服务器,因此可能出现服务器不稳定的情况,预计持续一周左右。
在此期间不会更新网站内容。
感谢支持,预祝春节快乐!
2024年1月19日,新服务器上线,相比之前增加了带宽和硬盘容量。
我们看到的最简单的 C++ 代码是如下形式:
int main()
{
std::cout << "Hello World!\n";
}
问题来了:如何在 UEFI 下面实现这种形式的代码?根据【参考1】,cout << n; 中,<< 是个运算符,n 是个变量,运算符应该接的是变量,所以 cout是个变量,但是在C++中这种高级变量叫做对象。cout 是一个对象。
因此,我们可以通过定义 cout 这个对象,然后定义 << 这个运算符即可。完整代码如下:
#include <UEFI/UEFI.h>
#include <type_traits>
#define EFI_ERROR(status) ((status) != EFI_SUCCESS)
EFI_SYSTEM_TABLE* gSystemTable;
void printInt(int value) {
CHAR16 out[32];
CHAR16* ptr = out;
static_assert(std::is_unsigned_v<char16_t>);
if (value == 0)
{
gSystemTable->ConOut->OutputString(gSystemTable->ConOut, u"0");
return;
}
ptr += 31;
*--ptr = 0;
int tmp = value;// >= 0 ? value : -value;
while (tmp)
{
*--ptr = '0' + tmp % 10;
tmp /= 10;
}
if (value < 0) *--ptr = '-';
gSystemTable->ConOut->OutputString(gSystemTable->ConOut, ptr);
}
class ostream {
public:
void operator<<(int x);
};
void ostream::operator<<(int x) {
printInt(x);
return ;
}
ostream cout;
EFI_STATUS
efi_main(EFI_HANDLE /*image*/, EFI_SYSTEM_TABLE* systemTable)
{
gSystemTable=systemTable;
cout << 122;
gSystemTable->ConOut->OutputString(gSystemTable->ConOut, u"\r\n");
return EFI_SUCCESS;
}
运行结果如下:
已经非常像了。接下来还有一个 std 的问题。这个可以通过 Namespace来实现。“编写程序过程中,名称(name)可以是符号常量、变量、函数、结构、枚举、类和对象等等。工程越大,名称互相冲突性的可能性越大。另外使用多个厂商的类库时,也可能导致名称冲突。为了避免,在大规模程序的设计中,以及在程序员使用各种各样的 C++ 库时,这些标识符的命名发生冲突,标准 C++ 引入关键字 namespace(命名空间/名字空间/名称空间),可以更好地控制标识符的作用域。
例如,我们在 C 语言中,通过 static 可以限制名字只在当前编译单元内可见,在 C++ 中我们通过 namespace 来控制对名字的访问。”【参考2】
修改代码如下形式:
namespace std {
class ostream {
public:
void operator<<(int x);
};
void ostream::operator<<(int x) {
printInt(x);
return ;
}
ostream cout;
}
namespace 是C++中的关键字,用来定义一个命名空间,语法格式为:
namespace name{
//variables, functions, classes
}
name是命名空间的名字,它里面可以包含变量、函数、类、typedef、#define 等,最后由{ }包围【参考3】。
我们就可以直接使用 std::cout << 122; 这种形式了。接下来,还有如何实现 std::cout << 122 << 13; 有兴趣的朋友可以继续研究。
参考:
最近因为测试需要一款能够占用内存的软件,于是求助天杀,请他帮忙编写了一个能够占用指定内存大小的代码。
在使用之前因为微软的限制需要对 Windows进行一些设定:
1.运行 gpedit.msc ,打开“本地组策略编辑器”
2.找到位于 “计算机配置”-> “Windows设置”->“安全设置”->“本地策略”->“用户权限分配”中的“锁定内存页”
3.接下来的目标是将“Administrators”加入其中。点击“添加用户组或组”。
4.点击“对象类型”按钮,勾选其中的“组”
5. 之后在“输入对象名称来选择”中输入“Administrators”(注意末尾有“s”),然后点击“检查名称”按钮
6.重启系统后以管理员权限打开 cmd 串口。这时候你可能遇到无法正常显示汉字的问题,例如:
7. 使用 chcp 936 切换到中文,再次运行即可,程序运行之后要求你输入的需要占用的内存,比如,这里输入 1024 ,可以在任务管理器中看到内存使用率升高了。按任意键之后释放占用的内存。
8.还可以运行多个程序方便进行内存调整
源代码和可执行程序:
写了一个简单的串口测试工具,测试的是写入的速度。简单的说,就是打开串口,然后向里面写入数值,计算写入耗费的时间。通常来说,我们使用 USB 转串口设备,决定速度的因素有两个:1. USB 处理数据的时间 2.设备转串口的速度。其中最主要的因素是后者。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO.Ports; // For SerialPort
using System.Threading;
using System.IO;
using System.Diagnostics;
namespace _433CMD
{
class Program
{
const int COUNTER = 3;
static void Main(string[] args)
{
Stopwatch stopwatch = new Stopwatch();
if (args.Count() != 2)
{
Console.WriteLine("Please input COM PORT Number and ON or OFF");
Console.WriteLine("Usage:");
Console.WriteLine("SST COM3 115200");
Environment.Exit(1);
}
if (args[0].IndexOf("COM") == -1)
{
Console.WriteLine("Parameters error");
Environment.Exit(2);
}
int BaudRate;
if (int.TryParse(args[1], out BaudRate))
{
Console.WriteLine(BaudRate);
}
else
{
Console.WriteLine("BaudRate wrong");
}
SerialPort serialPort1 = new SerialPort();
serialPort1.PortName = args[0];
serialPort1.BaudRate = BaudRate;
serialPort1.DataBits = 8;
serialPort1.Parity = 0;
serialPort1.StopBits = (StopBits)1;
serialPort1.Encoding = System.Text.Encoding.GetEncoding(28591);
serialPort1.DtrEnable = true;
serialPort1.RtsEnable = true;
// Buffer 长1秒
Byte[] Buffer = new Byte[BaudRate/10];
stopwatch.Start();
try
{
// 打开串口
serialPort1.Open();
for (int i=0;i< COUNTER;i++)
{
serialPort1.Write(Buffer, 0, Buffer.Length);
}
serialPort1.Close();
}
catch (IOException e)
{
Console.WriteLine("Open " + args[1] + " failed ");
Console.WriteLine(e.Message);
Environment.Exit(4);
}
stopwatch.Stop();
TimeSpan ts = stopwatch.Elapsed;
Console.WriteLine("Send " + (Buffer.Length *COUNTER /1024).ToString() + "KB in " +(ts.TotalMilliseconds/1000).ToString("F3")+"s");
Console.WriteLine("Serial speed: "+(Buffer.Length * COUNTER/1024 / (ts.TotalMilliseconds/1000)).ToString("F3") +"KBytes/s");
Console.ReadLine();
}
}
}
使用 CH343 进行测试:
‘
编译后的 EXE 下载:
在使用 MounRiver 的过程中,你可能会遇到将 Exam中的例子搬移到其他路径之后无法工作的问题,这是由于Exam项目都依赖了\EXAM\SRC 下面的文件。解决这个问题的方法是:
重复2-5步骤,直到所有的错误消失。
附件是一个按照上面修改过的 SimulateCDC 的例子, 可以放在任意的位置。
参考:
1.https://www.wch.cn/downloads/CH569EVT_ZIP.html
C++中使用关键字 class 来定义类, 其基本形式如下:
class 类名
{
public:
//行为或属性
protected:
//行为或属性
private:
//行为或属性
};
有一种比较特别的函数,被称为“构造函数”,名称和类名称相同。在创建类的对象时,编译器就运行一个构造函数。
设计一个Time类如下,其中有2个构造函数,其中是一个是构造函数的重载。如果在创建过程中有加参数,那么会调用重载之后的构造函数。
class Time {
public:
Time() {//构造函数
_hour = 9;
_min = 17;
_sec = 20;
gSystemTable->ConOut->OutputString(gSystemTable->ConOut, u"Time init1\n\r");
}
Time(int hour,int min,int sec){//对构造函数的重载
_hour=hour;
_min=min;
_sec=sec;
gSystemTable->ConOut->OutputString(gSystemTable->ConOut, u"Time init2\n\r");
}
void Print() {
printInt(gSystemTable->ConOut,_hour);
printInt(gSystemTable->ConOut,_min);
printInt(gSystemTable->ConOut,_sec);
gSystemTable->ConOut->OutputString(gSystemTable->ConOut, u"\n\r");
}
private:
int _hour;
int _min;
int _sec;
};
完整代码如下:
#include <UEFI/UEFI.h>
#include <type_traits>
#define EFI_ERROR(status) ((status) != EFI_SUCCESS)
EFI_SYSTEM_TABLE* gSystemTable;
void printInt(EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL* conOut, int value) {
CHAR16 out[32];
CHAR16* ptr = out;
static_assert(std::is_unsigned_v<char16_t>);
if (value == 0)
{
conOut->OutputString(conOut, u"0");
return;
}
ptr += 31;
*--ptr = 0;
int tmp = value;// >= 0 ? value : -value;
while (tmp)
{
*--ptr = '0' + tmp % 10;
tmp /= 10;
}
if (value < 0) *--ptr = '-';
conOut->OutputString(conOut, ptr);
}
class Time {
public:
Time() {//构造函数
_hour = 9;
_min = 17;
_sec = 20;
gSystemTable->ConOut->OutputString(gSystemTable->ConOut, u"Time init1\n\r");
}
Time(int hour,int min,int sec){//对构造函数的重载
_hour=hour;
_min=min;
_sec=sec;
gSystemTable->ConOut->OutputString(gSystemTable->ConOut, u"Time init2\n\r");
}
void Print() {
printInt(gSystemTable->ConOut,_hour);
printInt(gSystemTable->ConOut,_min);
printInt(gSystemTable->ConOut,_sec);
gSystemTable->ConOut->OutputString(gSystemTable->ConOut, u"\n\r");
}
private:
int _hour;
int _min;
int _sec;
};
EFI_STATUS
efi_main(EFI_HANDLE /*image*/, EFI_SYSTEM_TABLE* systemTable)
{
EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL* conOut = systemTable->ConOut;
gSystemTable=systemTable;
Time time1;
Time time2(1,2,3);
return EFI_SUCCESS;
}
上述代码运行结果如下:
此外,还有析构函数,与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。
有兴趣的朋友可以自行研究。
参考:
去年11月份 edk2 202311正式发布在:
https://github.com/tianocore/edk2/releases/tag/edk2-stable202311
从 History 来看,解决了不少Bug:
和之前类似,这里放上一个完整版,补全了所有的三方库,大小是136MB 左右。
链接:
https://pan.baidu.com/s/1eFC1XwfNTKj7hs_JRRieuw?pwd=LABZ
提取码: LABZ
个人建议:除非有特别明确的目的,否则没有必要追求最新的版本, 所谓 “小车能跑只管推”。
在之前的文章中【参考1】介绍了一个基于 Visual C++非常简单的 UEFI 开发框架。偶然的机会发现使用这个架构可以方便的实现C++ 的编写。于是,从这里开始,介绍如何使用这个框架学习简单的C++知识。
这次首先介绍实验环境的配置。在 C:\BuildBs\CppStudy\ 下创建 CPP 目录,其中放置测试使用到的文件:
其中 gfx.cpp 是源代码,g1.bat 是用于编译的批处理,这里直接使用批处理来进行编译,其中写入的都是绝对路径,这样更加直观容易理解发生了生么。
编译方法是打开 VS2019 X86 Command窗口(必须使用X64 窗口),在目录下运行 g1.bat 之后就会生成Cpp1.efi 的文件。
将这个文件拷贝到 Emulator 目录下就可以在模拟器中测试了。模拟器是来自EDk202308,有兴趣的朋友可以自行编译生成或者使用其他编译器。
之后进入 Emulator 运行 WinHost.exe ,即可运行 cpp1.efi 进行测试。
上述文件打包为一个文件有兴趣的朋友可以使用这个环境进行实验。
特别需要注意的是:在这一套环境中,当需要使用gSystemTable->ConOut->OutputString() 的时候,需要使用 u”XXX” 来定义字符串,这个是因为在 basic_types.h 中使用了如下定义:
using CHAR16 = char16_t;
这个对于我们编写代码影响不大,字符串使用 u作为前缀即可。
参考:
1. https://www.lab-z.com/sus/ 一个非常简单的UEFI Application开发框架
前一段想在一个视频中增加一个计时器的画面,然后忽然发现视频编辑工具并没有直接提供这样的功能。通常建议的手段是:直接插入其他计时器的视频。最简单的做法是在手机上安装一个秒表之类的软件,然后通过内置的录屏功能得到需要的视频。只是这样方法很难获得需要的背景颜色和文字颜色。
正好最近研究了 EasyX 的使用,于是通过编程生成图片,然后使用FFMpeg 把图片粘成视频。
这次测试的代码如下:
#include <graphics.h>
#include <time.h>
#include <conio.h>
#include <stdio.h>
#define WINWIDTH 1920
#define WINHEIGHT 1080
// 一秒25帧,一共1分钟
#define TOTALTIME 60*25
int main()
{
TCHAR FilenameBuffer[256];
// 初始化图形模式
initgraph(WINWIDTH, WINHEIGHT);
settextcolor(RED);
settextstyle(500, 0, _T("Consolas"));
for (int i = 0; i < TOTALTIME; i++) {
BeginBatchDraw();
cleardevice();
swprintf(FilenameBuffer, sizeof(FilenameBuffer), L"%02d:%03d", i / 24, (i * 1000 / 24)%1000);
outtextxy(20, 20, FilenameBuffer);
EndBatchDraw();
swprintf(FilenameBuffer,sizeof(FilenameBuffer), L"%05d.png", i);
printf("%ls\n", FilenameBuffer);
saveimage(FilenameBuffer);
//Sleep(20);
}
// 按任意键退出
_getch();
// 关闭图形模式
closegraph();
return 0;
}
用于生成视频的命令如下:
ffmpeg -r 25 -i %05d.png -b:v 4M output2.mp4
最终的结果可以在B站看到
【EasyX 生成1分钟计时器视频】
第一步:编写代码,在代码中需要将每一帧保存为图片格式。比如,下面是一个在圆中绘制另外一个圆的程序:
#include <graphics.h>
#include <time.h>
#include <conio.h>
#include <stdio.h>
#define WINWIDTH 1200
#define WINHEIGHT 600
#define RADIUS 280
int main()
{
TCHAR FilenameBuffer[256];
// 初始化图形模式
initgraph(WINWIDTH, WINHEIGHT);
//getimage(pImage,0,0, WINWIDTH-1, WINHEIGHT-1);
setfillcolor(RED);
fillcircle(WINWIDTH/2, WINHEIGHT/2,RADIUS);
setfillcolor(GREEN);
for (int i = 1; i < 1001; i++) {
BeginBatchDraw();
fillpie(
(WINWIDTH - RADIUS)/2,
(WINHEIGHT - RADIUS)/2,
(WINWIDTH + RADIUS)/2,
(WINHEIGHT + RADIUS)/2,
0, 3.14*2/1000*i);
EndBatchDraw();
swprintf(FilenameBuffer,sizeof(FilenameBuffer), L"%05d.jpg", i);
printf("%ls\n", FilenameBuffer);
saveimage(FilenameBuffer);
}
// 按任意键退出
_getch();
// 关闭图形模式
closegraph();
return 0;
}
运行这个程序之后,你会在目录下找到 00000.jpg 到 01000.jpg 文件
第二步,使用 FFMPEG 将这些文件“粘”成一个视频。在 https://github.com/BtbN/FFmpeg-Builds/releases 下载编译好的 FFMPEG 工具(对应 ffmpeg-master-latest-win64-gpl 这个名称的文件)。
使用的命令是:
ffmpeg -r 25 -i %05d.jpg -b:v 4M output2.mp4
其中 -r 25表示1秒25帧, -b:v 4M 设定输出视频码率。具体请根据需要酌情修改。
最终就可以生成一个40秒的视频:
最终制作的视频可以在 B站看到:
【EasyX 制作动画视频的例子】