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.

今天逆战发布新版了。我做的新军衔系统和转生系统在这次上线了。哈哈,好有成就感。第一次写的代码能够影响这么多人的生活。听说玩家都挤在主城转生NPC处了。

新军衔系统:

转生系统:

在逆战的界面开发中,一般只需要从uc往flash中单向传输数据,因此这里只总结从uc->flash的数据流动方法。flash->uc的数据流动,其实有相对应的方式,比如SetVariableBool函数有对应的GetVariableBool函数。

在unrealscript中,使用scaleform的界面类,都继承自GfxMoviePlayer。

一、传输变量

native function SetVariableString(string path, string s);

native function SetVariableBool(string path, bool b);

native function SetVariableNumber(string path, float f);

我在GfxUIMovie中没找到专门设置int的函数,估计可以用SetVariableNumber替代。关键是理解path的设置,比如对应flash文件的名称是ReBornGuide。那么path可能是ReBornGuide.str或者_root.ReBornGuide.str,具体跟ue3项目的一些设置有关。

这几个函数对应的cpp声明如下:

String:SetVariableString(const FString path, const FString s);

Bool:SetVariableBool(const FString path, UBool b);

Float:SetVariableNumber(const FString path, float f);

有趣的是,这几个函数的字符串参数都是引用,这样恰恰说明了在uc中,字符串是引用类型。

例子:

1
2
3
4
5
6
test.uc中:
String strTest = "测试";
String path = "_root.TestFlash.strTest";
SetVariableString(path,strTest);
TestFlash.as中:
var strTest:String;

二、传输数组

native function bool SetVariableStringArray(string path, int index, array Arg);

native function bool SetVariableFloatArray(string path, int index, array Arg);

native function bool SetVariableIntArray(string path, int index, array Arg);

其对应的cpp实现

String:SetVariableStringArray(const FString path, INT Index,const TArray arg);

Float:SetVariableFloatArray(const FString path, INT Index,const TArray arg);

Int:SetVariableFloatArray(const FString path, INT Index,const TArray arg);

这里Index表示从数组的第几个元素开始,0表示传输全部,arg是要传输的数组。

例子:

1
2
3
4
5
6
7
8
test.uc中:
local array<String> strArray;
strArray.AddItem("0");
strArray.AddItem("1");
String path = "_root.TestFlash.strTest";
SetVariableStringArray(path,strArray);
TestFlash.as中:
var strArray:Array = new Array();

三、传输复合对象(结构体)

当需要传输整体的数据,或者传输多个数据的时候,可以将数据组合成一个结构体。假设在uc中定义了如下结构体,

1
2
3
4
5
6
7
8
9
struct RankData
{
var string Name;
var int Rank;
var double d;
var float f;
var bool b;
};
RankData RD;

我们想将其一次性传入as中定义的Object对象,var RD:Object。这里我们可以使用SetVariableObject函数。其定义如下:

native function SetVariableObject(string path, GFxObject Object);

下面的例子很好说明了如何使用该函数。

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
uc中:
struct RankData
{
var string Name;
var int Rank;
var double d;
var float f;
var bool b;
};

function SetFlashVariables()
{
local GFxObject GfxObj;
local RankData RD;
local string path;

RD.Name = "test";
RD.Rank = 1;
RD.d = 1.0;
RD.f = 1.0;
RD.b = True;
GfxObj = CreateObject("Object");
GfxObj.SetString("Name", RD.Name);
GfxObj.SetInt("Rank", RD.Rank);
GfxObj.SetDouble("d", RD.d);
GfxObj.SetFloat("f", RD.f);
GfxObj.SetBool("b", RD.b);

path = "_root.TestFlash.RD"
SetVariableObject(path, GfxObj);
GfxObj.DestroyObject();
}

TestFlash.as中:
var RD:Object = new Object();
//在uc中调用了SetFlashVariables函数后,as中就能获得RD的值。
trace(RD.Name);
trace(RD.Name);
trace(RD.f);
trace(RD.d);
trace(RD.b);

从上面代表可以看出,GFxObject可以代表一个符号变量,可以设置一个GFxObject的多个属性,然后一次性将这个对象传入flash。GFxObject.uc中有如下Set变量的函数:

1
2
3
4
5
6
native final function SetBool(string Member, bool b);
native final function SetFloat(string Member, float f);
native final function SetDouble(string Member,double d);
native final function SetInt(string Member,int i);
native final function SetString(string Member, string s);
native final function SetObject(string Member, GFxObject val);

因此,可以看出可以设置GFxObject各种类型的属性。关键的是还可以设置其GFxObject类型的属性,从而可以嵌套定义。比如结构体中定义另一结构体成员。当需要将多次传输组合为一次时候,也可以使用这种方式,并不需要一定定义结构体。

四、传输结构体数组

假设as中定义如下数组,var RankListDatas:Array = new Array();那么可以用下面代码传输:

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
//军衔列表每项的数据
struct RankListData
{
var int Level;//等级
var string Name;//军衔名
};
var array<RankListData> RankListDatas;
var string path;
function SetFlashRankListDatas()
{
local int Index;
local GFxObject GfxRankListDatas;
local GFxObject GfxTempObj;

GfxRankListDatas = CreateArray();
for (Index = 0; Index < RankDatas.Length; Index++)//第0项是多余的
{
GfxTempObj = CreateObject("Object");
GfxTempObj.SetInt("Level", RankListDatas[Index].Level);
GfxTempObj.SetString("Name", RankListDatas[Index].Name);
GfxRankListDatas.SetElementObject(Index, GfxTempObj);
GfxTempObj.DestroyObject();
}

SetVariableObject(path, GfxRankListDatas);
GfxRankListDatas.DestroyObject();
}

从代码里面可以看到,还是用SetVariableObject传输数据。不过,需要用CreateArray()创建GFxObject对象。在as代码中RankListDatas[0].Name就可以访问第一个元素的Name属性了。

GfxRankListDatas.SetElementObject(Index, GfxTempObj)则是将GfxTempObj设置为GfxRankListDatas代表的数组的第Index个元素。

五、传输结构体数组的数组(结构体内部复合结构体数组)

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
struct AwardData
{
var string IconSource;
};

struct RankAwardData
{
var string RankAwardTitle;
var array<AwardData> AwardDatas;
};
var array<RankAwardData> RankAwardDatas;
var string path;

function SetFlashRankAwardDatas()
{
local int Index;
local int i;
local GFxObject GfxRankAwardDatas;
local GFxObject GfxTempObj;
local GFxObject GfxAwardDatas;
local GFxObject GfxAwardTempObj;

GfxRankAwardDatas = CreateArray();
for (Index = 0; Index < RankAwardDatas.Length; Index++)
{
GfxTempObj = CreateObject("Object");
GfxTempObj.SetString("RankAwardTitle", RankAwardDatas[Index].RankAwardTitle);

GfxAwardDatas = CreateArray();
for (i = 0; i < RankAwardDatas[Index].AwardDatas.Length; ++i)
{
GfxAwardTempObj = CreateObject("Object");
GfxAwardTempObj.SetString("IconSource", RankAwardDatas[Index].AwardDatas[i].IconSource);
GfxAwardDatas.SetElementObject(i, GfxAwardTempObj);
GfxAwardTempObj.DestroyObject();
}
GfxTempObj.SetObject("RankAward", GfxAwardDatas);
GfxRankAwardDatas.SetElementObject(Index, GfxTempObj);
GfxAwardDatas.DestroyObject();
GfxTempObj.DestroyObject();
}

SetVariableObject(path, GfxRankAwardDatas);
GfxRankAwardDatas.DestroyObject();
}

从上面代码可以看出,调用GfxTempObj.SetObject(“RankAward”, GfxAwardDatas)可以设置GFxObject对象的GFxObject属性,从而能够传输复合结构体类型的数组。假设as中对应的数组定义为var RankAwardDatas:Array=new Array();那么,在as代码中RankAwardDatas[0].RankAward.IconSource则能访问该数组第一个元素的结构体成员的IconSource属性。