Windows下使用std::wifstream读取Unicode文本的方法:

std::locale loc("chs");				//windows下ok
	std::wcout.imbue(loc);
	// open as a byte stream
	std::wifstream wif("路径", std::ios::binary);
	std::codecvt_utf16<wchar_t, 0x10ffff, std::consume_header>* codecvtToUnicode = new std::codecvt_utf16 < wchar_t, 0x10ffff, std::consume_header >;
	if (wif.is_open())
	{
		// apply BOM-sensitive UTF-16 facet
		wif.imbue(std::locale(wif.getloc(), codecvtToUnicode));
		std::wstring wline;
		while (std::getline(wif, wline))
		{
			std::wstring convert;
			for (auto c : wline)
			{
				if (c != L'\0' && c != L'?')
					convert += c;
			}
			wcout << convert << endl;
		}
		wif.close();
		//delete codecvtToUnicode;     //new和delete,应该不用手动delete,在哪里delete都会崩溃(亲测)
	}

 

Windows下使用std::wifstream读取UTF-8文本的方法:

std::locale loc("chs");				//windows下ok
	std::wcout.imbue(loc);
	// open as a byte stream
	std::wifstream wif("路径", std::ios::binary);
	std::codecvt_utf8<wchar_t, 0x10ffff, std::consume_header>* codecvtToUnicode = new std::codecvt_utf8 < wchar_t, 0x10ffff, std::consume_header >;
	if (wif.is_open())
	{
		// apply BOM-sensitive UTF-8 facet
		wif.imbue(std::locale(wif.getloc(), codecvtToUnicode));
		std::wstring wline;
		while (std::getline(wif, wline))
		{
			std::wstring convert;
			for (auto c : wline)
			{
				if (c != L'\0' && c != L'?')
					convert += c;
			}
			wcout << convert << endl;
		}
		wif.close();
		//delete codecvtToUnicode;     //new和delete,应该不用手动delete,在哪里delete都会崩溃(亲测)
	}

 

 

在完成搜索工具项目的时候,给DuiLib的一个窗口ResizeClient的时候编辑框会失去焦点,我们将焦点想办法重新给到编辑框后,发现光标的位置总是会默认在最前面的位置,这大大的影响了程序的用户体验,通过查看DuiLib源码发现只有SetSel函数而没有GetSel函数,于是通过修改源码实现了一个。

通过查看SetSel的源码我们可以看到如下代码:

void CEditUI::SetSel(long nStartChar, long nEndChar)
{
	if( m_pWindow != NULL ) Edit_SetSel(*m_pWindow, nStartChar,nEndChar);
}

接着查看Edit_SetSel的源码如下:

#define Edit_SetSel(hwndCtl, ichStart, ichEnd)  ((void)SNDMSG((hwndCtl), EM_SETSEL, (ichStart), (ichEnd)))

接着查看SNDMSG的源码如下:

#define SNDMSG :: SendMessage

原来是通过发消息来实现的。

同时Edit_SetSel的上面一行代码就是:

#define Edit_GetSel(hwndCtl)                    ((DWORD)SNDMSG((hwndCtl), EM_GETSEL, 0L, 0L))

通过查询微软文档:EM_GETSEL消息

我们可以知道:返回值是一个从零开始的值,其中光标的起始位置在LOWORD中,而光标末尾的位置在HIWORD中。

所以我们可以完成我们的GetSel了。

WORD CEditUI::GetSel(){
      if(m_pWindow != NULL)
      return LOWORD(Edit_GetSel(*m_pWindow));
}

如果只是为了获得光标位置,这里使用LOWORD和HIWORD得到的值是相同的。

SQLite官方下载页只提供SQLite3.def和SQlite3.dll文件的下载,若使用VC++编程的话,还需要SQLite3.lib库文件,才能调用编译成功。我们可以使用 Visual C++ 提供的 X:\Program Files\Microsoft Visual Studio 11.0\VC\bin\lib.exe 程序生成 SQLite3.lib 库文件。

官方下载地址:http://www.sqlite.org/download.html

一、下载 SQLite3.def和SQlite3.dll 文件,并解压到如 D:\SQLite3\ 目录下。

二、运行 CMD, 输入:
"D:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\bin\lib.exe" /MACHINE:IX86 /DEF:D:\SQLite3\SQLite3.def /OUT:D:\SQLite3\SQLite3.lib
如下图所示:

三、回车后,成功生成 SQLite3.lib 和 SQLite3.exp 两个文件。如下图所示:

VS2015中配置使用SQLite3

下载SQLite源文件

  1. 新建Win32空项目。
  2. 项目属性
    a) 添加包含目录,即刚才下载解压后sqlite3.h所在路径。
    b) 添加库目录,即添加SQLite3.lib所在文件路径。

c) 链接器-输入-附加依赖项,输入SQLite3.lib。

注:SQLite3路径下文件如下图:

3.添加源文件,输入如下:

#include <stdio.h>
#include "sqlite3.h"

int main(int argc, char* argv[])
{
    sqlite3 *db;
    char *zErrMsg = 0;
    int rc;

    rc = sqlite3_open("test.db", &db);

    if (rc) {
        fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));

    }
    else {
        fprintf(stderr, "Opened database successfully\n");
    }
    sqlite3_close(db);

    return 0;
}

编译,不报错的话即环境配置正确。

今天在根据文件名的拓展名进行文件分类的时候,发现文件的拓展名是不区分大小写的,所以要根据拓展名进行简单分类的话,就需要一个string不区分大小写查找的方法。

size_t FindNoCase(string strSource, char* szTarget)
{
if(strSource.empty())
{
return string::npos;
}
string strSub = szTarget;
if (strSub.empty())
{
return string::npos;
}
for (string::iterator it = strSource.begin(); it != strSource.end(); ++it)
{
*it = tolower(*it);
}
for (string::iterator ite = strSub.begin(); ite != strSub.end(); ++ite)
{
*ite=tolower(*ite);//do not change szTarget context.
}
return strSource.find(strSub);
}

 

在C++中,system函数可以运行命令行,但是只能得到该命令行的int型返回值,并不能获得显示结果。例如system(“ls”)只能得到0或非0,如果要获得ls的执行结果,则要通过管道来完成的。首先用_popen(Linux下函数名为popen)打开一个命令行的管道,然后通过fgets获得该管道传输的内容,也就是命令行运行的结果。

VS2013下代码如下:

#include "stdafx.h"
#include<windows.h>
#include<string>
#include<iostream>
using namespace std;

std::string getCMD(char * cmd){
	FILE *file;
	char ptr[1024] = { 0 };
	char tmp[1024] = { 0 };
	strcat_s(ptr, cmd);
	string result = "";
	if ((file = _popen(ptr, "r")) != NULL)
	{
		while (fgets(tmp, 1024, file) != NULL){
			result = result + tmp;
		}
		_pclose(file);
	}
	return result;
}

int _tmain(int argc, _TCHAR* argv[])
{
	cout << getCMD("ping www.maplefan.com");
	system("pause");
}

 

上列代码在执行诸如ping命令这种需要等待的命令行的时候,会知道ping指令完全结束才一次输出信息。

如果想要像ping命令一样即时输出的话,可以在while循环里想办法输出即可。

CLabelUI等控件可以实现高亮,实现方法如下:

(1)将该控件的SetShowHtml()设置为true,即使用html的方式进行解析。

(2)在字符中加入下列想要的格式即可。让它自己去解析,完成绘制。

 

//   Bold:             <b>text</b>
//   Color:            <c #xxxxxx>text</c>  where x = RGB in hex
//   Font:             <f x>text</f>        where x = font id
//   Italic:           <i>text</i>
//   Image:            <i x y z>            where x = image name and y = imagelist num and z(optional) = imagelist id
//   Link:             <a x>text</a>        where x(optional) = link content, normal like app:notepad or http:www.xxx.com
//   NewLine           <n>
//   Paragraph:        <p x>text</p>        where x = extra pixels indent in p
//   Raw Text:         <r>text</r>
//   Selected:         <s>text</s>
//   Underline:        <u>text</u>
//   X Indent:         <x i>                where i = hor indent in pixels
//   Y Indent:         <y i>                where i = ver indent in pixels

按照上述样式,可进行绘制操作。

比如构造一个字符串设置颜色:

<c #ff00ff>test</c>

构造成类似这种字符串,控件会自动以HTML格式解析绘制。

最近Win32编程被编码折磨得很惨。。。记录一下两个转化函数。

wchar_t*转std::string:

std::string wchar_tToString(wchar_t *wchar){
	std::string szDst;
	wchar_t* wText = wchar;
	DWORD dwNum = WideCharToMultiByte(CP_OEMCP, NULL, wText, -1, NULL, 0, NULL, FALSE);
	char *psText;
	psText = new char[dwNum];
	WideCharToMultiByte(CP_OEMCP, NULL, wText, -1, psText, dwNum, NULL, FALSE);
	szDst = psText;
	delete[]psText;
	return szDst;
}

std::string转wchar_t*:

wchar_t* stringToWchar_t( std::string str){
	std::string temp = str;
	int len = MultiByteToWideChar(CP_ACP, 0, (LPCSTR)temp.c_str(), -1, NULL, 0);
	wchar_t *wszUtf8 = new wchar_t[len + 1];
	memset(wszUtf8, 0, len * 2 + 2);
	MultiByteToWideChar(CP_ACP , 0 , (LPCSTR)temp.c_str() , -1 , (LPWSTR)wszUtf8 , len);
	return wszUtf8;
}

 

最近的文件搜索项目使用到了SQLite3作为数据库来缓存本地的文件,而通过USN读取到NTFS盘上的文件(开发机器上大概40w个文件/文件夹)在Intel Core i7-6700上大概就需要7s左右的时间,而通过常规的SQLite提供的C++ API 将文件的这些信息插入到数据库的表中,大概需要30s的时间,于是开始摸索是否有办法可以提高插入的效率进而减少我们程序的后台文件搜索耗时线程所花费的时间。

慢速:直接使用SQLite的C++ API

int sqlite3_exec(  sqlite3*,    const char *sql,   int (*callback)(void*,int,char**,char**),   void *,   char **errmsg)

中速:显式开启事务

所谓”事务“就是指一组SQL命令,这些命令要么一起执行,要么都不被执行。在SQLite中,每调用一次sqlite3_exec()函数,就会隐式地开启了一个事务,如果插入一条数据,就调用该函数一次,事务就会被反复地开启、关闭,会增大IO量。如果在插入数据前显式开启事务,插入后再一起提交,则会大大提高IO效率,进而加数据快插入速度。

开启事务只需在上述代码的前后各加一句开启与提交事务的命令即可:

开启事务:

sqlite3_exec(db,"begin;",0,0,0);

提交事务:

sqlite3_exec(db,"commit;",0,0,0);

高速:关闭写同步(synchronous)

之前的速度仍然不能够接受,在有关讲解SQLite配置的资料中,看到了“写同步”选项。

在SQLite中,数据库配置的参数都由编译指示(pragma)来实现的,而其中synchronous选项有三种可选状态,分别是full、normal、off。这篇博客以及官方文档里面有详细讲到这三种参数的设置。简要说来,full写入速度最慢,但保证数据是安全的,不受断电、系统崩溃等影响,而off可以加速数据库的一些操作,但如果系统崩溃或断电,则数据库可能会损毁。

SQLite3中,该选项的默认值就是full,如果我们再插入数据前将其改为off,则会提高效率。如果仅仅将SQLite当做一种临时数据库的话,完全没必要设置为full。在代码中,设置方法就是在打开数据库之后,直接插入以下语句:

sqlite3_exec(db,"PRAGMA synchronous = OFF; ",0,0,0);

极速:

虽然写同步设为off后,速度又有小幅提升,但是仍然较慢。我又一次踏上了寻找提高SQLite插入效率方法的道路上。终于,我发现,SQLite执行SQL语句的时候,有两种方式:一种是使用前文提到的函数sqlite3_exec(),该函数直接调用包含SQL语句的字符串;另一种方法就是“执行准备”(类似于存储过程)操作,即先将SQL语句编译好,然后再一步一步(或一行一行)地执行。如果采用前者的话,就算开起了事务,SQLite仍然要对循环中每一句SQL语句进行“词法分析”和“语法分析”,这对于同时插入大量数据的操作来说,简直就是浪费时间。因此,要进一步提高插入效率的话,就应该使用后者。

“执行准备”主要分为三大步骤:

  1. 调用函数sqlite3_prepare_v2(),并且声明一个指向sqlite3_stmt对象的指针,该函数对参数化的SQL语句zSql进行编译,将编译后的状态存入ppStmt中。
  2. 调用函数 sqlite3_step(),这个函数就是执行一步(本例中就是插入一行),如果函数返回的是SQLite_ROW则说明仍在继续执行,否则则说明已经执行完所有操作。
  3. 调用函数 sqlite3_finalize(),关闭语句。
  4. 样例代码如下:
    int _tmain(int argc, _TCHAR* argv[])
    {
    	const int nCount = 500000;
    	sqlite3* db;
    	sqlite3_open("testdb.db", &db);
    	sqlite3_exec(db, "PRAGMA synchronous = OFF; ", 0, 0, 0);
    	sqlite3_exec(db, "drop table if exists t1", 0, 0, 0);
    	sqlite3_exec(db, "create table t1(id integer,x integer,y integer ,weight real)", 0, 0, 0);
    	clock_t t1 = clock();
    
    	sqlite3_exec(db, "begin;", 0, 0, 0);
    	sqlite3_stmt *stmt;
    	const char* sql = "insert into t1 values(?,?,?,?)";
    	sqlite3_prepare_v2(db, sql, strlen(sql), &stmt, 0);
    
    	for (int i = 0; i<nCount; ++i)
    	{
    		sqlite3_reset(stmt);
    		sqlite3_bind_int(stmt, 1, i);//bind第一个数据
    		sqlite3_bind_int(stmt, 2, i * 2);//bind第二个数据
    		sqlite3_bind_int(stmt, 3, i / 2);//bind第三个数据
    		sqlite3_bind_double(stmt, 4, i*i);//bind第四个数据
    		sqlite3_step(stmt);
    	}
    	sqlite3_finalize(stmt);
    	sqlite3_exec(db, "commit;", 0, 0, 0);
    	clock_t t2 = clock();
    	sqlite3_close(db);
    	std::cout << "cost time: " << (t2 - t1) / 1000. << "s" << std::endl;
    
    	system("pause");
    	return 0;
    }

     

综上所述啊,SQLite插入数据效率最快的方式就是:事务+关闭写同步+执行准备(存储过程),如果对数据库安全性有要求的话,就开启写同步。

在Win32中调试程序时经常需要输出调试信息以追踪数据流及程序运行状态。

当然,我们可以通过输出日志、文件等方式得到信息,但是控制台或许更方便、直观。

之前在使用基于Win32的ACLlib进行开发的时候,一直都可以显示一个控制台(也可以隐藏),感觉很方便。

那么,如何方便地在带GUI的Win32程序中,比如基于Duilib的Win32GUI程序中使用控制台进行调试输出?

具体操作流程如下:

  1. 打开控制台
  2. 重定向输出流至控制台
  3. 执行调试信息输出操作.

完整代码如下:

AllocConsole();
freopen("CONOUT$", "w", stdout);
std::cout << "This is a test info" << std::endl;

 

相关函数说明:

AllocConsole函数的功能是为当前的窗口程序申请一个Console窗口,其原型为

BOOL AllocConsole(void);

 

函数调用成功,返回非零值,调用不成功则返回0。

freopen函数用来替换一个流,或者说重新分配文件指针,以实现重定向。可重定向的流有:标准输入流、标准输出流或者标准错误流。其函数原型为

FILE *freopen(const char *path, const char *mode, FILE *stream);

其中”CONOUT$”是指代当前console的特殊字符串,”w”表明以written模式打开这个console,stdout指代标准输出流。

 

补充:

AllocConsole函数不能改变控制台窗口在屏幕上的位置、尺寸等属性。以下函数可以控制&获取控制台相关信息。

GetConsoleScreenBufferInfo   // 检索窗口大小,屏幕缓冲区大小及颜色属性
SetConsoleWindowInfo       // 改变控制台窗口大小
SetConsoleScreenBufferSize   // 改变控制台屏幕缓冲区大小
SetConsoleTextAttribute      // 设置颜色属性
SetConsoleTitle          // 设置控制台窗口标题
GetConsoleTitle          // 获取控制台窗口标题

临界区

一个临界区就是一段不会被中断的代码。

使用临界区共有四个函数。为了使用这些函数,你必须定义一个临界区对象,这个对象是一个类型为CRITICAL_SECTION的全部变量。比如,

CRITICAL_SECTION cs;

这个CRITICAL_SECTION数据类型是一个结构,但它的字段只被Windows内部使用。这个变量必须首先被程序中的一个线程通过如下方式初始化:

InitializeCriticalSection(&cs);

这创立了一个叫cs的临界区对象。这个函数的文档包括以下警告:“临界区对象不能被移动或复制。程序不可以修改它,并且必须把它当作不透明的对象。”说白了,这可以被理解为“不要改动或甚至读取它。”

在临界区对象被初始化后,一个线程通过调用以下函数来进入临界区:

EnterCriticalSection(&cs);

这时,我们就说这个线程拥有这个临界区对象。两个线程不能同时拥有同一个临界区。所以,如果一个线程进入了临界区,那么下一个线程在调用EnterCriticalSection来进入同一个临界区对象时会被挂起。这个函数直到第一个线程调用以下函数离开临界区时才会返回:

LeaveCriticalSection(&cs);

在这时,由于调用EnterCriticalSection而被挂起的第二个线程会拥有这个临界区,而且函数调用会返回,使得线程可以继续。

当程序不再需要临界区对象时,可以通过以下函数删除它:

DeleteCriticalSection(&cs);

这会释放为了维护临界区对象而分配的所有系统资源。