c语言windows.h程序问题

c语言windows.h程序问题,第1张

恩,想用C语言做windows窗口界面的话,肯定是要调用windows api函数来完成,而大多数API涵数都在<windows.h>中有声明,所以你要包含它,但是windows程序的运行机制和你平时用C写的控制台程序很不一样,我给出一个例子,你可以参考一下:

/****************************************************************************

* *

* File: main.c *

* *

* Purpose : Generic Win32 application. *

* *

* History : Date Reason *

* 00/00/00 Created *

* *

****************************************************************************/

#define WIN32_LEAN_AND_MEAN /* speed up compilations */

#include <windows.h>

#include <windowsx.h>

#include <commctrl.h>

#include <tchar.h>

#include "main.h"

#define NELEMS(a) (sizeof(a) / sizeof((a)[0]))

/** Prototypes **************************************************************/

static LRESULT WINAPI MainWndProc(HWND, UINT, WPARAM, LPARAM)

static void Main_OnPaint(HWND)

static void Main_OnCommand(HWND, int, HWND, UINT)

static void Main_OnDestroy(HWND)

static LRESULT WINAPI AboutDlgProc(HWND, UINT, WPARAM, LPARAM)

/** Global variables ********************************************************/

static HANDLE ghInstance

/****************************************************************************

* *

* Function: WinMain*

* *

* Purpose : Initialize the application. Register a window class, *

* create and display the main window and enter the *

* message loop. *

* *

* History : Date Reason *

* 00/00/00 Created *

* *

****************************************************************************/

int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow)

{

INITCOMMONCONTROLSEX icc

WNDCLASS wc

HWND hwnd

MSG msg

ghInstance = hInstance

/* Initialize common controls. Also needed for MANIFEST's */

/*

* TODO: set the ICC_???_CLASSES that you need.

*/

icc.dwSize = sizeof(icc)

icc.dwICC = ICC_WIN95_CLASSES /*|ICC_COOL_CLASSES|ICC_DATE_CLASSES|ICC_PAGESCROLLER_CLASS|ICC_USEREX_CLASSES|... */

InitCommonControlsEx(&icc)

/* Load Rich Edit control support */

/*

* TODO: uncomment one of the lines below, if you are using a Rich Edit control.

*/

// LoadLibrary(_T("riched32.dll")) // Rich Edit v1.0

// LoadLibrary(_T("riched20.dll")) // Rich Edit v2.0, v3.0

/*

* TODO: uncomment line below, if you are using the Network Address control (Windows Vista+).

*/

// InitNetworkAddressControl()

/* Register the main window class */

wc.lpszClassName = _T("aaClass")

wc.lpfnWndProc = MainWndProc

wc.style = CS_OWNDC|CS_VREDRAW|CS_HREDRAW

wc.hInstance = ghInstance

wc.hIcon = LoadIcon(ghInstance, MAKEINTRESOURCE(IDR_ICO_MAIN))

wc.hCursor = LoadCursor(NULL, IDC_ARROW)

wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1)

wc.lpszMenuName = MAKEINTRESOURCE(IDR_MNU_MAIN)

wc.cbClsExtra = 0

wc.cbWndExtra = 0

if (!RegisterClass(&wc))

return 1

/* Create the main window */

hwnd = CreateWindow(_T("aaClass"),

_T("aa Program"),

WS_OVERLAPPEDWINDOW|WS_HSCROLL|WS_VSCROLL,

0,

0,

CW_USEDEFAULT,

CW_USEDEFAULT,

NULL,

NULL,

ghInstance,

NULL

)

if (!hwnd) return 1

/* Show and paint the main window */

ShowWindow(hwnd, nCmdShow)

UpdateWindow(hwnd)

/* Pump messages until we are done */

#if 0

/* "Politically correct" code -- SEE MICROSOFT DOCUMENTATION */

for ()

{

BOOL fRet = GetMessage(&msg, NULL, 0, 0)

if (fRet == -1) /* Error */

{

/* TODO: handle the error from GetMessage() */

__debugbreak()

return -1

}

else if (fRet == 0) /* WM_QUIT */

{

break

}

else /* Not error or WM_QUIT */

{

TranslateMessage(&msg)

DispatchMessage(&msg)

}

}

#else

/* "Commonly seen" code */

while (GetMessage(&msg, NULL, 0, 0))

{

TranslateMessage(&msg)

DispatchMessage(&msg)

}

#endif

return msg.wParam

}

/****************************************************************************

* *

* Function: MainWndProc*

* *

* Purpose : Process application messages. *

* *

* History : Date Reason *

* 00/00/00 Created *

* *

****************************************************************************/

static LRESULT CALLBACK MainWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)

{

switch (msg)

{

HANDLE_MSG(hwnd, WM_PAINT, Main_OnPaint)

HANDLE_MSG(hwnd, WM_COMMAND, Main_OnCommand)

HANDLE_MSG(hwnd, WM_DESTROY, Main_OnDestroy)

/* TODO: enter more messages here */

default:

return DefWindowProc(hwnd, msg, wParam, lParam)

}

}

/****************************************************************************

* *

* Function: Main_OnPaint *

* *

* Purpose : Process a WM_PAINT message.*

* *

* History : Date Reason *

* 00/00/00 Created *

* *

****************************************************************************/

static void Main_OnPaint(HWND hwnd)

{

PAINTSTRUCT ps

RECT rc

BeginPaint(hwnd, &ps)

GetClientRect(hwnd, &rc)

DrawText(ps.hdc, _T("Hello, Windows!"), -1, &rc, DT_SINGLELINE|DT_CENTER|DT_VCENTER)

EndPaint(hwnd, &ps)

}

/****************************************************************************

* *

* Function: Main_OnCommand *

* *

* Purpose : Process a WM_COMMAND message. *

* *

* History : Date Reason *

* 00/00/00 Created *

* *

****************************************************************************/

static void Main_OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify)

{

switch (id)

{

case IDM_ABOUT:

DialogBox(ghInstance, MAKEINTRESOURCE(DLG_ABOUT), hwnd, (DLGPROC)AboutDlgProc)

/* TODO: Enter more commands here */

}

}

/****************************************************************************

* *

* Function: Main_OnDestroy *

* *

* Purpose : Process a WM_DESTROY message. *

* *

* History : Date Reason *

* 00/00/00 Created *

* *

****************************************************************************/

static void Main_OnDestroy(HWND hwnd)

{

PostQuitMessage(0)

}

/****************************************************************************

* *

* Function: AboutDlgProc *

* *

* Purpose : Process messages for the About dialog. The dialog is *

shown when the user selects "About" in the "Help" menu.*

* *

* History : Date Reason *

* 00/00/00 Created *

* *

****************************************************************************/

static LRESULT CALLBACK AboutDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)

{

switch (uMsg)

{

case WM_INITDIALOG:

/*

* Nothing special to initialize.

*/

return TRUE

case WM_COMMAND:

switch (wParam)

{

case IDOK:

case IDCANCEL:

/*

* OK or Cancel was clicked, close the dialog.

*/

EndDialog(hDlg, TRUE)

return TRUE

}

break

}

return FALSE

}

这就是windows程序的基本框架

那就是你的GetAnnexbNALU函数发出来的异常,设个断点进去看看为什么报错了?是不是读写的长度有问题?

不行的话,看看能否把别的 *** 作屏蔽掉,就只运行有问题的地方,以测试是不是真的有问题。

1. 在开发环境下定位Crash错误

1.1 普通的crash

先来看看最普通的crash

参见图1(c01.png)

当你在debug模式下运行上面的程序就会d出上面的框。vc就帮你定位到了错误的位置。是个对零指针的 *** 作。非常简单,不是吗。

1.2 较难定位的crash

较难定位的crash往往是由于内存错误(参见5.1 为什么程序crash时调用堆栈是乱的)。例如以下代码

代码

char *p = new char[16]

p[10] = 0xfd

delete[] p

printf(p)

以上代码有两处错误,一是第2行的内存写越界,二是第4行使用被删除的指针。

但以上代码在vc的release和debug下都不会报错。这使得这类错误很难定位。

检测这一类问题可以使用BoundsChecker工具的FinalCheck模式(BoundsChecker)

用BoundsChecker检测后可得到两个错误:Write overrun(写越界) 和 Dangling pointer(使用被删除的指针)而且都精确定位到了出错的位置。是个不错的工具。

参见图2(c02.png)

参见图3(c03.png)

1.3 注意vc的输出日志

由于一些目前未知的原因(有可能是程序的错误太严重或是BoundsChecker本身的bug),BoundsChekcer有时不能正常工作。

这里vc的输出日志有时能提供一些有用的信息。

在难找的crash中,有很大一部分是引用了非法的指针。

有时在vc的输出日志里可以看到类似于这样的信息

“emule.exe 中的 0x004277b7 处最可能的异常: 0xC0000005: 读取位置 0xfeeeff62 时发生访问冲突 。”

在缺少BoundsChecker的支持时,这是一条很重要的信息。意思是说在“程序地址0x004277b7处”对“值为0xfeeeff62的指针”进行 *** 作。

(怎么通过“程序地址0x004277b7”找到对应的代码行可参照 3.1,)

这条信息的重要性在于,这个 *** 作只会触发一个警告,而不会导致crash,当crash真正发生时,很有可能不会在0x004277b7附近,

甚至调用堆栈都已经被写乱,让你无从下手。(参见5.1 为什么程序crash时调用堆栈是乱的)

2. 定位发布在外的版本的Crash错误

发布在外的软件crash了,往往不好调试,所以目前很多软件都有“发送错误报告”这一功能。

实现这一功能一般分以下几步:

a. 使用SetUnhandledExceptionFilter函数

使用SetUnhandledExceptionFilter设置最高一级的异常处理函数,当程序出现任何未处理的异常,都会触发你设置的函数里。具体使用可参照msdn和emule源码。

b. 使用MiniDumpWriteDump函数

在你的异常处理函数里,使用MiniDumpWriteDump把错误信息存成特定格式的文件。具体使用可参照msdn和emule源码。

c. 发送错误报告

选用一种形式把第二步产生的错误报告(.dmp)文件发送给你指定的地方。

d. 查看错误报告

这里介绍用vc查看错误报告的方法,还可以用windows debug tools这个工具看,方法见5.2 使用windows debug tools查看.dmp文件(错误报告)

查看错误报告需要有三样东西:对应release版的代码,当时编译release版所产生的.exe和.pdb文件。(这两个文件都在编译的输出目录里。)所以当程序发布时,要保留下这两个文件。

把.dmp(错误报告文件), .pdb, .exe. 代码,在同一目录下,用vc打开.dmp 文件。

按F5运行,程序即到达crash时的状态,可以对其进行相应的分析。

一点补充:当没有“发送错误报告”的功能,或是此功能失效,以致d出了windows的“发送错误报告”的对话框。这时其实也是有错误报告的,一般在C:Documents and Settings用户名Local SettingsTemp里的一个.dmp文件(一般只有一个.dmp)

3. 小技巧

3.1 根据程序地址找到代码位置

可按如下步骤:

a. 使程序处于停止状态。(比如程序运行时,在vc里按Ctrl+Alt+Break,或设断点使程序停下)

b. 切换到汇编状态。(Ctrl+F11)

c. 在地址栏输入程序地址,回车。

d. 可按Ctrl+F11切回代码模式。

3.2 根据消息值查看对应的windows消息

在vc的监视窗口里输入“消息值,wm”即可看到对应的消息。

3.3 查看GetLastError返回值

在vc的监视窗口里输入“@err,hr”,即可看到LastError及其解释。

3.4 在代码中暂停程序

在debug版中可以在代码中加上“AfxDebugBreak();”以暂停程序。release版可使用 “_asm int 3”

4. 编程小警示

4.1 慎用IsBadPtr系列函数

当使用IsBadReadPtr, IsBadWritePtr, IsBadCodePtr一系列函数时要注意,这一类函数可能并不能达到你所想要的意图。

比如下面的代码,两个返回都是false。

代码

char *p = new char[10]

bool b

b = IsBadReadPtr(p+10, 1)

delete[] p

char *q = new char[10]

b = IsBadReadPtr(p, 1)

所以切忌在程序中以IsBadPtr函数来判断是否可以对这个指针进行 *** 作。这些函数只能在调试中使用,或是你确切的知道这些函数的返回值表示的是什么意义的时候。

4.2 慎用catch(...)

为了防止程序crash或是解决一个不明白的crash时,大家很容易想到一个 try{}catch(...){}来解决问题。

的确大部分时间这样不会出问题了,但这个try-catch很有可能隐藏掉在try里面的错误,而当由此错误引起其他错误时,就很难追踪到这个问题了。

所以建议尽量少用catch(...),如果知道某一块代码会抛出异常,应该用确切的写法。比如catch(CFileException *e), catch(int e)等等。

5. 附录

5.1 为什么程序crash时调用堆栈是乱的

当内存被写乱时程序很有可能出现很难定位的crash,比如调用堆栈是乱的,或走到不存在的代码里。这里举一例

代码

class CA

{

public:

CA(){}

~CA(){}

virtual f(){}

}

void show()

{

printf("shown")

}

int main()

{

// 对像被创建删除

CA *p = new CA

delete p

// 一些正常的 *** 作

int *q = new int

int codeAddress = (int)show

*q = (int)&codeAddress

// 调用被删除的对像,程序有可能执行到任何地方。

p->f()

}

上面代码的结果会走到show()里显示出show(这跟编译环境有关,vc2003下测试结果是这样)。如果你了解c++的vtable机制就明白这是怎么回事。

如果不明白也没关系,我下面说个大概。

首先CA对象被创建又被删除。如果此时调用p->f()多半会crash。

第二块代码可视为一些正常的 *** 作,new了一个q,然后对它进行了一些赋值。

如果你不明白vtable机制,那你只要知道,最后一行的 “p->f()”会执行变量q所指向的变量所指向的变量所标示的地址。(这里没打错字,是两个“所指向的变量”)

这里我“心地善良”的给这个值赋上了一个合法的函数地址show。但实际程序中q所指向的变量有可能是任意值,它再指向的变量就更是任意值了。

那程序就不知道跑哪里去了。如果这个值过于离谱,那算你运气好,程序会立即crash,而你就知道错误位置在哪了。但讨人厌的是,它有可能是一个合法地址,那程序就继续走下去,

但迟早会crash,并且调用堆栈面目全非(原因牵涉到“调用堆栈的推导”的问题这里就不多说了),

到时就根本无从知道原来是调用了被删除的p对象而导致的。

5.2 使用Debugging tools for windows查看.dmp文件(错误报告)

a. 准备好程序对应的代码,exe文件,pdb文件(编译时在编译输出目录里)

b. 安装WinDbg

c. 在winDbg里把Symbol目录设在.pdb所在目录,Image目录设在.exe所在目录,code目录设到代码目录。

d. 打开.dmp文件

e. 输入命令.ecxr。(此命令使环境回到崩溃时的状态)

f. 打开调用堆栈(ALT + F6)查看Crash的位置

g. 进行分析

简介

(FinalCheck能检测出的错误列表见附录1)

BoundsChecker是一个很强大的调试工具。这里只简单介绍如何用它的FinalCheck模式定位比较难定位的错误。

FinalCheck模式简单来说就是BoundsChecker在你的代码里加一些诊断代码来检查平时比较难查出的内存越界,错误的指针使用等。

不过付出的代价就是程序跑起来会比较慢,所以在不用时最好是把FinalCheck模式关掉。特别是发布前。


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/yw/8128147.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-04-13
下一篇 2023-04-13

发表评论

登录后才能评论

评论列表(0条)

保存