命令行参数和控制台命令

游戏命令包括两种,一种是运行游戏时候指定的命令行参数,另外一种则指的是进入游戏后输入的控制命令。

控制台命令

对于虚幻三来说,控制台命令分为两种,一种是引擎中已经支持的可执行命令,这些命令都是在C++类的Exec函数中进行处理的。另一种是带exec前缀的脚本函数,称为可执行函数。
下面根据用途统一总结,不进行区分。

常用命令

exit(quit)

退出游戏

pause

暂停游戏,按pause break键也可以。

open [url]

打开地图,额外的参数同命令行参数的url部分。比如,在大厅中开始游戏就是用的该命令打开服务器下发的ip地址。

restartLevel

重启当前关卡

reconnect/disconnect/cancel

重新连接服务器,断开服务器连接,取消进行的服务器连接操作。

Kill系列

  1. KillAll [class] - 销毁或破坏关卡中特定类的所有实例。
  2. KillPawns - 销毁关卡中的所有 pawn。
  3. KillBadGuys - 销毁所有不在同一个团队作为玩家的 pawns。
  4. Suicide -玩家自杀

god/fly/walk

幽灵模式(可以飞、穿墙)/飞行模式/正常模式

AllAmmo

将弹药数目设置为所有武器的最大值

SetRes

setres [width] [x|X] [height] [w|f]
改变分辨率(width为宽,height为高)
模式(w = 窗口; f = 全屏),
比如 800x600w表示分辨率为800乘以600的窗口模式,
1024x768f表示分辨率为1024乘以768的全屏模式。

FreeCamera

将玩家的相机设置为自由轨道相机模式(第三人称视角),FreeCamera false恢复。

Addbots

  1. AddBots [number] - 为了进行测试,会向关卡中添加指定机器人数.
  2. AddBlueBots [number] - 在团队游戏中为蓝队添加指定的机器人数。
  3. AddRedBots [number] - 在团队游戏中为红队添加指定的机器人数。
  4. AddNamedBot [name] [bUseTeamNum] [teamnum] - 添加一个使用指定名称的机器人。如果 bUseTeamNum 为真而且指定了团队数,那么将该机器人添加到指定的团队。

Shot

  1. shot/screenshot 以当前的屏幕分辨率截取屏幕截图。
  2. tiledshot [factor] 以当前分辨率乘以指定因数为分辨率来获取屏幕截图。比如,tiledshot 2会得到2乘以2的shot截图,分辨率也是shot的2倍。
  3. SHOTNOHUD,不截屏hud。

渲染命令

ViewMode

viewmode命令设置渲染模式
1. detaillight 默认模式,使用受到法线贴图的光照影响的中性色彩材质渲染场景
2. unlit 无光照
3. lightingonly 只使用光照
4. wireframe 线框模式
5. brushwireframe 线框模式,但是显示画刷边缘
6. lightcomplexity 显示光照复杂度
7. lightmapdensity 显示光照贴图密度
8. litlightmapdensity 显示光照贴图像素密度 6和7的结合
9. texturedensity 显示每个表面上漫反射通道上的贴图像素密度
10. shadercomplexity 显示每个表面上所应用的材质的复杂度

Show

切换各种项目的显示(仅用于客户端)
1. bounds 切换actor边界的显示(包围盒和包围球)
2. volumes 切换体积的显示(体积盒)
3. collision 切换碰撞体的显示状态
4. bsp 切换bsp几何体的显示(用bsp画刷制作的物体,比如墙)
5. fog 切换雾actors的显示
6. particles 切换粒子几何体的显示(比如烟雾弹,特效做的门)
7. paths 切换路径或导航网格物体的显示
8. navnodes 切换和寻路相关的actors的显示
9. foliage 切换植被的显示
10. terrain 切换地形几何体的显示
11. terrainpatches 切换地形块的显示。在每个块的周围描画一个轮廓。
12. staticmeshes 切换静态网格物体几何体的显示
13. decal 切换decal actors的显示
14. decalinfo 切换decals(贴花)的调试开发信息的显示(平头截体、切线轴等)。
15. staticmeshes 切换静态网格物体几何体的显示。
16. postprocess 切换后期处理特效的显示
17. skelmeshes/skeletalmeshes 切换骨架网格物体几何体的显示
18. MISSINGCOLLISION 切换高亮显示启用了碰撞但是没有碰撞网格物体的静态网格物体

显示命令

display系列

displayall class prop

在屏幕上实时地显示类class所有实例的属性prop的值

display obj prop

在屏幕上实时地显示对象obj的属性prop的值。

displayallstate class

在屏幕上实时地显示类class所有实例的当前处于的状态,比如行走,空闲,攻击等

displayclear

清楚display系列命令所有的输出

set

set class/obj prop value
1. 设置给定类class(包括其子类)的所有对象的属性prop的值为value
2. 设置给定对象obj的属性prop的值为value

可以用displayall实时显示出来这个属性,再用set设置后观察属性变化。

统计命令

stat命令负责在游戏运行时在屏幕上启用显示统计数据功能。

none

关闭所有统计数据的显示

fps

切换帧频率统计数据的显示

anim

切换动画系统统计数据的显示状态

net

切换网络统计数据显示的 打开/关闭 状态

game

切换游戏统计数据的显示。(更新时间等)

ui

切换UIScene统计数据的显示

collision

切换碰撞统计数据的显示状态

octree

切换八叉树相关统计数据的显示

physics

切换一般物理统计数据的显示
1. physicscloth 切换关于布料仿真统计数据的显示。
2. physicsfields 切换关于物理域的统计数据的显示状态。
3. physicsfluids 切换关于PhysX流体仿真统计数据的显示。

memory

切换一般内存统计数据的显示

memorychurn

切换处理内存分配的统计数据的显示

scenerendering

切换场景渲染统计数据的显示

startfile/stopfile

  1. startfile开始捕获统计数据文件以便和StatsViewer结合使用。
  2. stopfile完成捕获统计数据文件。
  3. 文件存储位置:UDKGame\Profiling\UE3Stats\xxx文件..ustats
  4. 打开工具:Binaries\StatsViewer.exe

GameProfile/ProfileGame

该命令在虚幻三和UDK中用于统计脚本函数的运行时间。
1. start 开始Profile
2. stop 结束Profile
3. 文件:UDKGame\Profiling\T-2016.11.29-19.26.55.gprof
4. 打开工具:Binaries\GameplayProfiler.exe
5. UObject::CallFunction中统计了每个函数的调用时间。

调试命令

调试命令的结果是控制台形式的输出,并不是在游戏窗口中显示。在逆战中,需要按f8显示控制台窗口,再输入调试命令。其余类型的命令可以使用f7也可以使用f8。

obj

gc/garbage

强制进行垃圾回收清理。

list

显示包中的一个类别的所有物体的列表。
1. obj list显示包中所有的物体列表。
2. obj list class=pawn 只显示指定的类的所有物体的对象列表,比如pawn。

dump

dump objname
在控制台中输出某个对象的所有属性,可以先用displayall找到这个对象名。

物理命令

nxvis collision…

碰撞相关命令

nxvis joint…

关节相关命令

nxvis cloth…

布料相关命令

nxvis fluid…

流体相关命令

nxvis softbody…

软体相关命令

内存命令

mem

显示内存分配信息
1. mem
2. mem detailed
3. mem stat

configmem

显示配置文件内存分配信息

particlememory

粒子内存信息

memfragcheck

内存碎片检测

memleakcheck

内存泄漏检测

UI/GFX命令

ShowHUD

显示(隐藏)所有的HUD

ShowScores

显示(隐藏)积分面板

Toggleui

切换UI的更新和显示

gfxinvoke

调用GfxMovie对应的flash文件的as函数

dumpsftextures

输出GFx Texture Usage到log文件中。

虚幻三控制台命令调用流程

  • APlayerController::ConsoleCommand。
    • ULocalPlayer::Exec,处理一部分命令。
      • UGameViewportClient::Exec,引擎自带的大部分命令在此函数中实现。
        • UGFxInteraction::Exec,执行gfx相关的命令。
        • UUIInteraction::Exec,处理一部分命令。
          • UUIInteraction::ScriptConsoleExec。
          • UGameUISceneClient::Exec。
            • UUISceneClient::Exec.
              • UUISceneClient::ScriptConsoleExec.
        • UGameViewportClient::ScriptConsoleExec。
        • UEngine::Exec,处理一部分命令。
      • UPlayer::Exec,处理一部分命令。
        • UWorld::Exec。
        • APlayerInput::ScriptConsoleExec
        • APlayerController::ScriptConsoleExec
        • APawn::ScriptConsoleExec
        • AInvManager::ScriptConsoleExec
        • AWeapon::ScriptConsoleExec
        • AHUD::ScriptConsoleExec
        • AGameInfo::ScriptConsoleExec
        • ACheatManager::ScriptConsoleExec
        • AInteraction::ScriptConsoleExec
  • 返回APlayerController::ConsoleCommand,命令未处理。

注意:UObkect::ScriptConsoleExec,处理的是当前类中自定义脚本命令(执行带exec前缀的脚本函数)。
因此,流程中带有ScriptConsoleExec函数执行的类(以及子类)都可以定exec脚本函数来执行控制台命令。只有在处理流程中的命令才有效,处理流程外的命令无法被处理。

自定义命令

Native命令

在控制台命令调用流程中涉及到的C++类的Exec函数中添加对新命令的处理逻辑。

脚本命令

在处理流程中的有ScriptConsoleExec调用的类(Interaction、UISceneClient、GameViewportClient、PlayerInput、PlayerController、Pawn、InvManager、Weapon、HUD、GameInfo、CheatManager)
中添加exec前缀的脚本函数。

更多的游戏命令可以参考文档:Console Commands

虚幻引擎游戏命令

命令行参数是指通过命令行或者可执行文件快捷方式启动游戏进程的时候,附加在后面的一系列参数。

命令行参数

命令行参数分为两种,一种是编译游戏代码时候需要用到的命令行参数。另一种则是在启动游戏进程时候指定的参数。

编译命令行参数

编译代码期间会用到的参数如下:

  1. 版本 -debug -release</p>
  2. 全量 -full (默认增量)

  3. 自动更新C++头文件,不弹框确认 -auto
    注意:虚幻三编译脚本.uc文件时候,会更新.h头文件

  4. 移除.u文件中的源代码信息 -stripsource注意:.u文件是虚幻三脚本文件编译后的字节码文件,虚幻四中不存在

    因此,编译代码时候最多可能指定以下参数组合:
    make -debug -full -auto -stripsource
    make -release -full -auto -stripsource

注意:通过在vs中指定make命令可以调试编译代码的过程。
如下图所示:
enter description here

运行命令行参数

启动游戏进程指定的命令行参数分为两个个部分,第一个部分用于指定进程的运行模式(客户端、服务器、编辑器),第二个部分用于指定地图的URL以及附加选项。

运行模式

游戏进程可以用三种不同的模式进行启动,分别是客户端、服务器、编辑器模式。因此,虚幻引擎生成的游戏可执行文件同时可以作为游戏服务器、游戏客户端、游戏编辑器运行。这是一个很神奇的地方。

  1. 客户端模式
    默认情况下,启动的游戏进程就是客户端模式,不需要指定额外的命令行参数。
    在UE4中,也可以指定-game参数。

  2. 服务器模式
    通过指定server参数,可以启动一个游戏服务器。
    比如,udk.exe server,则是使用udk启动一个游戏服务器。
    对于UE4,则是UE4Editor.exe -server。
    实际上,可以修改游戏引擎设置,输出自定义的游戏执行文件。在启动这个游戏可执行文件时候,只需要附近sever参数就可以启动一个游戏服务器。

  3. 编辑器模式
    通过指定editor参数,可以启动一个游戏服务器。
    比如,udk.exe editor,则是使用udk启动游戏编辑器。
    对于UE4,则是UE4Editor.exe -editor。

模式的URL参数

URL分为两个部分:地图名称或者服务器地址,可选的附加参数。
地图部分用于强制游戏启动时候加载特定的地图,附加参数用于设置额外的启动方式,比如设置分辨率,是否打开log窗口等。这些参数和server或editor模式结合起来就可以启动特定地图的服务器或者用编辑器打开特定地图。
如果没有url参数,那么游戏进程会打开默认的地图。URL参数必须在可执行命令名称的后面或者在模式参数后面。

  1. 地图
    如果运行本地游戏,则指定Maps目录下的地图名称,比如MyMap. umap。
    如果运行网络游戏,则指定游戏服务器的IP地址(server模式启动的游戏进程就是游戏服务器)。

  2. 附加参数
    附加参数与地图之间用”?”分隔。
    附加参数分为两种类型,一种是用”=”指定的选项,一种是用”-“指定的开关。

    常用的选项参数:
    dedicated:指定服务器作为专用服务器。
    listen: 指定服务器作为监听服务器 。
    spectatoronly:以观看模式启动游戏
    class: 告诉引擎要使用的玩家类(覆盖默认值)。
    game:: 指定使用的GameInfo类。
    name: 要使用的玩家名称。
    team: 指定玩家所在的团队。
    resx/resy: 设置游戏窗口的分辨率。
    consolex/consoley:设置控制台窗口(log窗口)分辨率。

    常用的开关参数:
    log: 打开日志窗口。
    windowed:窗口模式运行。
    nomoviestartup: 略过启动动画。
    nosplash: 略过启动splash窗口。

    更多的附加参数请参考文档:虚幻四引擎命令行参数

  3. 一些示例:
    UDK.exe server MyMap.udk
    UDK.exe 127.0.0.1
    UDK.exe MyMap.udk?-resX=640 -resY=480 -log log=log.txt
    UDKLift.exe DM-发电站?Game=UTGame.UTTeamGame?listen=true?TeamIndex=0?Name=FS01 -log -windowed -resX=640 -resY=360 -nomoviestartup -nosplash windowPosX=0 windowPosY=0 -consolePosX=0 -consolePosY=365
    MyGame.exe editor MyMap.umap -NoLoadStartupPackages -NoGADWarning

DebugView简介

DebugView是一个监视本地系统或者通过tcp/ip连接的网络系统的OutputDebugString输出的应用程序。DebugView不仅能够监视Win32应用的debug输出,还可以监视内核模型的debug输出。因此,如果使用OutputDebugString来打印调试信息的话,就可以在程序运行时候通过DebugView来实时显示程序的调试信息。
这种方式在某种意义上,比将Log打印到文件中,关闭程序后再查看Log输出的方式更加方便。而且可以将这两种调试程序的方式结合起来,既使用DebugView来实时显示调试信息,又将调试信息输出到Log文件中,方便以后分析。

安装DebugView

下载地址:DebugView
下载后面后解压压缩包,发现里面有三个文件:Dbgview.exe、dbgview.chm、Eula.txt。
Dbgview.exe就是我们要使用的实时显示Log工具。dbgview.chm是自带的文档,有不懂的地方可以查阅该文档。
现在可以将DebugView.exe放到任何你喜欢的目录,比如桌面。

配置DebugView

配置Capture

如下图所示,要记得勾选Capture Win32和Capture Global Win32。Capture Global Win32用于网络模式下捕获网络主机的Debug输出的时候。如果需要捕获内核模式的调试输出,记得勾选Capture Kernel Win32。
enter description here
如果点击Capture Global Win32菜单出现提示:
enter description here
重新以管理员的身份启动DebugView。
enter description here

配置Filter

如下图所示,打开Filter对话框,
enter description hereenter description here
然后在Include中输入要包含的字符串,比如”hankpcxiao”,多个字符串用;分隔,比如”hankpcxiao;xpc”。
这样就只会捕获包括过滤字符串hankpcxiao或者xpc的OutputDebugString输出。
如果我们在每个OutputDebugString输出前自动加上过滤字符串,那么DebugView就只会输出我们的Log信息了。

开启捕获

最后确保开启了捕获,如下图所示:
enter description here

如何在程序中输出Log信息?

默认情况下,DebugView会捕获函数OutputDebugString的输出,但是这个函数的参数是个字符串指针,不太方便。下面我们通过一些列步骤来创建一个方便使用的Log类。

格式化输出

我们习惯使用printf这样的函数来格式化输出信息,因此这次我们也把OutputDebugString包装成可变参数形式的格式化输出函数。

1
2
3
4
5
6
7
8
9
10
11
void DebugViewOutput(const char* fm, ...)
{
static char szMsg[MAX_PATH];

va_list argList;
va_start(argList, fm);
vsprintf_s(szMsg, fm, argList);
va_end(argList);

OutputDebugString(szMsg);
}

让DebugView在VS调试程序时候也能够捕获Log

网上有不少介绍DebugView使用的文章,但是都忽略了一个事实,那就是默认情况下,使用VS运行程序时候,OutputDebugString的输出是到VS的输出窗口中,DebugView中并没有任何信息。只有单独运行程序的时候,DebugView才能够捕捉到信息。
但是这样就不能结合打断点调试和DebugView两个强大的调试方法了。不过,还是有解决办法的。通过一个叫做DBWin通信机制可以实现调试程序时候,把OutputDebugString的输出信息显示到DebugView窗口中。这套机制的本质是通过内存映射文件来跨进程交换数据。
具体参考以下的类代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
    class XpcDebugView
{
public:
XpcDebugView() {}
~XpcDebugView() {}
void XpcDebugViewOutput(const char* fm, ...);

private:
struct DBWinBuffer
{
DWORD ProcessId;
char Data[4096 - sizeof(DWORD)];
};
bool Initialize();
void UnInitialize();

HANDLE m_mutex;
HANDLE m_fileMapping;
HANDLE m_bufferReadyEvent;
HANDLE m_dataReadyEvent;
DBWinBuffer* m_buffer;
};

bool XpcDebugView::Initialize()
{
m_mutex = OpenMutex(SYNCHRONIZE, FALSE, TEXT("DBWinMutex"));

//打开DBWIN_BUFFER
m_fileMapping = OpenFileMapping(FILE_MAP_WRITE, FALSE, TEXT("DBWIN_BUFFER"));

if (m_fileMapping == NULL) return false;

//打开DBWIN_BUFFER_READY
m_bufferReadyEvent = OpenEvent(SYNCHRONIZE, FALSE, TEXT("DBWIN_BUFFER_READY"));

//打开DBWIN_DATA_READY
m_dataReadyEvent = OpenEvent(EVENT_MODIFY_STATE, FALSE, TEXT("DBWIN_DATA_READY"));

//等待DBWIN_BUFFER就绪
WaitForSingleObject(m_bufferReadyEvent, INFINITE);

//把DBWIN_BUFFER映射到某个地址
m_buffer = (DBWinBuffer*)MapViewOfFile(m_fileMapping, FILE_MAP_WRITE, 0, 0, 0);
m_buffer->ProcessId = GetCurrentProcessId();
}

void XpcDebugView::UnInitialize()
{
//释放和关闭DBWIN_BUFFER
FlushViewOfFile(m_buffer, 0);
UnmapViewOfFile(m_buffer);

//触发DBWIN_DATA_READY
SetEvent(m_dataReadyEvent);

CloseHandle(m_fileMapping);

//清理
CloseHandle(m_dataReadyEvent);
CloseHandle(m_bufferReadyEvent);
ReleaseMutex(m_mutex);
CloseHandle(m_mutex);
}

void XpcDebugView::XpcDebugViewOutput(const char* fm, ...)
{
if (Initialize() == false) return;

static char szMsg[MAX_PATH];

va_list argList;
va_start(argList, fm);
vsprintf_s(szMsg, fm, argList);
va_end(argList);

#pragma warning( push )
#pragma warning( disable: 4996 )
//向DBWIN_BUFFER写入数据
strcpy(m_buffer->Data, szMsg);
printf(szMsg);
#pragma warning( pop )

UnInitialize();
}

使用的时候直接调用类成员函数XpcDebugViewOutput即可。
关于这部分的内容更具体的可以参考文章,如何让OutputDebugString绕过调试器

输出到DebugView的同时输出到Log文件

我将上面的类改造成下面的样子,在初始化时候创建一个Log文件,在反初始化时候关闭Log文件,每次调用XpcDebugViewOutput使用调用fprintf将格式化字符串输出到文件中。这样就能达到输出Log信息到DebugView中的同时,又能够将Log信息持久化保存了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
    class XpcDebugView
{
public:
XpcDebugView() { m_pszLogName = "DefaultLog.txt"; Initialize(); }
XpcDebugView(const char* pszLogName) { m_pszLogName = pszLogName; Initialize();}
~XpcDebugView() { UnInitialize(); }
void XpcDebugViewOutput(const char* fm, ...);

private:
struct DBWinBuffer
{
DWORD ProcessId;
char Data[4096 - sizeof(DWORD)];
};

bool Initialize();
void UnInitialize();

bool InitializeDBWin();
void UnInitializeDBWin();

HANDLE m_mutex;
HANDLE m_fileMapping;
HANDLE m_bufferReadyEvent;
HANDLE m_dataReadyEvent;
DBWinBuffer* m_buffer;
const char* m_pszLogName;
FILE* m_pFileLog;
};

#pragma warning( push )
#pragma warning( disable: 4996 )
bool XpcDebugView::Initialize()
{
m_pFileLog = fopen(m_pszLogName, "w");
return m_pFileLog != NULL;
}

void XpcDebugView::UnInitialize()
{
if (m_pFileLog)
{
fclose(m_pFileLog);
}
}

bool XpcDebugView::InitializeDBWin()
{
m_mutex = OpenMutex(SYNCHRONIZE, FALSE, TEXT("DBWinMutex"));

//打开DBWIN_BUFFER
m_fileMapping = OpenFileMapping(FILE_MAP_WRITE, FALSE, TEXT("DBWIN_BUFFER"));

if (m_fileMapping == NULL) return false;

//打开DBWIN_BUFFER_READY
m_bufferReadyEvent = OpenEvent(SYNCHRONIZE, FALSE, TEXT("DBWIN_BUFFER_READY"));

//打开DBWIN_DATA_READY
m_dataReadyEvent = OpenEvent(EVENT_MODIFY_STATE, FALSE, TEXT("DBWIN_DATA_READY"));

//等待DBWIN_BUFFER就绪
WaitForSingleObject(m_bufferReadyEvent, INFINITE);

//把DBWIN_BUFFER映射到某个地址
m_buffer = (DBWinBuffer*)MapViewOfFile(m_fileMapping, FILE_MAP_WRITE, 0, 0, 0);
m_buffer->ProcessId = GetCurrentProcessId();
}

void XpcDebugView::UnInitializeDBWin()
{
//释放和关闭DBWIN_BUFFER
FlushViewOfFile(m_buffer, 0);
UnmapViewOfFile(m_buffer);

//触发DBWIN_DATA_READY
SetEvent(m_dataReadyEvent);

CloseHandle(m_fileMapping);

//清理
CloseHandle(m_dataReadyEvent);
CloseHandle(m_bufferReadyEvent);
ReleaseMutex(m_mutex);
CloseHandle(m_mutex);
}

void XpcDebugView::XpcDebugViewOutput(const char* fm, ...)
{
if (InitializeDBWin() == false) return;

static char szMsg[MAX_PATH];

va_list argList;
va_start(argList, fm);
vsprintf_s(szMsg, fm, argList);
va_end(argList);

//向DBWIN_BUFFER写入数据
strcpy(m_buffer->Data, szMsg);
if (m_pFileLog)
{
fprintf(m_pFileLog, szMsg);
}

UnInitializeDBWin();
}
#pragma warning( pop )

如何使用XpcDebugView类

最简单的方式是定义一个XpcDebugView的全局变量,比如:
XpcDebugView myDebugview(“myLog.txt”);
输出Log信息的时候调用函数myDebugview.XpcDebugViewOutput(“%d %d %s\n”, 1, 2, “log”):
为了方便使用,可以在XpcDebugViewOutput的输出后面添加换行符,这样每次调用后就会自动换行了。
并且加上过滤字符串前缀,这样DebugView就只会捕获我们的输出了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
void XpcDebugView::XpcDebugViewOutput(const char* fm, ...)
{
static char szMsg[MAX_PATH];
static char szOutput[MAX_PATH];

va_list argList;
va_start(argList, fm);
vsprintf_s(szMsg, fm, argList);
va_end(argList);

strcpy(szOutput, "[hankpcxiao] ");
strcat(szOutput, szMsg);
strcat(szOutput, "\n");
if (m_pFileLog)
{
fprintf(m_pFileLog, szOutput);
}

//向DBWIN_BUFFER写入数据
if (InitializeDBWin())
{
strcpy(m_buffer->Data, szOutput);
UnInitializeDBWin();
}
}

总结

首先需要下载好DebugView程序,然后配置capture选项,另外是Filter字符串。最后为了保证在VS中调试程序时候,能够将调试信息输出到DebugView,需要使用DBWin通信进制。为此,我封装了一个Log类,在将Log输出到DebugView的同时也将Log输出到日志文件中。

参考资料:

[1] Frequently asked questions on the Microsoft application DebugView.exe
[2] 如何让OutputDebugString绕过调试器

WebGL概述

什么是WebGL?WebGL简单的说就是在Web中渲染OpenGL的技术,也可以理解为把OpenGL的接口移植到浏览器中使用。具体的可以参考WebGL的维基百科
使用WebGL可以通过编写网页代码在浏览器中渲染三维图像,而且不需要任何的插件,比如Adobe Flash Player等。
WebGL在最新的浏览器中得到了广泛支持。

WebGL与HTML5的关系

HTML5是最新的HTML(超文本标记语言)的最新修订版本。
HTML5中新增了<canvas>标签用于绘图。在HTML5之前,只能使用<img>标签在网页中显示静态图片,如果要显示动画得借助于Adobe Flash Player等第三方插件。在HTML5中,可以在<canvas>标签上绘制二维图像,也可以使用WebGL绘制三维图像。
WebGL相对于HTML5的关系就好比是OpenGL库和三维应用程序的关系。WebGL只是提供了底层的渲染和计算的函数。

WebGL与JavaScript的关系

JavaScript是一种浏览器中运行的动态脚本语言。WebGL也需要依靠JavaScript来操作浏览器中的对象。JavaScript与WebGL的关系类似于C或者C++和OpenGL的关系。

WebGL与OpenGL的关系

WebGL基于OpenGL ES 2.0,WebGL实现了OpenGL ES 2.0的一个子集。WebGL使用Javascript进行内存管理,使用GLSL ES作为着色器语言。具体的关系可以参考下图:
WebGL与OpenGL

WebGL程序的结构

默认情况下,网页程序包括HTML和Javascript脚本语言两部分。但是WebGL程序,还有特殊的GLSL ES着色器语言部分。
具体结构如下图所示:
WebGL程序的结构

总结

本文介绍WebGL的基本概念,以及WebGL和HTML、JavaScript、OpenGL之间的关系等。接下来的文章会介绍具体的WebGL编程知识。

参考

[1] WebGL编程指南
[2] 维基百科

上一篇文章定制IE浏览器弹窗中的外部窗口就是一个不规则窗口,这篇文章介绍下其是如何实现的。思路是根据这张图片创建一个不规则区域,然后将窗口的区域设置为该不规则区域。

第一步,在资源文件rc中设置对话框的属性

Border:None

Style:Popup

第二步,导入背景图片到程序资源中

最好是导入位图,虽然也可以导入其它格式的图片。假设导入位图ID为IDB_BITMAP_BACK。

第三步,在OnInitialDlg函数中,创建区域,并将其设置为窗口区域

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
//OnInitDialog()中
CRgn wndRgn;

m_bitmapBack.LoadBitmap(IDB_BITMAP_BACK);
CreateRgn(m_bitmapBack, RGB(255, 255, 255), wndRgn);
SetWindowRgn(wndRgn, TRUE);

//根据图片创建区域的函数
void CClientBrowserDlg::CreateRgn(CBitmap cBitmap, COLORREF dwColorKey, CRgn wndRgn)
{
CDC *pDC = this->GetDC();
CDC memDC;
//创建与传入DC兼容的临时DC
memDC.CreateCompatibleDC(pDC);

CBitmap *pOldMemBmp=NULL;
//将位图选入临时DC
pOldMemBmp = memDC.SelectObject(cBitmap);

//创建总的窗体区域,初始region为0
wndRgn.CreateRectRgn(0,0,0,0);

BITMAP bit;
cBitmap.GetBitmap (bit);//取得位图参数,这里要用到位图的长和宽

int y;
for(y=0; y <= bit.bmHeight; y++)
{
CRgn rgnTemp;
int iX = 0;
do
{
//跳过透明色找到下一个非透明色的点.
while (iX <= bit.bmWidth memDC.GetPixel(iX, y) == dwColorKey)
iX++;
//记住这个起始点
int iLeftX = iX;
//寻找下个透明色的点
while (iX <= bit.bmWidth memDC.GetPixel(iX, y) != dwColorKey)
++iX;
//创建一个包含起点与重点间高为1像素的临时“region”
rgnTemp.CreateRectRgn(iLeftX, y, iX, y+1);
//合并到主"region".
wndRgn.CombineRgn(wndRgn, rgnTemp, RGN_OR);
//删除临时"region",否则下次创建时和出错
rgnTemp.DeleteObject();
} while(iX < bit.bmWidth );
iX = 0;
}

if(pOldMemBmp)
memDC.SelectObject(pOldMemBmp);
}

第四步,在OnPaint()绘制窗口背景图片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
void CClientBrowserDlg::OnPaint()
{
if (IsIconic())
{
CPaintDC dc(this); // device context for painting

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

// Center icon in client rectangle
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;

// Draw the icon
dc.DrawIcon(x, y, m_hIcon);
}
else
{
//选入DC
CClientDC cdc(this);
CDC comdc;
comdc.CreateCompatibleDC(cdc);
comdc.SelectObject(m_bitmapBack);

//生成BITMAP
BITMAP bit;
m_bitmapBack.GetBitmap(bit);

//客户区域
CRect rect;
GetClientRect(rect);

//用客户区的DC绘制所生成的BITMAP,并适应为窗口大小
cdc.StretchBlt(0,0,rect.Width(),rect.Height(),comdc,0,0,bit.bmWidth,bit.bmHeight,SRCCOPY);

CDialog::OnPaint();
}
}

第五步,点击客户区移动窗口

这一点还是有意义的,比如上一篇定制IE浏览器窗口的文章,其外部窗口就是使用这里介绍的不规则窗体。不规则窗体由于是无边框的,因此无法点击边框移动窗口了。因此,设置点击客户端移动是有意义的。而且窗口的内部区域已经被浏览器控件占据了,只有外部的边界区域可以点击到,因此这样刚好模拟出了点击正常窗口边框的效果。

设置客户区可以点击的代码如下:

1
2
3
4
5
6
7
8
9
10
11
LRESULT CClientBrowserDlg::OnNcHitTest(CPoint point)
{
// TODO: Add your message handler code here and/or call default
// 取得鼠标所在的窗口区域
UINT nHitTest = CDialog::OnNcHitTest(point);

// 如果鼠标在窗口客户区,则返回标题条代号给Windows
// 使Windows按鼠标在标题条上类进行处理,即可单击移动窗口
return (nHitTest == HTCLIENT) ? HTCAPTION : nHitTest;
//return CDialog::OnNcHitTest(point);
}

在客户端程序中嵌入浏览器,有两种方式,一种是使用微软的IE控件,一种是使用CEF。这里介绍的是使用CWebBrowser2类(在MFC程序中插入IE的Active控件生成),定制内嵌浏览器窗口的一些经验。

本文的经验积累于实现逆战退出游戏时候的广告弹窗的过程中,下面Show一下这个自带萌妹子的弹窗吧。

这是一个无边框的Windows对话框程序,并且是一个基于背景图片的不规则弹窗窗口;内部嵌入了一个浏览器控件窗口,这个漂亮的妹子就是浏览器控件打开的网页显示出来的。对这个妹子有兴趣的,可以去玩一把逆战,退出客户端的时候就会出来这个弹窗了。

下面介绍一些关于实现该弹窗浏览器的Tips。

一、如何获得CWebBrowser2

方法1:网络搜索下载,比如我以前的一篇博文里面有下载链接:vc内嵌浏览器。

方法2:在MFC程序中插入IE对应的Activex控件,工程中就会生成这个类。

为了定制浏览器窗口,我继承了该类,自定义了浏览器窗口类CYXBrwser。

二、让浏览器窗口适应对话框窗口大小

在对话框类的OnInitDialog()函数中,添加如下代码:

1
2
3
4
5
6
7
8
m_pBrowser = new CYXBrowser(); 
RECT rect;
GetClientRect(rect);
rect.left += 8;
rect.right -= 8;
rect.top += 8;
rect.bottom -= 1;
m_pBrowser->Create(TEXT("NZBrowser"), WS_CHILD | WS_VISIBLE, rect, this, MY_IEBROWSER_ID);

注意,rect的大小需要调节来获得需要的效果。

三、屏蔽右键

有种比较的方法是在PreTranslateMessage中过滤WM_RBUTTONDOWN消息。

1
2
3
4
5
6
7
8
9
10
11
//屏蔽右键
BOOL CYXBrowser::PreTranslateMessage(MSG* pMsg)
{
// TODO: Add your specialized code here and/or call the base class
if(WM_RBUTTONDOWN == pMsg->message)
{
//AfxMessageBox(_T("Right Menu!"));
return TRUE;
}
return CWnd::PreTranslateMessage(pMsg);
}

四、隐藏网页的滚动条

这是最难处理的一个地方。不仅仅需要修改程序,而且需要web端的配合。

第一步:添加DocumentComplete事件响应。在C*Dlg的cpp中添加如下宏:

1
2
3
BEGIN_EVENTSINK_MAP(CClientBrowserDlg, CDialog)
ON_EVENT(CClientBrowserDlg, MY_IEBROWSER_ID, DISPID_DOCUMENTCOMPLETE, DocumentComplete, VTS_DISPATCH VTS_PVARIANT)
END_EVENTSINK_MAP()

注意,CClientBrowserDlg是响应函数所在的类,MY_IEBROWSER_ID是二中指定的浏览器窗口ID。DocumentComplete是CClientBrowserDlg中的响应该事件的成员函数。

第二步:实现该函数,直接贴代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
void CClientBrowserDlg::DocumentComplete(LPDISPATCH pDisp, VARIANT* URL)
{
UNUSED_ALWAYS(pDisp);
ASSERT(V_VT(URL) == VT_BSTR);

CString str(V_BSTR(URL));
m_pBrowser->OnDocumentComplete(str);
}

void CYXBrowser::OnDocumentComplete(LPCTSTR lpszURL)
{
m_bDocumentComplete = true;
HideScrollBar();
}

void CYXBrowser::HideScrollBar()
{
HRESULT hr;
IDispatch *pDisp = GetDocument();
IHTMLDocument2 *pDocument = NULL;
IHTMLElement* pEl;
IHTMLBodyElement *pBodyEl;

if (pDisp)
{
hr = pDisp->QueryInterface(IID_IHTMLDocument2, (void**)pDocument);
if (!SUCCEEDED(hr))
{
return;
}
}

if(pDocument SUCCEEDED(pDocument->get_body(pEl)))
{
if(pEl SUCCEEDED(pEl->QueryInterface(IID_IHTMLBodyElement, (void**)pBodyEl)))
{
pBodyEl->put_scroll(L"no");//去滚动条
}
IHTMLStyle *phtmlStyle;
pEl->get_style(phtmlStyle);

if(phtmlStyle != NULL)
{
phtmlStyle->put_overflow(L"hidden");
//需要设置网页源码DOCTYPE为<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
//去除边框才有效
phtmlStyle->put_border(L"none");// 去除边框

phtmlStyle->Release();
pEl->Release();
}
}
}

关键函数是HideScrollBar()。

第三步:在浏览器内嵌网页的最前面添加,

1
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

注意,第三步是不可缺少的。

五、屏蔽多次点击浏览器窗口的提示:”服务器正在运行中”要选择”切换到…”或”重试”的对话框**

在CClientBrowserDlg::OnInitDialog()中添加如下代码,

1
2
3
4
5
/*屏蔽掉"服务器正在运行中"要选择"切换到..."或"重试"的对话框*/
AfxOleGetMessageFilter()->EnableBusyDialog(FALSE);
AfxOleGetMessageFilter()->SetBusyReply(SERVERCALL_RETRYLATER);
AfxOleGetMessageFilter()->EnableNotRespondingDialog(TRUE);
AfxOleGetMessageFilter()->SetMessagePendingDelay(-1);

六、点击网页打开系统默认浏览器

第一步:绑定NEWWINDOW2事件。

1
ON_EVENT(CClientBrowserDlg, MY_IEBROWSER_ID, DISPID_NEWWINDOW2, OnNewWindow2, VTS_PDISPATCH VTS_PBOOL)

第二步:设置该OnNewWindow2的*bCancel为true,并且调用ShellExecute打开网页。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
void CClientBrowserDlg::OnNewWindow2(LPDISPATCH* ppDisp, BOOL* bCancel)
{
m_pBrowser->OnNewWindow2(ppDisp, bCancel);
}

void CYXBrowser::OnNewWindow2(LPDISPATCH* ppDisp, BOOL* bCancel)
{
*bCancel = TRUE;//禁止弹出新窗口(因为会使用IE弹窗)

HRESULT hr;
IDispatch *pDisp = GetDocument();
IHTMLDocument2 *pHTMLDocument2 = NULL;

if (pDisp)
{
hr = pDisp->QueryInterface(IID_IHTMLDocument2, (void**)pHTMLDocument2);
if (!SUCCEEDED(hr))
{
return;
}
}

if (pHTMLDocument2 != NULL)
{
CComPtr<IHTMLElement> pIHTMLElement;
pHTMLDocument2->get_activeElement(pIHTMLElement);

if (pIHTMLElement != NULL)
{
variant_t url;
hr = pIHTMLElement->getAttribute(L"href", 0, url);
if (SUCCEEDED(hr))
{
CString strURL(V_BSTR(url));
//打开默认浏览器
ShellExecute(m_hWndOwner, NULL, strURL, NULL, NULL, SW_NORMAL);
}
}
}
}

处理了六,五也就不需要了,因为点击网页不会再弹出IE浏览器了。

七、为网页元素的添加事件处理:比如web按钮的点击等

第一步:继承CCmdTarget新建类CHtmlEventHandle,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
#pragma once

#import <mshtml.tlb>

// CHtmlEventHandle command target
class CYXBrowser;

class CHtmlEventHandle : public CCmdTarget
{
DECLARE_DYNAMIC(CHtmlEventHandle)

public:
CHtmlEventHandle();
virtual ~CHtmlEventHandle();

public:
void SetWnd(CWnd* pWnd) { m_pWnd = pWnd;}
void SetWebBrowser(CYXBrowser* pWebBroswer) { m_pWebBrowser = pWebBroswer; }
// 消息处理函数
void OnClick(MSHTML::IHTMLEventObjPtr pEvtObj);

private:
CWnd* m_pWnd;
CYXBrowser* m_pWebBrowser;

protected:
DECLARE_MESSAGE_MAP()
DECLARE_DISPATCH_MAP()
DECLARE_INTERFACE_MAP()
};

// HtmlEventHandle.cpp : implementation file
//

#include "stdafx.h"
#include "ClientBrowser.h"
#include "HtmlEventHandle.h"
#include "mshtmdid.h"
#include "MsHTML.h"
#include "YXBrowser.h"

// CHtmlEventHandle

IMPLEMENT_DYNAMIC(CHtmlEventHandle, CCmdTarget)

CHtmlEventHandle::CHtmlEventHandle()
{
EnableAutomation(); // 重要:激活 IDispatch
}

CHtmlEventHandle::~CHtmlEventHandle()
{
}

BEGIN_MESSAGE_MAP(CHtmlEventHandle, CCmdTarget)
END_MESSAGE_MAP()

BEGIN_DISPATCH_MAP(CHtmlEventHandle, CCmdTarget)
DISP_FUNCTION_ID(CHtmlEventHandle, "HTMLELEMENTEVENTS2_ONCLICK",
DISPID_HTMLELEMENTEVENTS2_ONCLICK, OnClick,
VT_EMPTY, VTS_DISPATCH)
END_DISPATCH_MAP()

BEGIN_INTERFACE_MAP(CHtmlEventHandle, CCmdTarget)
INTERFACE_PART(CHtmlEventHandle,
DIID_HTMLButtonElementEvents2, Dispatch)
END_INTERFACE_MAP()

// CHtmlEventHandle message handlers

void CHtmlEventHandle::OnClick(MSHTML::IHTMLEventObjPtr pEvtObj)
{
MSHTML::IHTMLElementPtr pElement =
pEvtObj->GetsrcElement(); // 事件发生的对象元素
while(pElement) // 逐层向上检查
{
_bstr_t strId;
pElement->get_id(strId.GetBSTR());
if(_bstr_t(HTML_CLOSE_BUTTON) == strId)//响应关闭按钮点击
{
PostQuitMessage(0);
break;
}
else if (_bstr_t(HTML_SET_BUTTON) == strId)//30天不弹出设置
{

}

pElement = pElement->GetparentElement();
}
}
//注意那几个宏。宏的具体解释我没有去深究,仿照DISP_FUNCTION_ID可以为点击外的其它事件添加处理。

第二步:在CYXBrowser中注册这个web事件处理类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
//添加成员
private:
void InstallEventHandler();
void UninstallEventHandler();

private:
CHtmlEventHandle *m_pEventHandler;
DWORD m_dwDocCookie; // 用于卸载事件响应函数
IDispatch *m_pDispDoc; // 用于卸载事件响应函数
bool m_bDocumentComplete;

//相应的函数实现

// 安装响应函数。省略了一些失败判断以突出主要步骤
void CYXBrowser::InstallEventHandler()
{
if(m_dwDocCookie) // 已安装,卸载先。最后一次安装的才有效
UninstallEventHandler();

m_pDispDoc = GetDocument();
IConnectionPointContainerPtr pCPC = m_pDispDoc;
IConnectionPointPtr pCP;
// 找到安装点
pCPC->FindConnectionPoint(DIID_HTMLDocumentEvents2, pCP);
IUnknown* pUnk = m_pEventHandler->GetInterface(IID_IUnknown);
//安装
HRESULT hr = pCP->Advise(pUnk, m_dwDocCookie);
if(!SUCCEEDED(hr)) // 安装失败
m_dwDocCookie = 0;
}

// 卸载响应函数。省略了一些失败判断以突出主要步骤
void CYXBrowser::UninstallEventHandler()
{
if(0 == m_dwDocCookie) return;

IConnectionPointContainerPtr pCPC = m_pDispDoc;
IConnectionPointPtr pCP;
pCPC->FindConnectionPoint(DIID_HTMLDocumentEvents2, pCP);
HRESULT hr = pCP->Unadvise(m_dwDocCookie);
}

//在OnDocumentComplete中安装事件处理
CYXBrowser::CYXBrowser()
{
m_pEventHandler = new CHtmlEventHandle;
m_pEventHandler->SetWnd(m_pParent);
m_pEventHandler->SetWebBrowser(this);
m_dwDocCookie = 0; // 用于卸载事件响应函数
m_pDispDoc = NULL; // 用于卸载事件响应函数
m_bDocumentComplete = false;
}

void CYXBrowser::OnDocumentComplete(LPCTSTR lpszURL)
{
m_bDocumentComplete = true;
HideScrollBar();
InstallEventHandler();
}

//在OnBeforeNavigate2和OnDestroy中卸载处理
// 在 BeforeNavigate2 和 Destroy 事件中卸载响应函数
void CYXBrowser::OnBeforeNavigate2(LPCTSTR lpszURL, DWORD nFlags,
LPCTSTR lpszTargetFrameName, CByteArray baPostedData,
LPCTSTR lpszHeaders, BOOL* pbCancel)
{
UninstallEventHandler();
// 其他代码...
}

void CYXBrowser::OnDestroy()
{
UninstallEventHandler();
CWebBrowser2::OnDestroy();
}

现在就可以在void CHtmlEventHandle::OnClick(MSHTML::IHTMLEventObjPtr pEvtObj)函数内捕获网页按钮之类的点击了。处理代码的思路是从当前元素开始,不断往上查找父元素,直到匹配的元素ID为止。

八、判断url是否有效,如果无效则打开资源url,防止Web页面为空

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//使用该函数判断url是否能打开
bool CYXBrowser::IsUrlAvailable(CString strUrl)
{
CInternetSession* session = new CInternetSession();
CInternetFile* file = NULL;
bool bAvailable = true;

try
{
file = (CInternetFile*)session->OpenURL(strUrl);
}
catch (CInternetException*)
{
bAvailable = false;
}

delete session;
if (!file)
{
bAvailable = false;
}

return bAvailable;
}

//在对话框初始化时候,判断外网url是否能打开,如果不能则加载资源内的url
if (m_pBrowser->IsUrlAvailable(theApp.m_strUrl))
{
m_pBrowser->Navigate2(theApp.m_strUrl);
}
else
{
TCHAR szModule[MAX_PATH];
GetModuleFileName(theApp.m_hInstance, szModule, MAX_PATH);
theApp.m_strUrl.Format(_T("res://%s/%s"), szModule, theApp.m_strLocalUrl);
m_pBrowser->Navigate2(theApp.m_strUrl);
}

注意资源url的格式是res://模块名/网页名,因此需要在该程序中导入自定义的资源,并且将其命名为theApp.m_strLocalUrl代表的字符串值,比如”NZ.HTML”。

这是我的毕业论文题目,两年前的东西了,来出来纪念下吧。后面附演讲pdf。

这讲的是在一个三维模型上手绘纹理的故事,纹理是用户选择的样本纹理。因此,实际上我的毕业论文就是做了一个上纹理的工具。用户选择不同的模型,同时选择不同的样本纹理(符合马尔科夫随机场的样本纹理),就可以用鼠标在模型上刷三维纹理了。

看起来挺好玩的,实际上整个系统的实现过程非常繁杂。这个系统主要用了两个算法,一个是扩展指数映射计算笔画的局部参数化,另外一个是根据样本纹理动态合成新的大纹理(要贴到笔画区域),这个用了并行可控制纹理合成算法,在约束条件下做了特殊实现。由于扩展指数映射用的是他人的实现代码,严重影响了整个系统的速度,也影响了我后面做实验的效果,非常恶心,所以不到无奈时候,千万不要用所谓的牛人的资源。

系统流程:

最终效果:

PDF下载:三维表面上基于笔画的纹理交互式合成

预览如下:

记得以前学汇编和PE文件的时候知道,系统不会直接调用我们编写的main,而是调用指定的入口地址。实际上这个入口地址,是在链接时候指定的,MS C++中使用链接命令/entry:function可以修改默认设置。

那么,默认情况下,我们使用VC编写的应用程序使用的是什么入口函数了?

函数 默认
mainCRTStartup (or wmainCRTStartup) An application using /SUBSYSTEM:CONSOLE; calls main (or wmain)
WinMainCRTStartup (or wWinMainCRTStartup) An application using /SUBSYSTEM:WINDOWS; calls WinMain (or wWinMain), which must be defined with __stdcall

注意,区分入口函数和主函数(main,WinMain)。

默认情况下,控制台程序使用mainCRTStartup作为入口函数,窗口程序使用WinMainCRTStartup作为入口函数。同时,这两个函数都有对应的Unicode版本(前缀加w)。

现在要考虑的是,这些启动函数都做了什么事情?

在crtexe.c文件中可以找到这几个启动函数的定义,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#ifdef _WINMAIN_

#ifdef WPRFLAG
int wWinMainCRTStartup(
#else /* WPRFLAG */
int WinMainCRTStartup(
#endif /* WPRFLAG */

#else /* _WINMAIN_ */

#ifdef WPRFLAG
int wmainCRTStartup(
#else /* WPRFLAG */
int mainCRTStartup(
#endif /* WPRFLAG */

#endif /* _WINMAIN_ */
void
)
{
/*
* The /GS security cookie must be initialized before any exception
* handling targetting the current image is registered. No function
* using exception handling can be called in the current image until
* after __security_init_cookie has been called.
*/
__security_init_cookie();

return __tmainCRTStartup();
}

因此,实际上是根据平台(Windows或者Console),多字节还是Unicode,生成不同的默认入口函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
__declspec(noinline)
int
__tmainCRTStartup(
void
)
{
int initret;
int mainret=0;
int managedapp;
#ifdef _WINMAIN_
_TUCHAR *lpszCommandLine;
STARTUPINFO StartupInfo;

__try {
/*
Note: MSDN specifically notes that GetStartupInfo returns no error, and throws unspecified SEH if it fails, so
the very general exception handler below is appropriate
*/
GetStartupInfo( amp;StartupInfo );
} __except(EXCEPTION_EXECUTE_HANDLER) {
return 255;
}
#endif /* _WINMAIN_ */
/*
* Determine if this is a managed application
*/
managedapp = check_managed_app();

if ( !_heap_init(1) ) /* initialize heap */
fast_error_exit(_RT_HEAPINIT); /* write message and die */

if( !_mtinit() ) /* initialize multi-thread */
fast_error_exit(_RT_THREAD); /* write message and die */

/* Enable buffer count checking if linking against static lib */
_CrtSetCheckCount(TRUE);

/*
* Initialize the Runtime Checks stuff
*/
#ifdef _RTC
_RTC_Initialize();
#endif /* _RTC */
/*
* Guard the remainder of the initialization code and the call
* to user's main, or WinMain, function in a __try/__except
* statement.
*/

__try {

if ( _ioinit() lt; 0 ) /* initialize lowio */
_amsg_exit(_RT_LOWIOINIT);

/* get wide cmd line info */
_tcmdln = (_TSCHAR *)GetCommandLineT();

/* get wide environ info */
_tenvptr = (_TSCHAR *)GetEnvironmentStringsT();

if ( _tsetargv() lt; 0 )
_amsg_exit(_RT_SPACEARG);
if ( _tsetenvp() lt; 0 )
_amsg_exit(_RT_SPACEENV);

initret = _cinit(TRUE); /* do C data initialize */
if (initret != 0)
_amsg_exit(initret);

#ifdef _WINMAIN_

lpszCommandLine = _twincmdln();
mainret = _tWinMain( (HINSTANCE)amp;__ImageBase,
NULL,
lpszCommandLine,
StartupInfo.dwFlags amp; STARTF_USESHOWWINDOW
? StartupInfo.wShowWindow
: SW_SHOWDEFAULT
);
#else /* _WINMAIN_ */
_tinitenv = _tenviron;
mainret = _tmain(__argc, _targv, _tenviron);
#endif /* _WINMAIN_ */

if ( !managedapp )
exit(mainret);

_cexit();

}
__except ( _XcptFilter(GetExceptionCode(), GetExceptionInformation()) )
{
/*
* Should never reach here
*/

mainret = GetExceptionCode();

if ( !managedapp )
_exit(mainret);

_c_exit();

} /* end of try - except */

return mainret;
}

从上面代码,可以很清晰了解启动函数到底做了什么事情。

Console版本

1.初始化C的堆申请(_heap_init(1))

2.初始化多线程(_mtinit())

3.获取命令行(GetCommandLineT())

4.获取环境变量(GetEnvironmentStringsT())

5.初始化C和C++的全局变量(_cinit(TRUE))

6.调用main函数(_tmain(__argc, _targv, _tenviron))

Windows版本

1.获取StartupInfo(GetStartupInfo( StartupInfo ))

2.初始化C的堆申请(_heap_init(1))

3.初始化多线程(_mtinit())

4.获取命令行(GetCommandLineT())

5.获取环境变量(GetEnvironmentStringsT())

6.初始化C和C++的全局变量(_cinit(TRUE))

7.调用WinMain函数

最后再看看_cinit(TRUE)到底做了什么事情?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
int __cdecl _cinit (
int initFloatingPrecision
)
{
int initret;

/*
* initialize floating point package, if present
*/
#ifdef CRTDLL
_fpmath(initFloatingPrecision);
#else /* CRTDLL */
if (_FPinit != NULL amp;amp;
_IsNonwritableInCurrentImage((PBYTE)amp;_FPinit))
{
(*_FPinit)(initFloatingPrecision);
}
_initp_misc_cfltcvt_tab();
#endif /* CRTDLL */

/*
* do initializations
*/
initret = _initterm_e( __xi_a, __xi_z );
if ( initret != 0 )
return initret;

#ifdef _RTC
atexit(_RTC_Terminate);
#endif /* _RTC */
/*
* do C++ initializations
*/
_initterm( __xc_a, __xc_z );

#ifndef CRTDLL
/*
* If we have any dynamically initialized __declspec(thread)
* variables, then invoke their initialization for the thread on
* which the DLL is being loaded, by calling __dyn_tls_init through
* a callback defined in tlsdyn.obj. We can't rely on the OS
* calling __dyn_tls_init with DLL_PROCESS_ATTACH because, on
* Win2K3 and before, that call happens before the CRT is
* initialized.
*/
if (__dyn_tls_init_callback != NULL amp;amp;
_IsNonwritableInCurrentImage((PBYTE)amp;__dyn_tls_init_callback))
{
__dyn_tls_init_callback(NULL, DLL_THREAD_ATTACH, NULL);
}
#endif /* CRTDLL */

return 0;
}

最重要的两行,initret = _initterm_e( xi_a, xi_z )和_initterm( xc_a, xc_z )。这两行的作用分别是初始化C标准库中的全局变量和初始化C++的全局变量。

至此,对C++程序的默认启动函数的基本过程有个了解了。

1.发布博客软件:Windows Live Write,可以参考我的WindowsLiveWrite博文

2.Windows7/Xp上显示TGA图片的缩略图:MysticThumbs,可以参考我的MysticThumbs博文

3.记笔记:印象笔记(Evernote),不用介绍了,任何设备上都可以用(电脑,手机,ipad…)。

4.注册表浏览器:registryworkshop,比Windows默认的注册表舒服很多。

5.破解工具:TrialReset。该软件可以清除很多软件的保护信息,比如visual assist X,我就用它清楚过VA的保护信息。搜索该软件会得到一些介绍,单纯的清除信息操作很简单,清除后相当于无限延长试用期。试试会得到你意想不到的结果。

6.阅读软件:calibre。calibre是一个非常优秀的书籍管理软件。当然,我找到它的原因是它可以非常方便的转mobi,epub等。这样就可以将任意格式的书籍放到kindle或者多看阅读器(支持多种设备,手机,ipad,kindle等)上面去看了。这对于用阅读打法无聊时光的我,非常有用。calibre还可以在PC上当做书籍管理和阅读软件,我正在用它看epub格式的书籍了,因为多看阅读器不支持Windows平台。更多功能我就不知道了,有待挖掘。

7.文件内容搜索工具:FileSeek。这款软件能够下载到破解版,我现在主要用来搜索as代码(逆战的界面使用的是ScaleForm开发的,需要写as代码)。Windows自带的搜索功能对于文件内容搜索确实不方便额。

持续更新…

我使用的是windows下的最新版CTex,版本号是CTeX_2.9.2.164_Full.exe。注意使用full版本,如果不是,可能不支持beamer。beamer是一个流行的演讲文档模板,有多种主题,效果也还不错。

我在尝试beamer的过程中,发现网上大部分例子都不支持中文,或者不支持我使用的环境下的中文,即windows下的最新版CTex。折腾了几天,试了很多个模板,发现xeCJK能够完美解决这个问题,因为很多老的模板都是用的CJK,我将其中一个模板改成xeCJK就支持中文了。
下面给出这个模板的设置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
\documentclass{beamer}
\usetheme{Warsaw}
\usepackage{fontspec,xunicode,xltxtra}
\usepackage[slantfont,boldfont]{xeCJK} % 允许斜体和粗体
\setbeamercovered{transparent}
\usepackage[english]{babel}
% or whatever
\usepackage{hyperref}
\usepackage[T1]{fontenc}
% or whatever
\usefonttheme{professionalfonts}
\usepackage{times}
\usepackage{mathptmx}
\usepackage{tabularx}
% Or whatever. Note that the encoding and the font should match. If T1
% does not look nice, try deleting the line with the fontenc.
\usepackage{xcolor}
\usepackage{booktabs, multirow, enumerate}
\usepackage{animate}
\usepackage{multimedia}

% ... or whatever. Note that the encoding and the font should match.
% If T1 does not look nice, try deleting the line with the fontenc.
\usepackage{lmodern} %optional
\usepackage{listings}

% Delete this, if you do not want the table of contents to pop up at
% the beginning of each subsection:
\AtBeginSection[]
{
\begin{frame}<beamer>
\frametitle{内容大纲}
\tableofcontents[currentsection]
\end{frame}
}

\setCJKmainfont{Microsoft YaHei} % 设置缺省中文字体
\setCJKmonofont{SimSun} % 设置等宽字体
\setmainfont{TeX Gyre Pagella} % 英文衬线字体
\setmonofont{Microsoft YaHei} % 英文等宽字体
\setsansfont{Trebuchet MS} % 英文无衬线字体

\begin{document}

\title[***纹理***]% optional, use only with long paper titles
{ ***纹理***\\[2ex]}

%\subtitle[malloc] %optional
%{malloc\ 实现}

\author[yx] % optional, use only with lots of authors
{
\textcolor[rgb]{0.00, 0.41, 0.66}{yx}
}

% the titlepage
% the plain option removes the sidebar and header from the title page
\begin{frame}[plain]
\titlepage
\end{frame}
%%%%%%%%%%%%%%%%

\begin{frame}
\frametitle{内容大纲}
\tableofcontents
\end{frame}

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\section{研究背景和意义}
%%%%%%%%%%%%%%%%
\begin{frame}{纹理的作用}{}
\begin{block}<1->{}
纹理是图形学中增强真实性的重要手段。
\end{block}
\begin{block}<2->{}
应用纹理到三维表面能够有效的表示物体表面的颜色,材质,几何等属性。
\end{block}
\end{frame}
%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\end{document}

值得注意的是,请用utf8来保存该文档,并且在WinEdit中用utf8来打开,否则还是可能看到乱码的。
boats against the current, borne back ceaselessly into the past.