2009年5月21日星期四

wxWidgets

//change log out to textcontrol
wxLog *logger=new wxLogTextCtrl(TextCtrlOut);
wxLog::SetActiveTarget(logger);

//wxtring cmp
(TextCtrl2->GetValue().Cmp(wxT("text"))==0)

//Close dialog
EndModal(int)
Int is showmodal return code.

// Give the frame an icon
#include<wx/icon.h>
SetIcon(wxICON(mondrian));

Best Regards
Miao

2009年5月19日星期二

解决“由于应用程序的配置不正确,应用程序未能启动,重新安装应用程序可能会纠正这个问题” 收藏

解决"由于应用程序的配置不正确,应用程序未能启动,重新安装应用程序可能会纠正这个问题" 收藏
http://blog.csdn.net/soar_ersa/archive/2009/04/29/4135436.aspx

解决"由于应用程序的配置不正确,应用程序未能启动,重新安装应用程序可能会纠正这个问题"
[VS2005]解决"由于应用程序的配置不正确,应用程序未能启动,重新安装应用程序可能会纠正这个问题"
今天在准备发布用VS2005写的那个程序时,拷贝到我同事机器上,双击突然出现了"由于应用程序的配置不正确,应用程序未能启动,重新安装应用程序可能会纠正这个问题",这个问题很让我意外,以前只出现过缺少DLL的情况,而这次出现这个问题,让我一时没办法。想想,无非是两个原因引起的,要么是他没有安装VS2005的原因,要么是我的程序里依赖了其它的一些库。于是百度一下,发现好多相关主题。我是按照这个帖子解决的:

在VS2005下用C++写的程序,在一台未安装VS2005的系统上,
用命令行方式运行,提示:
"系统无法执行指定的程序"
直接双击运行,提示:
"由于应用程序的配置不正确,应用程序未能启动,重新安装应用程序可能会纠正这个问题"

以前用VC6和VS2003的话, 如果缺少库文件,是会提示缺少"**.dll",但是用VS2005却没有这样的提示。
自己实验了一下,感觉以下几种解决办法是可行的:
方法一:
在类似C:\Program Files\Microsoft Visual Studio 8\VC\redi
st\Debug_NonRedist\x86\Microsoft.VC80.DebugCRT 下找到了下列文件:
msvcm80d.dll
msvcp80d.dll
msvcr80d.dll
Microsoft.VC80.DebugCRT.manifest
把这几个文件拷贝到目标机器上,与运行程序同一文件夹或放到system32下,就可以正确运行了。
其他release版、MFC程序什么的都是拷redist下相应文件夹下的文件就可以了,文件夹后都有标识!
方法二:
修改编译选项,将/MD或/MDd 改为 /MT或/MTd,这样就实现了对VC运行时库的静态链接,在运行时就不再需要VC的dll了。
方法三:
工程-》属性-》配置属性-》常规-》MFC的使用,选择"在静态库中使用mfc"
这样生成的exe文件应该就可以在其他机器上跑了。
方法四:
你的vc8安装盘上找到再分发包vcredist_xxx.exe和你的程序捆绑安装

2009年5月13日星期三

Convert from wxString to std::string

Convert from wxString to std::string
Posted on 2008-11-15 09:49 chefZ 阅读(182) 评论(0) 编辑 收藏 引用
http://wiki.wxwidgets.org/Converting_ev ... m_wxString

http://wiki.wxwidgets.org/WxString

Converting everything to and from wxString
From WxWiki
Jump to: navigation, search

This question seems common so I thought I'd write an article. Note that sometimes there may be more than one possible solutions, so don't forget to check the docs.

Note that it is recommended to use wxString as much as possible. Do no use char* or std::string unless you use a third-party library that requires you to do so.
Contents
[hide]

* 1 Literals
* 2 char* to wxString
* 3 wxString to char*
* 4 int to wxString
* 5 float to wxString
* 6 wxString to integer number
* 7 wxString to floating-point number
* 8 std::string to wxString
* 9 wxString to std::string

[edit] Literals

A literal is a string written in code with "quotes around it". A literal is not a wxString, and will not be implicitly converted to one. This means that you can never pass in a raw literal into a wxWidget function or method.

MessageBox("I'm a mistake!") // WRONG

Instead, Unicode builds (prior to wxWidgets 3) require you to use one of these macros to turn literals into wxStrings:

_("text that can be translated")
wxT("text that can't be translated")
_T("same as wxT")

char* c = "sometext";
wxT(c) // WRONG, not a literal

Rather than being a nuisance, the _(), wxT(), and _T() macros take care of some unicode issues and help with internationalization.
[edit] char* to wxString

char* chars = "Hello world";
wxString mystring(chars, wxConvUTF8);

[edit] wxString to char*

void my_function(const char* foo)
{
}
...
wxString mystring(wxT("HelloWorld"));
my_function( mystring.mb_str() );

mb_str() returns a temporary pointer. If you need to store it in a char* :

wxString mystring(wxT("HelloWorld"));
char cstring[1024];
strcpy(cstring, (const char*)mystring.mb_str(wxConvUTF8));

[edit] int to wxString

wxString mystring = wxString::Format(wxT("%i"),myint);

or

wxString mystring;
mystring << myint;

[edit] float to wxString

wxString mystring = wxString::Format(wxT("%f"), myfloat);

or

wxString mystring;
mystring << myfloat;

[edit] wxString to integer number

wxString number(wxT("145"));
long value;
if(!s.ToLong(&value)) { /* error! */ }

[edit] wxString to floating-point number

wxString number(wxT("3.14159"));
double value;
if(!s.ToDouble(&value)){ /* error! */ }

[edit] std::string to wxString

std::string stlstring = "Hello world";
wxString mystring(stlstring.c_str(), wxConvUTF8);

[edit] wxString to std::string

wxString mystring(wxT("HelloWorld"));
std::string stlstring = std::string(mystring.mb_str());

wxWidgets 中的 C 字符串

wxWidgets 中的 C 字符串
关键字: wxwidgets, lua

在 wxWidgets 中处理 C 语言字符串真是一件令人头痛的事情,因为 wxWidgets 库是 C++ 编写的,类中的成员函数大多使用 wxChar*, wxString 作为参数。然而众所周知,在 C 语言里,字符串是以字符数组的形式存储,所以当 wxWidgets 需要和一些 C 库结合使用的时候,比如 Lua,难免会遇到将 C 字符串实例化成 wxString 或 wxChar* 对象的过程中出现乱码的问题。而且这种乱码的出现与一般编程中遇到的乱码不太一样,使用的转换方法不正确,不论是中文还是英文都会出现乱码,一视同仁。

一开始,我都是按照文档中的例子使用 wxString::Printf 方法进行转换,通常情况下不会有问题,例如:


C++代码

1. wxString temp;
2. temp.Printf(wxT("abc");//字符显示正确
3. temp.Printf(wxT("测试");//字符显示正确

wxString temp;
temp.Printf(wxT("abc");//字符显示正确
temp.Printf(wxT("测试");//字符显示正确




但是使用一段时间之后发现 Printf 函数只能正确得到第一个参数中的字符,其他参数中的字符都会变成乱码,即使是英文字符也会有问题,例如:


C++代码

1. wxString temp;
2. const char * str = "abc";
3. temp.Printf(wxT("%s", "abc");//temp会包含乱码
4. temp.Printf(wxT("%s", str);//temp会包含乱码

wxString temp;
const char * str = "abc";
temp.Printf(wxT("%s", "abc");//temp会包含乱码
temp.Printf(wxT("%s", str);//temp会包含乱码




阅读官方文档后,我发现 wxWidgets 还提供了另外一个函数 wxString::FromUTF8,函数声明如下:


C++代码

1. static wxString FromUTF8(const char* s)
2. static wxString FromUTF8(const char* s, size_t len)

static wxString FromUTF8(const char* s)
static wxString FromUTF8(const char* s, size_t len)




使用 wxString::FromUTF8 就可以顺利地解决问题,而且这还是一个静态函数,不需要实例化就可以调用,函数执行成功会返回 wxString 的一个实例:


C++代码

1. const char * str = "abc";
2. wxString::FromUTF8("abc");//返回 wxString 实例,值为"abc"
3. wxString::FromUTF8(str);//返回值与上一条语句相同

const char * str = "abc";
wxString::FromUTF8("abc");//返回 wxString 实例,值为"abc"
wxString::FromUTF8(str);//返回值与上一条语句相同




如果遇到要将 wxString 转化成为 char* 的情况,有三个函数可供使用:wxString::c_str(), wxString::wc_str(), wxString::mb_str()

mb_str returns a C string representation of the string, a const char*, regardless of whether Unicode is enabled. In Unicode mode, the string is converted, and data may be lost.

wc_str returns a wide character representation of the string, a wchar_t*, regardless of whether Unicode is enabled. In ANSI mode, the string is converted to Unicode.

c_str returns a pointer to the string data (const char* in ANSI mode, const wchar_t* in Unicode mode). No conversion takes place.

2009年5月8日星期五

转贴 MFC到wxWidgets移植笔记

1#
打印
字体大小: tT
发表于 2008-12-11 13:34 | 只看该作者
转贴 MFC到wxWidgets移植笔记
MFC到wxWidgets移植笔记(1)――工程设置
最近在捣腾wxWidgets,学习的最好办法是做个实在东西。以前做过一个个人财物管理的软件MyMoney,功能不太多,与系统关联不大,界面的东西多,还有一点数据库的内容,是学习wxWidgets的绝佳对象。以后将会不断的贴出移植的笔记。

首先准备开发环境。从http://www.wxwidgets.org/downloads/下载wxWidgets 的wxMSW版本,我们一步步来,先在MSW平台上移植成功,再到Linux上测试。当前wxWidgets版本为2.8.7,解压到e:\wx,打开 e:\wx\build\msw\wx.dsw,我用的是VC2005,将工程全部转换成新的工程文件wx.sln。wx有20个工程,其中的core和 base最基础,需要编译,其它的工程以后用到再说。不需要编译所有的解决方案配置,因为我们的工程是单工程,字符集为Unicode,因此只需编译 Unicode Debug和Unicode Release就行了。

接下来修改MyMoney工程设置。右键点击工程,在属性页中修改以下属性(Debug版本,Release版本类似): Use of MFC Use Standard WIndows Libraries
Additional Include Directories E:\wx\include;E:\wx\lib\vc_lib\mswd
Preprocessor Definitions WIN32;_DEBUG;__WXMSW__;__WXDEBUG__;_WINDOWS
Additional Library Directories E:\wx\lib\vc_lib
Additional Dependencies wxmsw28d_core.lib wxbase28d.lib wxtiffd.lib wxjpegd.lib wxpngd.lib wxzlibd.lib wxregexd.lib wxexpatd.lib winmm.lib comctl32.lib rpcrt4.lib wsock32.lib odbc32.lib

打开stdafx.h,清空,再写入下面两行代码:
#pragma once
#include <wx/wx.h>

至此,工程设置完成。

MFC到wxWidgets移植笔记(2)――移植CDoubleEdit
移植从最简单、最小的模块开始,MyMoney中有几个控件类比较独立,首先移植他们。
CDoubleEdit是一个只能输入double的文本控件,其小数位置最多允许两位,在MyMoney中提供金钱输入的功能。
在MFC中,CDoubleEdit从CEdit派生,为响应消息EN_CHANGE,实现了消息函数OnEnChange,当文本控件内容发生改变后,判断是否是合法值,如果不是则恢复成旧值m_content。以下是CDoubleEdit在MFC中的声明:

class CDoubleEdit : public CEdit
{
public:
CDoubleEdit();
virtual ~CDoubleEdit();

protected:
DECLARE_MESSAGE_MAP()

public:
afx_msg void OnEnChange();

private:
std::wstring m_content;
};

wxWidgets提供了一个wxTextCtrl类,这个类相当于CEdit,同样wxWidgets也有消息映射机制,实现了消息函数OnText,响应消息wxEVT_COMMAND_TEXT_UPDATE。以下是CDoubleEdit在wxWidgets中的声明:

class CDoubleEdit : public wxTextCtrl
{
public:
CDoubleEdit(wxWindow *parent,
wxWindowID id = wxID_ANY,
const wxString& value = wxT("0"),
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize);
virtual ~CDoubleEdit();

public:
void OnText(wxCommandEvent& event);

private:
wxString m_content;
DECLARE_EVENT_TABLE()
};


我不喜欢用MFC的CString,所以以前的代码用std::wstring比较多,虽然这种类型也可以跨平台,但wxString似乎更好用一点,因此移植后将所有的std::wstring改成了wxString。

wxWidgets的宏wxT可以根据工程字符集来转换字符串。

wxPoint和wxSize类似于MFC中的CPoint和CSize。

MFC用DECLARE_MESSAGE_MAP()声明消息映射表,wxWidgets中为DECLARE_EVENT_TABLE()。



再改cpp文件。将MFC中

BEGIN_MESSAGE_MAP(CDoubleEdit, CEdit)
ON_CONTROL_REFLECT(EN_CHANGE, OnEnChange)
END_MESSAGE_MAP()

换成适合wxWidgets的

BEGIN_EVENT_TABLE(CDoubleEdit, wxTextCtrl)
EVT_TEXT(wxID_ANY, CDoubleEdit::OnText)
END_EVENT_TABLE()

其它的工作,就是替换部分成员函数的体力活了。 GetWindowText GetValue
SetWindowText SetValue
SetSel SetSelection
GetSafeHwnd GetHandle


另外提一笔wxString,很好用,比如他提供了Format函数:

double d = 5.6f;

m_content = wxString::Format(L"%.2f", d);

和类型转换函数:

double d;

m_content.ToDouble(&d);

MFC到wxWidgets移植笔记(3)--移植CDailyButton
CDailyButton显示简单的每日支出,点击此Button可以看到详细情况并做支出操作。由于要显示的内容比较多,需要自定义绘制。下图是简单示意:


CDailyButton从wxButton派生。可能其它操作系统没提供自绘功能,wxButton没有自己的自绘机制,还好基于Windows操作系统,WxButton的MSW版本提供了MSWOnDraw函数,这个函数相当于Windows的DrawItem函数。另外如果要激发 MSWOnDraw函数,必须在CDailyButton的构造函数中调用函数SetBackgroundColour,此函数调用 MakeOwnerDrawn,设置OWNERDRAW属性。

通过这个类的移植,我发现wxWidgets并不是无所不能的,有些时候还是需要做与平台相关的实现。

MFC到wxWidgets移植笔记(4)――前两篇移植笔记的补充

年前写的移植笔记,就突然中断了,没有别的原因,因为当时就发现走了很多弯路,代码改了又改,最后一直到真正的移植到Ubuntu,编译通过,是几个月后的事情了。因为把这些东西都弄清楚了,才开始继续这个笔记,其中又学到了不少的东西。当初的想法果然是对的,只有找个小小的项目练练手,才可能学到不少好东西。
回头看看前三篇笔记,第一篇没必要修改,第2、3篇的内容发生了不少改变,其实主要是重构,以前的路子还是正确的。
第二篇中的CDoubleEdit,类名称做了修改。首先是去掉了前面的C。类命名时在前面加C,是传统的匈牙利命名法,这种方法没什么问题,但是好像 Linux下的代码不买这个帐,因此我也去掉了。顺带的,后面所有的类,都去掉了以C开头的命名方式。其次以Double为名,没有反映出表示钱的意思,因此将类名改成了MoneyTextCtrl。TextCtrl是为了符合wxWidgets的命名标准。
第三篇中的CDailyButton,同样换了一个名字,新名字为DailyPanel。Ubuntu下面,没有自绘的Button一说,因此新的 DailyPanel直接从wxWindow派生,然后实现wxWindow的EVT_PAINT消息,同样实现了自绘功能。当然,因为不再是 Button,此时需要自己实现一些Button的功能了,在EVENT_TABLE中,增加几个消息响应。主要是当DailyButton获得和失去焦点,或者按键、鼠标点击的时候,触发他的刷新消息就行了:
BEGIN_EVENT_TABLE(DailyPanel, wxWindow)
EVT_PAINT(DailyPanel::OnPaint)
EVT_ERASE_BACKGROUND(DailyPanel::OnErase)
EVT_SET_FOCUS(DailyPanel::OnSetFocus)
EVT_KILL_FOCUS(DailyPanel::OnKillFocus)
EVT_CHAR(DailyPanel::OnChar)
EVT_MOUSE_EVENTS(DailyPanel::OnMouseEvent)
END_EVENT_TABLE()
另外,在有RETURN和SPACE按键或者鼠标点击时,需要向上级窗口发送一个wxEVT_COMMAND_BUTTON_CLICKED消息,模拟Button消息:
void DailyPanel::OnChar(wxKeyEvent& event)
{
if (WXK_RETURN == event.GetKeyCode()
|| WXK_SPACE == event.GetKeyCode())
{
wxCommandEvent et;
et.SetEventType(wxEVT_COMMAND_BUTTON_CLICKED);
et.SetId(GetId());
GetParent()->GetEventHandler()->rocessEvent(et);
}
}
这样的实现,可保证DailyPanel的平台移植性了。

MFC到wxWidgets移植笔记(5)――移植CSortListCtrl
记帐类的软件,是不可能没有Grid的。我这个小东西,还用不上那么麻烦,一个小小的list就够显示数据了,不过排序功能不可少,因此有了这个 SortListCtrl(MFC版本中叫做CSortListCtrl)。当用户点击list的Column时,将触发排序功能。
先看MFC的定义:

class CSortListCtrl : public CListCtrl
{

public:
CSortListCtrl();
virtual ~CSortListCtrl();

public:
afx_msg void OnLvnColumnclick(NMHDR *pNMHDR, LRESULT *pResult);

protected:
DECLARE_MESSAGE_MAP()

private:
static int CALLBACK CompareProc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort);
};

wxWidgets中的定义:

class SortListCtrl : public wxListCtrl
{
DECLARE_EVENT_TABLE()
public:
SortListCtrl(wxWindow *parent, wxWindowID id, wxSize sz = wxDefaultSize);
virtual ~SortListCtrl();

public:
void OnColumnclick(wxListEvent& event);

private:
static int wxCALLBACK CompareProc(long lParam1, long lParam2, long lParamSort);
};

以前就有评论说了,wxWidgets的类结构非常像MFC,通过这个类的移植,能非常清楚的反映出来。

MFC到wxWidgets移植笔记(6)――移植对话框CClauseDlg
控件只有简单的三个,下面就是移植对话框了。对话框将近20个 。我找了一个最简单,又具有比较完备功能的对话框ClauseDlg说明一下。



图片是在Ubuntu下面截的,说老实话,简单的截图然后裁剪一下,Ubuntu还真不是很好用。

这个对话框很简单,新增一个收支项目,除了指定项目名称,还要设定是支出还是收入,最后点击确定增加。

同理,首先将CClauseDlg改成ClauseDlg。下面是类声明:
class ClauseDlg : public wxDialog
{
DECLARE_EVENT_TABLE()
public:
ClauseDlg(wxWindow* pParent);
virtual ~ClauseDlg();
public:
virtual bool TransferDataToWindow();
virtual bool TransferDataFromWindow();
protected:
void CreateControl();
void OnBnClickedOk(wxCommandEvent& event);
public:
wxString m_strClause;
bool m_bin;
};
注:为了在此简单说明,我将成员变量的访问属性改成了public,去掉了get/set函数。
这个类与原MFC类有以下几个不同:
1.基类从CDialog改成wxDialog
2.消息映射表声明从DECLARE_MESSAGE_MAP改成DECLARE_EVENT_TABLE
3.对话框不再使用资源文件,其初始化的所有过程,都在CreateControl函数中。
4.重载TransferDataToWindow和TransferDataFromWindow。这两个函数实现了成员变量与控件的数据同步,类似于MFC的DoDataExchange。

再看看实现:

ClauseDlg::ClauseDlg(wxWindow* pParent)
: wxDialog(pParent, wxID_ANY, wxT("收支项目"), wxDefaultPosition)
, m_strClause(wxEmptyString)
, m_bin(false)
{
CreateControl();
}

ClauseDlg::~ClauseDlg()
{
}

BEGIN_EVENT_TABLE(ClauseDlg, wxDialog)
EVT_BUTTON(wxID_OK, ClauseDlg::OnBnClickedOk)
END_EVENT_TABLE()

// ClauseDlg message handlers
enum {wxID_TEXT_CLAUSE, wxID_RADIO_OUT, wxID_RADIO_IN};

void ClauseDlg::CreateControl()
{
wxBoxSizer* clause = new wxBoxSizer(wxHORIZONTAL);
clause->Add(new wxStaticText(this, wxID_ANY, wxT("收支项目")), 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 10);
clause->Add(new wxTextCtrl(this, wxID_TEXT_CLAUSE, m_strClause), 1, wxEXPAND);

wxBoxSizer* outin = new wxBoxSizer(wxHORIZONTAL);
outin->Add(new wxRadioButton(this, wxID_RADIO_OUT, wxT("支出"), wxDefaultPosition, wxDefaultSize, wxRB_GROUP), 1, wxEXPAND);
outin->Add(new wxRadioButton(this, wxID_RADIO_IN, wxT("收入")), 1, wxEXPAND);

wxStdDialogButtonSizer* button = new wxStdDialogButtonSizer();
button->AddButton(new wxButton(this, wxID_OK, wxT("确定")));
button->AddButton(new wxButton(this, wxID_CANCEL, wxT("取消")));
button->Realize();
((wxButton*)FindWindow(wxID_OK))->SetDefault();

wxBoxSizer* root = new wxBoxSizer(wxVERTICAL);
root->Add(clause, 0, wxEXPAND | wxALL, 5);
root->Add(outin, 0, wxEXPAND | wxALL, 5);
root->Add(button, 0, wxALL, 5);
SetSizerAndFit(root);
}

void ClauseDlg::OnBnClickedOk(wxCommandEvent&)
{
if (m_strClause.Length() > 0)
{
EndDialog(wxID_OK);
}
}

bool ClauseDlg::TransferDataToWindow()
{
((wxTextCtrl*)FindWindow(wxID_TEXT_CLAUSE))->SetValue(m_strClause);
((wxRadioButton*)FindWindow(wxID_RADIO_IN))->SetValue(m_bin);

return wxDialog::TransferDataToWindow();
}

bool ClauseDlg::TransferDataFromWindow()
{
m_strClause = ((wxTextCtrl*)FindWindow(wxID_TEXT_CLAUSE))->GetValue();
((wxRadioButton*)FindWindow(wxID_RADIO_IN))->GetValue();

return wxDialog::TransferDataFromWindow();
}
这个实现中包含了很多东西,一点点的来说。

1.构造函数在构造时首先调用了基类的构造函数,并传递了适当的信息。基类的构造函数,实际上已经完成了对话框的创建过程,此时窗口已经被创建出来。不过此时的对话框还是一个光秃秃的东西。然后调用CreateControl来构造对话框的所有控件。

2.在函数CreateControl前,定义了一个enum,它定义了对话框中绑定数据的控件ID。通过FindWindow和这个ID,就能找到控件相对应的类,并对其做相关操作。

3.CreateControl创建控件,同时还构造了一个布局。wxWidgets使用了和Java一样的布局方式,设定好控件的相对位置,加入到 BoxSizer中,然后调用SetSizerAndFit,将一切交给BoxSizer来计算他们的实际位置。关于布局的具体说明,参考《使用 wxWidgets进行跨平台程序开发》。

4.CreateControl除了可以创建控件,还可以做其它的任何初始化的操作,有点相当于MFC中的OnInitialDlg函数。

5.关闭模式对话框,应该使用EndDialog,并传递一个ID作为参数。外部通过函数ShowModal调用这个对话框的时候,这个ID作用ShowModal的返回值返回。

6.查找控件用FindWindow,找到会返回一个wxWindow的对象指针,可以强制转换成对应的类指针。

7.TransferDataToWindow将成员变量转到窗口控件上,TransferDataFromWindow从窗口控件获得数据。类似于DDX_Control和DDX_Text一类的东西。

8.__super这个关键字好像g++不支持,因此在调用基类的时候,必须明确指出基类的名称。

最后记一笔这个Dialog的用法:

ClauseDlg dlg(this, this);
if (dlg.ShowModal() == wxID_OK)
{
...
}

MFC到wxWidgets移植笔记(7)――数据库sqlite3
在Windows平台上,最好的小型数据库,可能非Access莫属了。小巧,独立,数据库功能齐全,具备一定的安全性,真可谓是居家旅行,必备之数据库了。

可惜的是,Access只能适用于Windows平台。想通吃其它平台,我找了很久,最后发现了sqlite3。

sqlite3也是拥有独立的数据库文件,文件小巧,易使用,提供了标准的C函数库文件。能提供大多数数据库操作,由于他支持的数据库特性太多,所以他的网站上只列出不能支持的特性:

外键约束(FOREIGN KEY constraints)
外键约束会被解析但不会被执行。
完整的触发器支持(Complete trigger support)
现在有一些触发器的支持,但是还不完整。 缺少的特性包括 FOR EACH STATEMENT 触发器(现在所有的触发器都必须是 FOR EACH ROW ), 在表上的 INSTEAD OF 触发器(现在 INSTEAD OF 触发器只允许在视图上),以及递归触发器――触发自身的触发器。
完整的 ALTER TABLE 支持(Complete ALTER TABLE support)
只支持 ALTER TABLE 命令的 RENAME TABLE 和 ADD COLUMN。 其他类型的 ALTER TABLE 操作如 DROP COLUMN,ALTER COLUMN,ADD CONSTRAINT 等等均被忽略。
嵌套事务(Nested transactions)
现在的实现只允许单一活动事务。
RIGHT 和 FULL OUTER JOIN(RIGHT and FULL OUTER JOIN)
LEFT OUTER JOIN 已经实现,但还没有 RIGHT OUTER JOIN 和 FULL OUTER JOIN。
可写视图(Writing to VIEWs)
SQLite 中的视图是只读的。无法在一个视图上执行 DELETE,INSERT,UPDATE。 不过你可以创建一个试图在视图上 DELETE,INSERT,UPDATE 时触发的触发器,然后在触发器中完成你所需要的工作。
GRANT 和 REVOKE(GRANT and REVOKE)
由于 SQLite 读和写的是一个普通的磁盘文件, 因此唯一可以获取的权限就是操作系统的标准的文件访问权限。一般在客户机/服务器架构的关系型数据库系统上能找到的 GRANT 和 REVOKE 命令对于一个嵌入式的数据库引擎来说是没有意义的,因此也就没有去实现。
其实上述问题,要么有其它办法解决(比如外键约束,可心用触发器实现),要么就用不上(比如视图),所以对于大多数需要跨平台的小型数据库的应用来说,sqlite3完全够用了。

官方的sqlite3只提供了一套C函数库文件(sqlite3.c,sqlite3.h,sqlite3ext.h),要想用可视化的操作方式,可以用第三方的Sqlite Developer。

MFC到wxWidgets移植笔记(8)――数据库设计
据我实验感觉,sqlite3的内部数据,实际上都是二进制。其实所有的数据库,内部都是以二进制保存。只不过通过那些数据库的访问接口,必须根据正确的类型来获得数据。sqlite3在这方面做得更开放一些,或者说更不严格。虽然在创建表的时候可以指定字段的类型和长度,实际存储或者读取的时候,是可以用其它类型的,并且不会出错。

但是即便如此,我们也还是要设计一个完善的数据库定义,这样对于以后数据的移植也方便得多。

以下是我用Sqlite Developer导出的表user的定义:

Drop Table If Exists [user];
CREATE TABLE IF NOT EXISTS 'user'(
[id] integer PRIMARY KEY
,[name] varchar(32) UNIQUE NOT NULL
,[pass] varchar(128) NOT NULL
,[last_time_login] datetime
);

可以看到,大部分的sql关键字都支持,语法非常通用。

再看表bank的定义:

Drop Table If Exists [bank];
CREATE TABLE IF NOT EXISTS 'bank'(
[id] integer PRIMARY KEY
,[user_id] integer NOT NULL
,[name] varchar(32) NOT NULL
,[last_time_record] datetime
);

字段user_id是一个外键,与user中的id具有删除关联。在前面提到过,sqlite3不支持外键,可以用触发器实现:

Drop Trigger If Exists [tgrDeleteUser];
CREATE TRIGGER IF NOT EXISTS [tgrDeleteUser] AFTER DELETE On [user] FOR EACH ROW
begin
delete from bank where user_id=old.id;
end;

Drop Trigger If Exists [tgrInsertBank];
CREATE TRIGGER IF NOT EXISTS [tgrInsertBank] BEFORE INSERT On [bank] FOR EACH ROW
begin
select raise(rollback, 'insert invalid')
where (select id from user where id=new.user_id) is null;
end;

MFC到wxWidgets移植笔记(9)――数据库访问
sqlite3提供了一套C代码,用来访问sqlite3数据。这套代码包含了三个文件,sqlite3.h,sqlite3.c以及 sqlite3ext.h。我将sqlite3的源代码放到整个工程中,因此用不上sqlite3ext.h。我们只需要在工程中加入sqlite3.h 和sqlite3.c就行了。

前面说过sqlite3的内部完全是二进制,我们可以仅管把所有读取的数据当作二进制看待。因此,只要把中文全部转换成UTF8,这样就能完美的解决中文读取和存储的问题了。

下面是一些常用到的函数:

1.打开关闭数据库:

SQLITE_API int sqlite3_open(
const char *filename, /* Database filename (UTF-8) */
sqlite3 **ppDb /* OUT: SQLite db handle */
);

SQLITE_API int sqlite3_close(sqlite3 *);

sqlite3_open的第二个参数返回sqlite3*的指针。这个指针指向了一个sqlite3的对象,我们不用关注它的具体内容是什么,就当成一个Windows系统中的句柄就行了。

sqlite3的函数通常都返回一个int表示函数执行结果,一般来说返回SQLITE_OK,也就是0,就表示成功了。

2.执行一个命令

SQLITE_API int sqlite3_exec(
sqlite3*, /* An open database */
const char *sql, /* SQL to be evaluted */
int (*callback)(void*,int,char**,char**), /* Callback function */
void *, /* 1st argument to callback */
char **errmsg /* Error msg written here */
);

基本上,所有的sql操作都是通过这条语句来执行的,以下是一个例子,只处理那种非查询的sql语句:

extern sqlite3* sqlite3DB;

bool DBDatabase::Execute(const wxString& sql)

{

char* errMsg = NULL;
bool ret = (SQLITE_OK == sqlite3_exec(sqlite3DB, sql.ToUTF8(), NULL, NULL, &errMsg));
if (errMsg != NULL)
{
if (! ret)
{
m_err = wxString::From8BitData(errMsg);
m_err += wxT("\r\n");
}
sqlite3_free(errMsg);
}
return ret;

}

当处理非查询sql语句时,sqlite3_exec的第三、四个参数为空。

注意,如果执行结果有错,错误信息保存在errMsg中,这段内存需要通过sqlite3_free释放,否则有内存泄露的问题。

3.事务操作

非常简单,看代码:

void DBDatabase::BeginTrans()
{
Execute(wxString(wxT("begin transaction")));
}

void DBDatabase::CommitTrans()
{
Execute(wxString(wxT("commit transaction")));
}

void DBDatabase::RollbackTrans()
{
Execute(wxString(wxT("rollback transaction")));
}

4.查询操作

当执行"select"语句时,需要为sqlite3_exec的第三个参数指定一个回调函数:

int DBDatabase::sqlite3_callback(void* para, int n_column, char** column_value, char** column_name)
{
DBRecordSet* set = (DBRecordSet*)para;
set->AddRecord();
for(int i = 0 ; i < n_column; i ++ )
{
wxString field(column_name, wxMBConvUTF8());
wxString value(column_value, wxMBConvUTF8());
set->AddField(field, value);
}
return SQLITE_OK;
}

bool DBDatabase:ueryRecordSet(const wxString& sql, DBRecordSet* set)
{
set->Clear();
char* errMsg = NULL;
bool ret = (SQLITE_OK == sqlite3_exec(sqlite3DB, sql.ToUTF8(), DBDatabase::sqlite3_callback, (void*)set, &errMsg));
if (errMsg != NULL)
{
if (! ret)
{
m_err = wxString::From8BitData(errMsg);
m_err += wxT("\r\n");
}
sqlite3_free(errMsg);
}
return ret;
}

MFC到wxWidgets移植笔记(10)――数据访问接口
上一篇谈到了对sqlite3的封装,下面完整的描述数据访问接口。

数据访问接口模仿ADO设计,保存了查询结果。接口的最小的单位为Field,记录字段数据。其次为Record,描述一条记录。再就是RecordSet,是记录集合。数据库封装类为Database。所有的数据库访问接口都以DB开头。

MID表示主键类型,定义如下:

typedef unsigned long MID;

MTIME是对时间的封装类:

struct MTIME{
long year;
long month;
long day;

};

以下是数据结构定义:

class DBField
{
public:
DBField();
virtual ~DBField();

public:
virtual void SetField(const wxString& field, const wxString& value);
virtual const wxString& Field()const;
virtual const wxString& Value()const;

private:
wxString m_field;
wxString m_value;
};

class DBRecord
{
public:
DBRecord();
virtual ~DBRecord();

public:
virtual void AddField(const wxString& field, const wxString& value);
virtual bool GetField(const wxString& field, wxString& value)const;

private:
typedef std::vector<DBField> Fields;
Fields m_fields;
};

class DBRecordSet
{
public:
DBRecordSet();
virtual ~DBRecordSet();

public:
virtual void Clear();
virtual long RecordCount()const;

virtual void MoveFirst();
virtual bool MoveNext();
virtual bool GetField(const wxString& field, wxString& value)const;
virtual bool GetField(const wxString& field, MTIME& tm)const;
virtual bool GetField(const wxString& field, MID& id)const;
virtual bool GetField(const wxString& field, long& l)const;
virtual bool GetField(const wxString& field, double& d)const;
virtual bool GetField(const wxString& field, bool& b)const;

virtual void AddRecord();
virtual void AddField(const wxString& field, const wxString& value);
virtual void AddField(const wxString& field, const MTIME& tm);
virtual void AddField(const wxString& field, MID id);
virtual void AddField(const wxString& field, long l);
virtual void AddField(const wxString& field, double d);
virtual void AddField(const wxString& field, bool b);

private:
typedef std::vector<DBRecord> Records;
Records m_records;
Records::size_type m_pos;
};

interface IDatabase
{
virtual void BeginTrans()=0;
virtual void CommitTrans()=0;
virtual void RollbackTrans()=0;
virtual bool Execute(const wxString& sql)=0;
virtual bool QueryRecordSet(const wxString& sql, DBRecordSet* set)=0;
virtual wxString GetLastError()const=0;
};

struct sqlite3;

class DBDatabase : public IDatabase
{
public:
DBDatabase();
virtual ~DBDatabase();
static wxString GetDBFile();

public:
bool InitConnection();

virtual void BeginTrans();
virtual void CommitTrans();
virtual void RollbackTrans();
virtual bool Execute(const wxString& sql);
virtual bool QueryRecordSet(const wxString& sql, DBRecordSet* set);
virtual wxString GetLastError()const;

private:
static int sqlite3_callback(void* para, int n_column, char** column_value, char** column_name);

private:
sqlite3* sqlite3DB;
wxString m_err;
};

MFC到wxWidgets移植笔记(11)――表访问类
有了上节基本的数据访问接口,可以实现具体的表访问类了。以下以user表为例:

interface IDatabase;
class MDUser
{
public:
MDUser(IDatabase* pDB);
virtual ~MDUser(void);

public:
virtual void GetUser(wxArrayString& vUserNames);
virtual MID AppendUser(const wxString& name, const wxString& pass);
virtual bool DeleteUser(MID id);
virtual bool UpdateUser(MID id, const wxString& name, const wxString& pass);
virtual bool GetUserID(const wxString& name, const wxString& pass, MID& id);
virtual bool GetUserName(MID id, wxString& name);
virtual wxString EncryptPass(const wxString& pass)const;
virtual void UpdateLogin(MID id)const;

private:
IDatabase* db;
};

以两个函数实现为例:

MID MDUser::AppendUser(const wxString& name, const wxString& pass)
{
MID iduser = eMID;
if (db->Execute(wxString::Format(wxT("insert into user(name,pass) values(\'%s\',\'%s\')"),
name.c_str(), EncryptPass(pass).c_str())))
{
DBRecordSet set;
if (db->QueryRecordSet(wxT("select max(id) as max_id from user"), &set))
{
if (set.RecordCount() == 1)
{
set.MoveFirst();
set.GetField(wxT("max_id"), iduser);
}
}
}
return iduser;
}

bool MDUser:eleteUser(MID id)
{
return db->Execute(wxString::Format(wxT("delete from user where id=\'%d\'"), id));
}

MFC到wxWidgets移植笔记(12)――XML操作
作为当前最流行的通用格式xml,wxWidgets同样提供了非常方便的xml类库。由于用起来实在是太舒服了,以至于让我完全兴不起重新封装的念头。

要使用xml类库,除了编译xml模块外,还要先编译expat模块,xml模块必须用到此模块。expat没有在wxWidgets的默认工程中,需要自己手动编译。找到src下的expat,打开并分别编译debug和release版本,会分别生成expat.lib和expatd.lib。然后再编译xml模块。

要在工程中使用xml,首先是将expat.h拷贝到include目录中,在stdafx.h中添加#include <expat.h>,然后在工程设置中为lib中添加wxbase28u_xml.lib和expat.lib(Debug版本为 wxbase28ud_xml.lib和expatd.lib)。

以下是使用方式,先说读(只用过DOM模式读xml文档)。DOM的根类是wxXmlDocument:

wxXmlDocument doc;
doc.Load(file);

打开文件后,就可以直接取子节点了:

wxXmlNode *child = doc.GetRoot()->GetChildren();
while (child)
{
if (child->GetName() == wxT("store"))
{
ImportBank(id, child);
}
child = child->GetNext();
}

GetRoot()获得根节点,wxXmlNode是所有节点的基类,函数GetChildren()获得最左边的子孩子,函数GetNext()获得右孩子结点,函数GetName()获得节点名称,函数GetPropVal()获得属性。

以下是写的一个例子:

wxXmlDocument doc;

doc.SetVersion(wxT("1.0"));
doc.SetFileEncoding(wxT("UTF-8"));



wxXmlNode* root = new wxXmlNode(wxXML_ELEMENT_NODE, wxT("wallet"));
root->AddProperty(wxT("user"), name);
root->AddProperty(wxT("pass"), pass);
root->AddProperty(wxT("version"), wxT("1.0"));
doc.SetRoot(root);


wxXmlNode* cash = new wxXmlNode(root, wxXML_ELEMENT_NODE, wxT("cash"));


doc.Save(file);

MFC到wxWidgets移植笔记(13)――多语言实现
多语言实现是大部分软件需要考虑的问题,解决的办法也很多,也没有什么规范性的做法。比较有名的算是微软的支持多语言的资源文件文件了,虽然用起来很方便,但同样存在着编辑困难,编译后无法修改的问题。在我这个软件里,使用了最最简单的做法,用一个xml文件存储字符串,通过简单英文(也可以是数字)标记词汇。然后在代码中用一个语言类来管理。

以下是这个xml文件(只列出一部分):

<?xml version="1.0"?>
<language_package code="chinese" name="简体中文">
<node field="S_CONNECT_FAIL" content="连接数据库失败!" />
<node field="bank" content="银行" />
<node field="bankname" content="银行名称" />
<node field="import_success" content="导入成功!" />
<node field="import_fail" content="导入失败!" />
</language_package>

然后是类实现。在类的构造函数中,打开xml文件,读出所有的数据,并存到一个map中:

LanguageRes:anguageRes()
{
wxStandardPaths stdPath;
wxFileName fn(stdPath.GetExecutablePath());
wxString szFile = fn.GetPath();
szFile += wxFILE_SEP_PATH;
szFile += wxT("chs.lng");

wxXmlDocument doc;
doc.Load(szFile);
wxXmlNode *root = doc.GetRoot();
ASSERT(root->GetName() == wxT("language_package"));
wxString code = root->GetPropVal(wxT("code"), wxEmptyString);
ASSERT(code == wxT("chinese"));
wxXmlNode *child = root->GetChildren();
while (child)
{
if (child->GetName() == wxT("node"))
{
wxString field = child->GetPropVal(wxT("field"), wxEmptyString);
wxString content = child->GetPropVal(wxT("content"), wxEmptyString);
m_map.insert(LANGUAAGE_MAP::value_type(field, content));
}
child = child->GetNext();
}
}
然后用了singleton模式,返回一个唯一的全局变量:

LanguageRes* LanguageRes::res = NULL;

LanguageRes* LanguageRes::GetLanguageRes()
{
if (res == NULL)
{
res = new LanguageRes;
}
return res;
}

注:最近面试时我会问一个问题,如果是个多线程程序,如何确保singleton的线程安全性。让人失望的是,让大家说线程的同步有几种方式时,基本上都能答上来,但灵活应用到具体的问题上时却答不上来了,sign

MFC到wxWidgets移植笔记(14)――消除代码中的平台差异
此系列的大纲是早就拟好的,写到这一篇还真有点犯难了。因为当初从Windows平台移到Linux平台下编译时,遇到了很多代码中的问题,可是没有记下来,现在再回想,有点无处下笔了。只能凑合着写个一两点:

1.gcc编译器好像只能处理ansi版本的代码文件。如果是unicode或者utf8,都会出现编译问题。并且,他不会根据你当前操作系统的默认语言识别代码文件中非ansi字符(比如中文)。所以,如果代码中,是绝对不能有中文的。这种问题有两个办法解决,一是参考我的前一篇笔记,将所有的中文字符移到非代码文件中,二是用Asicii编码来代替。比如:

wchar_t *t = L"中文";

就改成了:

byte t[] = {0x2d, 0x4e, 0x87, 0x65, 0, 0};
wprintf(L"%s\n", (wchar_t*)t);

2.所有的代码文件必须在文件结尾有一个空行。这是Linux编译器的要求,很怪异

3.Linux下面没有工程文件,所以某些配置需要在预定义头文件中用代码加上。比如如果编译带调试信息的,要加上:

#ifndef DEBUG
#define DEBUG

2009年4月24日星期五

在VC中WININET如何使用HTTP的POSTA和GET方法

在VC中WININET如何使用HTTP的POSTA和GET方法 2009-02-13 16:35
分类:默认分类
字号: 大大 中中 小小

Post资料:

SUMMARY

To properly simulate a Form submission using WinInet, you need to send a header that indicates the proper Content-Type. For Forms, the proper Content-Type header is: Content-Type: application/x-www-form-urlencoded



MORE INFORMATION

In many cases, the server does not respond appropriately if a Content-Type is not specified. For example, the Active Server Pages component of IIS 3.0 actually checks this header specifically for 'application/x-www-form- urlencoded' before adding form variables to the "Request.Form" object. This MIME/Content-Type indicates that the data of the request is a list of URL- encoded form variables. URL-encoding means that space character (ASCII 32) is encoded as ' ', special character such '!' encoded in hexadecemal form as '!'.

Here is a snippet of code that uses the MFC WinInet classes to simulate a Form POST request:

CString strHeaders = _T("Content-Type: application/x-www-form-urlencoded");

// URL-encoded form variables -

// name = "John Doe", userid = "hithere", other = "P&Q"

CString strFormData = _T("name=John Doe&userid=hithere&other=P&Q");

CInternetSession session;

CHttpConnection* pConnection = session.GetHttpConnection(_T("ServerNameHere"));

CHttpFile* pFile = pConnection->OpenRequest(CHttpConnection::HTTP_VERB_POST,_T("FormActionHere"));

BOOL result = pFile->SendRequest(strHeaders,(LPVOID)(LPCTSTR)strFormData, strFormData.GetLength());

Without MFC, the same code translates to straight SDK calls as follows:

static

TCHAR hdrs[] = _T("Content-Type: application/x-www-form-urlencoded");

static

TCHAR frmdata[] = _T("name=John Doe&userid=hithere&other=P&Q");

statuc TCHAR accept[] = _T("Accept: */*");



// for clarity, error-checking has been removed

HINTERNET hSession = InternetOpen("MyAgent",INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);

HINTERNET hConnect = InternetConnect(hSession, _T("ServerNameHere"),

INTERNET_DEFAULT_HTTP_PORT,

NULL,

NULL,

INTERNET_SERVICE_HTTP,

0,

1);

HINTERNET hRequest = HttpOpenRequest(hConnect,

"POST",

_T("FormActionHere"),

NULL,

NULL,

accept,

0,

1);

HttpSendRequest(hRequest,

hdrs,

strlen(hdrs),

frmdata,

strlen(frmdata));

// close any valid internet-handles

我这里有一段程序,用来在一个对话框里显示出一次http request的原始信息,不过使用Inet API做的,希望能有帮助。

void CHTTPRequestDlg::OnButtonRequest()

{

UpdateData(TRUE);

HINTERNET hInternet = InternetOpen("Mozilla/4.0 (compatible; MSIE 5.0; Windows NT 5.0)",

INTERNET_OPEN_TYPE_DIRECT,

NULL,

NULL,

NULL);

HINTERNET hSession = InternetConnect(hInternet,

m_strHost,

m_nPort,

"username",

"password",

INTERNET_SERVICE_HTTP,

0,

0);

char* szAccept[] = {"*/*",NULL};

CString strVerb;

m_comboVerb.GetWindowText(strVerb);

HINTERNET hRequest = HttpOpenRequest(hSession,

strVerb,

m_strObject,

NULL,

NULL,

(LPCSTR*)szAccept,

0,

0);

struct

{

char* Language;

char* Encoding;

char* ContentType;

}Headers = {"Accept-Language:zh-cn\r\n",

"Accept-Encoding:gzip,deflate\r\n",

"Content-Type:application/x-www-form-urlencoded\r\n"};



if(m_bLanguage)

{

HttpAddRequestHeaders(hRequest,

Headers.Language,

-1,

HTTP_ADDREQ_FLAG_ADD|HTTP_ADDREQ_FLAG_REPLACE);

}

if(m_bEncoding)

{

HttpAddRequestHeaders(hRequest,

Headers.Encoding,

-1,

HTTP_ADDREQ_FLAG_ADD|HTTP_ADDREQ_FLAG_REPLACE);

}

if(m_bContentType)

{

HttpAddRequestHeaders(hRequest,

Headers.ContentType,

-1,

HTTP_ADDREQ_FLAG_ADD|HTTP_ADDREQ_FLAG_REPLACE);

}

LPCSTR lpAddHeader = NULL;

LPCSTR lpContent = NULL;

if(m_strHeaders.GetLength())

{

if(m_strHeaders.Right(2) != "\r\n")

{

m_strHeaders+="\r\n";

}

lpAddHeader = (LPCSTR)m_strHeaders;

}

if(m_strContent.GetLength() && (strVerb == "POST" || strVerb == "PUT"))

{

lpContent = (LPCSTR)m_strContent;

}

HttpSendRequest(hRequest,

lpAddHeader,

-1,

(LPVOID)lpContent,

m_strContent.GetLength());



m_editContentGot.SetSel(0,-1);

m_editContentGot.ReplaceSel("");



LPSTR lpszData; //buffer for the data

DWORD dwSize; //size of the data available

DWORD dwDownloaded; //size of the downloaded data



//Set the cursor to an hourglass.

SetCursor(LoadCursor(NULL,IDC_WAIT));



// This loop handles reading the data.

while(1)

{

// The call to InternetQueryDataAvailable determines the amount of

// data available to download.

if (!InternetQueryDataAvailable(hRequest,&dwSize,0,0))

{

SetCursor(LoadCursor(NULL,IDC_ARROW));

break;

}

else

{

// Allocates a buffer of the size returned by InternetQueryDataAvailable

lpszData = new char[dwSize+1];



// Reads the data from the HINTERNET handle.

if(!InternetReadFile(hRequest,(LPVOID)lpszData,dwSize,&dwDownloaded))

{

delete[] lpszData;

break;

}

else

{

// Adds a null terminator to the end of the data buffer

lpszData[dwDownloaded]='\0';



int nLen = m_editContentGot.GetWindowTextLength();

m_editContentGot.SetSel(nLen-1, nLen-1);

m_editContentGot.ReplaceSel(lpszData);



// Delete the two buffers

delete[] lpszData;



// Check the size of the remaining data. If it is zero, break.

if (dwDownloaded == 0)

{

break;

}

}

}

}



// Close the HINTERNET handle

InternetCloseHandle(hRequest);

InternetCloseHandle(hSession);

InternetCloseHandle(hInternet);



// Set the cursor back to an arrow

SetCursor(LoadCursor(NULL,IDC_ARROW));

}

==========================================

关文件:#include

使用MFC示例如下:

首先设置m_strRequest请求字符串 eg."name=aaa&pass=bbb";

m_strServerName 服务器名称或者IP eg."www.yahoo.com"

m_strObjectName 请求文件位置 eg. "pub/aaa.asp"

请求的结果存放在m_strHtml中

void func()

{

CInternetSession m_InetSession("session");

CHttpConnection *pServer = NULL;

CHttpFile* pFile = NULL;

try

{

INTERNET_PORT nPort;

nPort=80;

pServer = m_InetSession.GetHttpConnection(m_strServerName, nPort);

pFile = pServer->OpenRequest(CHttpConnection::HTTP_VERB_POST,m_strObjectName);

char szHeaders[100];

strcpy(szHeaders,"Accept: text*/*\r\nContent-Type: application/x-www-form-urlencoded");

pFile->AddRequestHeaders(szHeaders);



pFile->SendRequestEx(m_strRequest.GetLength());

pFile->WriteString(m_strRequest);//重要-->m_Request 中有"name=aaa&name2=BBB&"

pFile->EndRequest();

DWORD dwRet;

pFile->QueryInfoStatusCode(dwRet);

CString str;



//m_Mutex.Lock();

m_strHtml="";

char szBuff[1024];

if (dwRet == HTTP_STATUS_OK)

{

UINT nRead;

while ((nRead = pFile->Read(szBuff,1023))>0)

{

m_strHtml += CString(szBuff,nRead);

}

}

//m_Mutex.Unlock();



delete pFile;

delete pServer;

}

catch (CInternetException* e)

{

CString s;

s.Format("Internet Exception\r\nm_dwError%u,m_dwContextError%u",e->m_dwError,e->m_dwContext);

AfxMessageBox(s);

//catch errors from WinInet

}

}

============================

1、获得WebBrowser Control的DWebBrowserEvents2::DocumentComplete事件

2、在DWebBrowserEvents2::DocumentComplete事件中根据IWebBrowser2::Document获得IHTMLDocument2

3、IHTMLDocument2::forms得到IHTMLElementCollection

4、在IHTMLElementCollection中根据name、tagName、ID得到指定的IHTMLElement

5、从IHTMLElement得到IHTMLFormElement

6、执行IHTMLFormElement::submit

==================================================

要正确模拟表单提交使用 WinInet, 您需要发送一个指示正确 Content-Type 标头。 对于窗体, 正确 Content-Type 标题是:

Content-Type: application/x-www-form-urlencoded

如果没有指定 Content-Type 在许多情况下, 服务器不响应正确。 在之前将表单变量添加到 " Request.Form "

对象例如, IIS 3.0 的 ActiveServerPages 组件实际上检查专门用于应用表单 ' www 程序 / x -

urlencoded ' 此标题。 此 MIME / 内容类型指示请求数据是 - URL 编码窗体变量的列表。 URL 编码意味着空格字符

(ASCII 32) 被编码为 + ' ', 特殊字符如 ' ! ' 以十六进制形式编码为 21 % '。

下面是使用 MFCWinInet 类来模拟 FormPOST 请求的代码段:

CString strHeaders =

_T("Content-Type: application/x-www-form-urlencoded");

// URL-encoded form variables -

// name = "John Doe", userid = "hithere", other = "P&Q"

CString strFormData = _T("name=John+Doe&userid=hithere&other=P%26Q");

CInternetSession session;

CHttpConnection* pConnection =

session.GetHttpConnection(_T("ServerNameHere"));

CHttpFile* pFile =

pConnection->OpenRequest(CHttpConnection::HTTP_VERB_POST,

_T("FormActionHere"));

BOOL result = pFile->SendRequest(strHeaders,

(LPVOID)(LPCTSTR)strFormData, strFormData.GetLength());



没有 MFC, 相同的代码将转换为直 SDK 调用如下:

static TCHAR hdrs[] =

_T("Content-Type: application/x-www-form-urlencoded");

static TCHAR frmdata[] =

_T("name=John+Doe&userid=hithere&other=P%26Q");

static LPSTR accept[2]={"*/*", NULL};

// for clarity, error-checking has been removed

HINTERNET hSession = InternetOpen("MyAgent",

INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);

HINTERNET hConnect = InternetConnect(hSession, _T("ServerNameHere"),

INTERNET_DEFAULT_HTTP_PORT, NULL, NULL, INTERNET_SERVICE_HTTP, 0, 1);

HINTERNET hRequest = HttpOpenRequest(hConnect, "POST",

_T("FormActionHere"), NULL, NULL, accept, 0, 1);

HttpSendRequest(hRequest, hdrs, strlen(hdrs), frmdata, strlen(frmdata));

// close any valid internet-handles

===================================================



Get资料:

#include "winsock2.h"

#include "stdio.h"

#include "Wininet.h"

#pragma comment (lib,"Wininet.lib")

void main()

{

HINTERNET hNet = ::InternetOpen("Google page", //当HTTP协议使用时,这个参数随意赋值

PRE_CONFIG_INTERNET_ACCESS, //访问类型指示Win32网络函数使用登记信息去发现一个服务器。

NULL,

INTERNET_INVALID_PORT_NUMBER, //使用INTERNET_INVALID_PORT_NUMBER相当于提供却省的端口数。

0 //标志去指示使用返回句句柄的将来的Internet函数将"不"为回调函数发送状态信息

) ;

HINTERNET hUrlFile = ::InternetOpenUrl(hNet, //从InternetOpen返回的句柄

"http://www.google.com", //需要打开的URL

NULL, //用来向服务器传送额外的信息,一般为NULL

0, //用来向服务器传送额外的信息,一般为 0

INTERNET_FLAG_RELOAD, //InternetOpenUrl行为的标志

0) ; //信息将不会被送到状态回调函数

char buffer[1024] ;

DWORD dwBytesRead = 0;

BOOL bRead = ::InternetReadFile(hUrlFile, //InternetOpenUrl返回的句柄

buffer, //保留数据的缓冲区

sizeof(buffer),

&dwBytesRead); //指向包含读入缓冲区字节数的变量的指针;

//如果返回值是TRUE,而且这里指向0,则文件已经读到了文件的末尾。

::InternetCloseHandle(hUrlFile) ;

::InternetCloseHandle(hNet) ;

printf("%s", buffer);

}

WinInet API 的异步方式使用

作者邮箱:zhtrue@sina.com

异步方式并不是什么高深莫测的事物,WinInet API 更是大家耳熟能详。

如果你仔细看过 MSDN 和 internet 上关于 WinInet API 的文章,你会发现尽管在很多篇章中提到了异步方式的使用,但是大部分说明都只说可以使用,而没有说如何使用。尽管如此,还是有一些文章可以给我们很多的提示,我会在后面列出。

由于网络数据传输经常会消耗一定的时间,因此我们总是把这些可能消耗时间的操作放到一个单独的子线程,以免影响主线程正常的进行。可是当子线程发生 长时间阻塞的时候,主线程由于某种原因需要退出,我们通常希望子线程能在主线程退出前正常退出。这时主线程就不得不 wait 子线程,这样就导致主线程也被阻塞了。当然,主线程可以不 wait 子线程而自行退出,还可以使用 TerminateThread 强行终止子线程,但是这样的后果通常是不可预料的,内存泄漏或许是最轻的一种危害了。

使用异步方式是解决这类问题的正确手段,下面我们根据一个实例来分析一下 WinInet API 异步方式的使用方法和注意事项。

我们的例子完成这样的功能:给定一个 URL (如:http://www.sina.com.cn/), 使用 HTTP 协议下载该网页或文件。我们一共创建了三个线程:主线程负责创建下载子线程,并等待子线程返回消息;子线程则使用异步方式的 WinInet API 完成下载任务,并在各个阶段返回消息给主线程;子线程还会创建一个回调函数线程,其作用我们稍后解释。

实例代码中涉及到一些线程,消息,事件,错误处理的 API,由于不是我讨论的内容,就不仔细说明了。

1. 主线程工作流程
a. 创建下载子线程
m_hMainThread = ::CreateThread(NULL,
0,
AsyncMainThread,
this,
NULL,
&m_dwMainThreadID);

b. 等待子线程返回消息
MSG msg;
while (1)
{
::GetMessage(&msg, m_hWnd, 0, 0);

if (msg.message == WM_ASYNCGETHTTPFILE)
{ //子线程发回消息
switch(LOWORD(msg.wParam))
{
case AGHF_FAIL:
{
MessageBox(_T("下载行动失败结束!"));
return;
}
case AGHF_SUCCESS:
MessageBox(_T("下载行动成功结束!"));
return;
case AGHF_PROCESS:
//下载进度通知
break;
case AGHF_LENGTH:
//获取下载文件尺寸通知
break;
}
}

DispatchMessage(&msg);
}

2. 下载子线程工作流程
a. 使用标记 INTERNET_FLAG_ASYNC 初始化 InternetOpen
m_hInternet = ::InternetOpen(m_szAgent,
INTERNET_OPEN_TYPE_PRECONFIG,
NULL,
NULL,
INTERNET_FLAG_ASYNC);
起步并不费劲,也不难理解,MSDN 上说这样设置之后,以后所有的 API 调用都是异步的了。
警惕......
看起来好像很简单,但是会有无数的陷阱等着我们掉进去。

b. 设置状态回调函数 InternetSetStatusCallback
::InternetSetStatusCallback(m_hInternet, AsyncInternetCallback);
第一个陷阱就在这里等着你呢,文献[2]中提到使用一个单独的线程来进行这项设置,并解释说如果不这样会有潜在的影响,而在其他文档中却没有这样使用的例子。尽管看起来多余,并且增加了一些复杂度,我们还是先把这种方法写出来再讨论。子线程需要创建一个回调函数线程:
//重置回调函数设置成功事件
::ResetEvent(m_hEvent[0]);
m_hCallbackThread = ::CreateThread(NULL,
0,
AsyncCallbackThread,
this,
NULL,
&m_dwCallbackThreadID);
//等待回调函数设置成功事件
::WaitForSingleObject(m_hEvent[0], INFINITE);
回调函数线程的实现如下:
DWORD WINAPI CAsyncGetHttpFile::AsyncCallbackThread(LPVOID lpParameter)
{
CAsyncGetHttpFile * pObj = (CAsyncGetHttpFile*)lpParameter;

::InternetSetStatusCallback(pObj->m_hInternet, AsyncInternetCallback);

//通知子线程回调函数设置成功,子线程可以继续工作
::SetEvent(pObj->m_hEvent[0]);

//等待用户终止事件或者子线程结束事件
//子线程结束前需要设置子线程结束事件,并等待回调线程结束
::WaitForSingleObject(pObj->m_hEvent[2], INFINITE);
return 0;
}
确实复杂了很多吧,虽然我试验的结果发现两种设置方法都能正确工作,但是确实发现了这两种设置方法产生的一些不同效果,遗憾的是我没有弄清具体的原因。我推荐大家使用后一种方法。

c. 打断一下子线程的流程,由于回调函数和上一部分的关系如此密切,我们来看看它的实现
void CALLBACK CAsyncGetHttpFile::AsyncInternetCallback(
HINTERNET hInternet,
DWORD dwContext,
DWORD dwInternetStatus,
LPVOID lpvStatusInformation,
DWORD dwStatusInformationLength)
{
CAsyncGetHttpFile * pObj = (CAsyncGetHttpFile*)dwContext;
//在我们的应用中,我们只关心下面三个状态
switch(dwInternetStatus)
{
//句柄被创建
case INTERNET_STATUS_HANDLE_CREATED:
pObj->m_hFile = (HINTERNET)(((LPINTERNET_ASYNC_RESULT)
(lpvStatusInformation))->dwResult);
break;
//句柄被关闭
case INTERNET_STATUS_HANDLE_CLOSING:
::SetEvent(pObj->m_hEvent[1]);
break;
//一个请求完成,比如一次句柄创建的请求,或者一次读数据的请求
case INTERNET_STATUS_REQUEST_COMPLETE:
if (ERROR_SUCCESS == ((LPINTERNET_ASYNC_RESULT)
(lpvStatusInformation))->dwError)
{ //设置句柄被创建事件或者读数据成功完成事件
::SetEvent(pObj->m_hEvent[0]);
}
else
{ //如果发生错误,则设置子线程退出事件
//这里也是一个陷阱,经常会忽视处理这个错误,
::SetEvent(pObj->m_hEvent[2]);
}
break;
}
}

d. 继续子线程的流程,使用 InternetOpenUrl 完成连接并获取下载文件头信息
//重置句柄被创建事件
::ResetEvent(m_hEvent[0]);
m_hFile = ::InternetOpenUrl(m_hInternet,
m_szUrl,
NULL,
NULL,
INTERNET_FLAG_DONT_CACHE | INTERNET_FLAG_RELOAD,
(DWORD)this);
if (NULL == m_hFile)
{
if (ERROR_IO_PENDING == ::GetLastError())
{
if (WaitExitEvent())
{
return FALSE;
}
}
else
{
return FALSE;
}
}
等我们把 WaitExitEvent 函数的实现列出在来再解释发生的一切:
BOOL CAsyncGetHttpFile::WaitExitEvent()
{
DWORD dwRet = ::WaitForMultipleObjects(3, m_hEvent, FALSE, INFINITE);
switch (dwRet)
{
//句柄被创建事件或者读数据请求成功完成事件
case WAIT_OBJECT_0:
//句柄被关闭事件
case WAIT_OBJECT_0+1:
//用户要求终止子线程事件或者发生错误事件
case WAIT_OBJECT_0+2:
break;
}
return WAIT_OBJECT_0 != dwRet;
}
在 这里我们终于看到异步方式的巨大优势了,InternetOpenUrl 函数要完成域名解析,服务器连接,发送请求,接收返回头信息等任务,异步方式中 InternetOpenUrl 并不等待成功创建了 m_hFile 才返回,我们看到 m_hFile 是可以在回调函数中赋值的。如果 InternetOpenUrl 的返回值为 NULL 并且 GetLastError 返回 ERROR_IO_PENDING,我们使用 WaitForMultipleObjects 来等待请求的成功完成,这样主线程就有机会在这个等待过程中终止子线程的操作。我真是迫不及待的想把主线程如何强行终止子线程的代码列出来了:
//设置要求子线程结束事件
::SetEvent(m_hEvent[2]);
//等待子线程安全退出
::WaitForSingleObject(m_hMainThread, INFINITE);
//关闭线程句柄
::CloseHandle(m_hMainThread);
哈哈,不需要使用 TerminateThread 终止线程,一切都是安全的,可预料的。
我 们再考虑一种情况,这种情况好得超乎你的想象,InternetOpenUrl 返回了一个非空的 m_hFile 怎么办?呵呵,这说明 InternetOpenUrl 已经成功创建了一个 m_hFile,并且没有发生任何阻塞,都不用等待任何事件,直接继续下一步吧。
最后需要说明得是,InternetOpenUrl 的最后一个参数会被作为回调函数的第二个参数使用。并且哪怕在回调函数中不需要这个参数,这个值你也不能设置为 0,否则 InternetOpenUrl 将不会按照异步的方式工作。
到这里,我们已经将 WinInet API 的异步方式使用的关键部分都展示了,你应该可以使用 WinInet API 的异步方式写出你自己的应用了。不过还是让我们继续完成这个实例的其他部分。

e. 使用 HttpQueryInfo 分析头信息
DWORD dwStatusSize = sizeof(m_dwStatusCode);
if (FALSE == ::HttpQueryInfo(m_hFile,
HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER,
&m_dwStatusCode,
&dwStatusSize,
NULL)) //获取返回状态码
{
return FALSE;
}
//判断状态码是不是 200
if (HTTP_STATUS_OK != m_dwStatusCode)
{
return FALSE;
}
DWORD dwLengthSize = sizeof(m_dwContentLength);
if (FALSE == ::HttpQueryInfo(m_hFile,
HTTP_QUERY_CONTENT_LENGTH | HTTP_QUERY_FLAG_NUMBER,
&m_dwContentLength,
&dwLengthSize,
NULL)) //获取返回的Content-Length
{
return FALSE;
}
...//通知主线程获取文件大小成功
需要说明的是 HttpQueryInfo 并不进行网络操作,因此它不需要进行异步操作的处理。

f. 使用标记 IRF_ASYNC 读数据 InternetReadFileEx
//为了向主线程报告进度,我们设置每次读数据最多 1024 字节
for (DWORD i=0; i {
INTERNET_BUFFERS i_buf = {0};
i_buf.dwStructSize = sizeof(INTERNET_BUFFERS);
i_buf.lpvBuffer = new TCHAR[1024];
i_buf.dwBufferLength = 1024;

//重置读数据事件
::ResetEvent(m_hEvent[0]);
if (FALSE == ::InternetReadFileEx(m_hFile,
&i_buf,
IRF_ASYNC,
(DWORD)this))
{
if (ERROR_IO_PENDING == ::GetLastError())
{
if (WaitExitEvent())
{
delete[] i_buf.lpvBuffer;
return FALSE;
}
}
else
{
delete[] i_buf.lpvBuffer;
return FALSE;
}
}
else
{
//在网络传输速度快,步长较小的情况下,
//InternetReadFileEx 经常会直接返回成功,
//因此要判断是否发生了用户要求终止子线程事件。
if (WAIT_OBJECT_0 == ::WaitForSingleObject(m_hEvent[2], 0))
{
::ResetEvent(m_hEvent[2]);
delete[] i_buf.lpvBuffer;
return FALSE;
}
}
i += i_buf.dwBufferLength;
...//保存数据
...//通知主线程下载进度
delete[] i_buf.lpvBuffer;
}
这里 InternetReadFileEx 的异步处理方式同 InternetOpenUrl 的处理方式类似,我没有使用 InternetReadFile 因为它没有异步的工作方式。

g. 最后清理战场,一切都该结束了
//关闭 m_hFile
::InternetCloseHandle(m_hFile);
//等待句柄被关闭事件或者要求子线程退出事件
while (!WaitExitEvent())
{
::ResetEvent(m_hEvent[0]);
}
//设置子线程退出事件,通知回调线程退出
::SetEvent(m_hEvent[2]);
//等待回调线程安全退出
::WaitForSingleObject(m_hCallbackThread, INFINITE);
::CloseHandle(m_hCallbackThread);
//注销回调函数
::InternetSetStatusCallback(m_hInternet, NULL);
::InternetCloseHandle(m_hInternet);
...//通知主线程子线程成功或者失败退出


实例中,我们建立一个完整的 HTTP 下载程序,并且可以在主线程中对下载过程进行完全的监控。我们使用了 WinInet API 中的这些函数:
InternetOpen
InternetSetStatusCallback
InternetOpenUrl
HttpQueryInfo
InternetReadFileEx
InternetCloseHandle
其中 InternetOpenUrl 和 InternetReadFileEx 函数是按照异步方式工作的,文献[4]中列出了可以按照异步方式工作的 API:
FtpCreateDirectory
FtpDeleteFile
FtpFindFirstFile
FtpGetCurrentDirectory
FtpGetFile
FtpOpenFile
FtpPutFile
FtpRemoveDirectory
FtpRenameFile
FtpSetCurrentDirectory
GopherFindFirstFile
GopherOpenFile
HttpEndRequest
HttpOpenRequest
HttpSendRequestEx
InternetConnect
InternetOpenUrl
InternetReadFileEx


参考文献:
1. http://www.codeproject.com/internet/asyncwininet.asp
2. MSDN:
3. MSDN:
4. MSDN:

查询的结果网页

问题描述如下:
我想从www.dymeteo.com上取得查询的结果网页,步骤如下:登陆主页后->点击最新
天气实状链接后->在弹出的网页的中输入我想查询的城市,点查询出现查询结果.。现在
我想使用程序指定城市自动抓取该网页,那么我该如何发送何种命令才能取得这个结果,
也就是说我发这个命令串与我在该主页上输入城市点查询的功能是一样的????,
有没有什么工具可以在本地查看我在执行这个动作后其发出的结果串????

先HttpOpenRequest得到hRequest,open参数指定"POST"

然后HttpAddRequestHeaders加入"Content-Type: application/x-www-form-urlencoded\r\n"

然后HttpSendRequest指定lpPostData诸如"lname=Doe&fname=John"

然后InternetReadFile循环

最后CloseHandle

2009年4月20日星期一

wxwidgets的消息处理机制

wxwidgets的消息处理机制比较灵活,效率也比较高,这里只研究一下最基本的东西。

自定义一个消息以前的用法是
BEGIN_DECLARE_EVENT_TYPES()
DECLARE_EVENT_TYPE(, )
END_DECLARE_EVENT_TYPES()
现在的用法是
DEFINE_EVENT_TYPE()

消息处理也很容易理解
BEGIN_EVENT_TABLE(窗口类,基类)
EVT_BUTTON(窗口标识,处理函数)
........
END_EVENT_TABLE()
如果是自定义消息就要看处理函数的类型是怎样的。
如果处理函数是wxEventHandler类型直接使用EVT_CUSTOM来处理,如果处理函数类型是wxCommandEventHandler,就要使用EVT_CUSTOM来处理。

下面的例子在http://blog.csdn.net/kese/archive/2007/09/08/1776985.aspx例子 的基础上改造出来的

wxButtonStudio.cpp



#include <wx/wx_gch.h>

class wxButtonStudio : public wxApp
{
public:
bool OnInit();
};

class wxButtonFrame : public wxFrame
{
public:
wxButtonFrame(wxWindow* parent,const wxWindowID id,const wxString& title);
~wxButtonFrame();
wxChoice* choice;
wxButton* button;
wxButton* button2;


void OnButtonClick(wxCommandEvent& event);
void OnButton2Click(wxCommandEvent& event);
private:
DECLARE_EVENT_TABLE()
};

DEFINE_EVENT_TYPE(wxEVT_CUSTOM1)

IMPLEMENT_APP(wxButtonStudio)

enum
{
ID_BUTTON1,
ID_BUTTON2,
ID_CHOICE1
};

BEGIN_EVENT_TABLE(wxButtonFrame,wxFrame)
EVT_BUTTON(ID_BUTTON1,wxButtonFrame::OnButtonClick)
EVT_BUTTON(ID_BUTTON2,wxButtonFrame::OnButton2Click)
EVT_COMMAND(ID_BUTTON1,wxEVT_CUSTOM1,wxButtonFrame::OnButtonClick)
END_EVENT_TABLE()

bool wxButtonStudio::OnInit()
{
wxButtonFrame* frame = new wxButtonFrame((wxWindow*)NULL,wxID_ANY,_T("ButtonFrame"));
frame->Show(true);
return true;
}

wxButtonFrame::wxButtonFrame(wxWindow* parent,const wxWindowID id,const wxString& title)
: wxFrame(parent,id,title,wxDefaultPosition,wxDefaultSize,wxDEFAULT_FRAME_STYLE)
{
choice = new wxChoice(this,ID_CHOICE1);
choice->Append(_T("A"));
choice->Append(_T("B"));
choice->Append(_T("C"));
choice->Append(_T("D"));
choice->Append(_T("E"));
choice->Append(_T("F"));
choice->Append(_T("G"));
button = new wxButton(this,ID_BUTTON1,_T("A Button"),wxPoint(200,0),wxSize(100,30));
button2 = new wxButton(this,ID_BUTTON1,_T("B Button"),wxPoint(200,60),wxSize(100,30));
}

wxButtonFrame::~wxButtonFrame()
{
}

void wxButtonFrame::OnButtonClick(wxCommandEvent& event)
{
if (choice->GetCurrentSelection() < (int)choice->GetCount() - 1)
choice->Select(choice->GetCurrentSelection() + 1);
else
choice->Select(-1);
}

void wxButtonFrame::OnButton2Click(wxCommandEvent& event)
{
::SendMessageA((HWND)button->GetHWND(),wxEVT_CUSTOM1,0,0);
}

2009年4月19日星期日

如何使用gcc编译器?

 
如何使用gcc编译器?
 
如何使用gcc编译器?2007-05-18 16:08
GCC rules
你 能想象使用封闭源代码的私有编译器编译自由软件吗?你怎么知道编译器在你的 可执行文件中加入了什么?可能会加入各种后门和木马。Ken Thompson是一个著名 的黑客,他编写了一个编译器,当编译器编译自己时,就在'login'程序中留下后门 和永久的木马。请到 这里 阅读他对 这个杰作的描述。幸运的是,我们有了gcc。当你进行 configure; make; make install 时, gcc在幕后做了很多繁重的工作。如何才能让gcc为我们工作呢?我们将开始编写一个纸牌游戏, 不过我们只是为了演示编译器的功能,所以尽可能地精简了代码。 我们将从头开始一步一步地做,以便理解编译过程,了解为了制作可执行文件需要 做些什么,按什么顺序做。我们将看看如何编译C程序,以及如何使用编译选项 让gcc按照我们的要求工作。步骤(以及所用工具)如下: 预编译 (gcc -E), 编译 (gcc), 汇编 (as),和 连接 (ld)。
开始...
首先,我们应该知道如何调用编译器。实际上,这很简单。我们将从那个著名的第一个C程序开始。 (各位老前辈,请原谅我)。

[pre]#include <stdio.h>

int main()


{
printf("Hello World!n");
}
[/pre]把这个文件保存为 game.c。 你可以在命令行下编译它:
[pre]gcc game.c
[/pre]在默认情况下,C编译器将生成一个名为 a.out 的可执行文件。 你可以键入如下命令运行它: [pre]./a.out

Hello World

[/pre]每一次编译程序时,新的 a.out 将覆盖原来的程序。你无法知道是哪个 程序创建了 a.out。我们可以通过使用 -o 编译选项,告诉 gcc我们想把可执行文件叫什么名字。我们将把这个程序叫做 game,我们 可以使用任何名字,因为C没有Java那样的命名限制。 [pre]gcc -o game game.c
game

Hello World

[/pre] 到现在为止,我们离一个有用的程序还差得很远。如果你觉得沮丧,你可以想一想我们 已经编译并运行了一个程序。因为我们将一点一点为这个程序添加功能,所以我们必须 保证让它能够运行。似乎每个刚开始学编程的程序员都想一下子编一个1000行的程序, 然后一次修改所有的错误。没有人,我是说没有人,能做到这个。你应该先编一个可以 运行的小程序,修改它,然后再次让它运行。这可以限制你一次修改的错误数量。另外, 你知道刚才做了哪些修改使程序无法运行,因此你知道应该把注意力放在哪里。这可以 防止这样的情况出现:你认为你编写的东西应该能够工作,它也能通过编译,但它就是 不能运行。请切记,能够通过编译的程序并不意味着它是正确的。
下一步为我们的游戏编写一个头文件。头文件把数据类型和函数声明集中到了一处。 这可以保证数据结构定义的一致性,以便程序的每一部分都能以同样的方式看待一切事情。
[pre]#ifndef DECK_H
#define DECK_H

#define DECKSIZE 52

typedef struct deck_t
{
int card[DECKSIZE];
/* number of cards used */
int dealt;
}deck_t;