C/C++全局变量多线程并发访问临界锁(Critical Section Lock)

因为之前一直习惯于C语言编程,对C++的一些使用方式没有在意过,在阅读一些代码后渐渐的发现了C++的一些便捷性,这种便捷性在一定程度上降低了我们的编码量,对于全局变量多线程并发访问一般可以通过临界区(Critical Section)实现,比如在C语言中形如以下代码:

#include <windows .h>
#define THREAD_NUM 5
 
CRITICAL_SECTION g_cs;
int              g_nResource;
 
DWORD WINAPI ThreadProc(LPVOID lpParam)  
{  
    EnterCriticalSection(&g_cs);
    printf("Thread id %d Resource Count %d\n", 
                GetCurrentThreadId(), g_nResource); 
    g_nResource++;
 
    LeaveCriticalSection(&g_cs);
    return 0;  
}  
 
int main(void)
{
    HANDLE hThreads[THREAD_NUM] = {0};
    InitializeCriticalSection(&g_cs);
 
    for (i = 0; i < THREAD_NUM; i++)
        hThreads[i] = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
 
    WaitForMultipleObjects(THREAD_NUM, hThreads, TRUE, INFINITE);
    DeleteCriticalSection(&g_cs);
    return 0;
}

继续阅读

Shell_GetImageLists使GetOpenFileName/SHBrowseForFolder导致程序崩溃(Crash)

最近在用C/C++开发一个小项目,文章好久没更新,跟大家打个招呼。今天要介绍的是Win32桌面程序开发的问题,当然我的博客关于Windows程序开发介绍得还是比较少的,主要因为我做Windows开发的时候由于博客不稳定,一直没有更新这类文章,当博客稳定下来后又主打脚本或者Web编程,所以相关内容比较少,以后慢慢增加丰富吧。

今天的问题是一个我遇到的程序Bug,我设计的某程序,其中用到了系统的文件图标,查阅Win32 API后得知可以使用Shell_GetImageLists这个API获取,比如说获取16*16大小的系统图标组可以如下编写:

HIMAGELIST himl;
Shell_GetImageLists(NULL, &himl);

继续阅读

Visual Studio 2010不支持Windows Mobile(PPC)的开发

原来用的是装有Windows Mobile 5.0的多普达(Dopod)手机,也开发了几个小程序,后来也刷新了Windows 6.1和6.5的ROM,不过由于配置太低了,觉得还是6.1版本的能凑合着用吧,再后来觉得WM系统不是很稳定的,于是就换用最简单的诺基亚(Nokia)手机了,诺基亚简单耐用耐摔,基本符合目前要求。昨天正好要群发短信,于是把老的多普达翻了出来。今天突发奇想,能不能用VS2010开发WM程序呢?

正好有几个用VS2008做的WM程序项目,于是用VS2010打开,结果要安装SDK,于是屁颠屁颠的跑到微软网站上下载400多M的SDK,安装时提示找不到Visual Studio 2005。额,难道装不上?于是搜索谷歌,然后杯具了。

MSDN社区论坛上找到这样一个帖子《windows mobile 6 standard sdk not detecting visual studio 2010 beta installed…..》,然后根据YiChun Chen的回复得知Windows Mobile 6 SDK仅能够支持VS2008或者VS2005平台,目前为止不能支持VS2010。

然后这篇新闻《Microsoft drops support for Windows Mobile 6.X in Visual Studio 2010, Windows Phone 7 coming soon》彻底击碎了我残缺的梦想,看来想在VS2010上开发WM是不可能的了。

继续阅读

VC开发中HEAP CORRUPTION DETECTED错误

今天在VS2010调试项目中出现的问题,通过调用free出现了这个错误。一般VC的HEAP CORRUPTION DETECTED这类错误只有在开发的DEBUG版本上出现,Realse版本可能看不到这个错误,有时甚至软件程序还能正常运行,但是千万不能被表象所蒙蔽,随着时间的流逝,程序会因为一些莫名其妙的问题而崩溃,所以在Debug版本上出现这个问题是有其道理的,这里无非是对内存的操作出现了问题,内存经典的操作就是通过调用malloc申请内存,以及调用free释放内存,微软在调试模式下帮我们修改了这两个函数,特别是malloc多分配了一些空间,这些空间就是用于检测内存泄漏(Memory Leaks)和内存损坏(Memory damage)等一系列问题的。所以一旦出现这些问题,Debug版本下能立刻反应过来,告知HEAP CORRUPTION DETECTED以及详细原因并中止程序执行。不过我们如果点击调试,VC将跟踪到SDK的malloc实现中去,不是我们自己的程序问题点,这是比较头疼的地方。

首先出现内存泄漏,一般是内存泄漏不会出现这么明显的中断,所以判断是否因为分配内存损坏的原因,经典的是“野指针”问题,因为free释放指针指定的空间后不能置空指针,所以该指针就变成了野指针,其所指向的地址不是我们所期望的,如果这个时候调用free将造成内存损坏,Realse模式下可能会导致程序异常中止,Debug模式下就会出现HEAP CORRUPTION DETECTED的错误。

鉴于此我检查了代码,并没有找到类似问题,这时我的注意力集中在下面图片指示的一段文字上:

继续阅读

Visual Studio 2010显示代码行号

默认的Visual Studio 2008(VS2008)或VS2010代码编辑器是不显示行号的,这给我们定位代码带来了麻烦,下面简单介绍一下如何显示代码行号。

依次选择工具,选项,然后选择要显示行号的语言,比如说这里选择C#,然后勾选“显示”下面的“行号”复选框,然后确认即可。

继续阅读

C#利用反射(Reflection)进行SHA1和MD5的哈希(Hash)加密

最近在ASP.NET项目中用到的,主要是对用户密码进行哈希加密,传统编程进行的两种哈希加密方式一般如下:

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
using System;
using System.Text;
using System.Security.Cryptography;
 
namespace ConsoleApplication1
{
    class Program
    {
        public static string ComputeSHA1Hash(string str)
        {
            using (SHA1CryptoServiceProvider
                sha1Csp = new SHA1CryptoServiceProvider())
            {
                str = Convert.ToBase64String(
                        sha1Csp.ComputeHash(
                        Encoding.UTF8.GetBytes(str)
                        )
                    );
                sha1Csp.Clear();
            }
            return str;
        }
 
        public static string ComputeMD5Hash(string str)
        {
            using (MD5CryptoServiceProvider
                md5Csp = new MD5CryptoServiceProvider())
            {
                str = Convert.ToBase64String(
                        md5Csp.ComputeHash(
                        Encoding.UTF8.GetBytes(str)
                        )
                    );
                md5Csp.Clear();
            }
            return str;
        }
 
        static void Main(string[] args)
        {
            System.Console.WriteLine(ComputeSHA1Hash(@"http://wangye.org"));
            System.Console.WriteLine(ComputeMD5Hash(@"http://wangye.org"));
        }
    }
}

通过代码可以很明显的看出,我们主要引入System.Security.Cryptography的命名空间,然后调用其SHA1CryptoServiceProvider进行SHA1哈希或者调用MD5CryptoServiceProvider进行MD5哈希,当然由于哈希所需要的参数是字节型的,所以还需要将字符串进行一次字节编码,为了更好的兼容性,我们这里选择Encoding.UTF8.GetBytes进行UTF8编码,然后将哈希编码好的字节码再转换为Base64,至此整个哈希编码完成。

大家可能看到这两个函数比较类似,有没有什么办法将它们整合一下,变得更通用呢?对了,你可能想到了反射(Reflection),确实我们可以引入反射机制,从而让我们的代码更精简,更通用。反射功能需要引入System.Reflection命名空间,然后查找SHA1CryptoServiceProvider和MD5CryptoServiceProvider的定义,发现其共同继承了HashAlgorithm这个类,这么说这个类可以作为一个通用的类型。Assembly.GetAssembly().CreateInstance()的方式创建对象,那么GetAssembly()的参数类型该如何获得呢?我们可以通过System.Type.GetType(“命名空间名称”)这个方法得到,比如说System.Type.GetType(“System.Security.Cryptography.SHA1CryptoServiceProvider”)将得到SHA1CryptoServiceProvider的类型,那么SHA1就可以独立出来,就像System.Type.GetType(“System.Security.Cryptography.”+param+”CryptoServiceProvider”),param如果是SHA1,那么就是SHA1的Provider,如果是MD5,那么就是MD5的Provider。恩,先简单介绍到这里,具体的代码如下:

继续阅读

VB利用键盘勾子实现按键消息截获

好久不用VB写程序了,但是不得不承认VB的简单和高开发效率,这点比C/C++要好很多,尤其是一些开发周期短,实用性较强的软件。近期应单位要求,开发个××系统(名称保密),然后有个功能就是截获按键,实现特殊的按键热键的功能,首先想到的就是SetWindowsHookEx、CallNextHookEx 和UnhookWindowsHookEx,是的,想法很好,但是在实际运用时发现这几个C/C++下好用的函数老是容易导致VB崩溃,有时连IDE一起挂了,纠结了,因为这个系统最大的要求就是稳定,不能中途崩溃,为此我还特地建立了崩溃恢复机制,好了废话不说,这个问题总是要解决的,网上找到的一类代码比较混杂,大多是有问题的,最终我在vbaccelerator找到了个稳定可行的版本,那就是《Win32 Hooks in VB – The vbAccelerator Hook Library》这篇文章所介绍的Hook Library[下载地址][源代码]

使用方法很简单,下载那个ActiveX DLL,然后VB引用,然后在窗体级代码中输入:

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
Implements IWindowsHook
 
Private Sub Form_Load()
   InstallHook Me, WH_JOURNALRECORD
End Sub
 
Private Sub Form_QueryUnload( _
      Cancel As Integer, UnloadMode As Integer)
   RemoveHook Me, WH_JOURNALRECORD
End Sub
 
Private Function IWindowsHook_HookProc( _
      ByVal eType As vbalWinHook.EHTHookTypeConstants, _
      ByVal nCode As Long, _
      ByVal wParam As Long, _
      ByVal lParam As Long, _
      bConsume As Boolean) As Long
 
   If (eType = WH_JOURNALRECORD) Then
 
      Dim cEvent As cJournallParam
      Set cEvent = JournalRecordlParam(lParam)
 
      ' cEvent now contains information about
      ' the Journal record message.
 
   End If
 
End Function

当然《Using a Journal Record Hook to Capture Mouse and Key Events from any System Window》给出了一个完整的示例,大家不妨参考一下。

后来发现一个问题,那就是Visual Basic的ListView控件老是接收到我们要处理的按键,原本是想要处理的按键被按键勾子截获后就被“吃掉”不再继续传递,但是即使将IWindowsHook_HookProc的bConsume设为True也无济于事,后来我想起了以前看到的一个简单的办法,那就是KeyPress,看下面的代码:

1
2
3
4
Private Sub ListView1_KeyPress(KeyAscii As Integer)
  ' 如果按下字母B,则“吃掉”这个按键
  If KeyAscii = vbKeyB Then KeyAscii = 0
End Sub

上述代码中,如果接收到字母则将KeyAscii置0,这样控件本身就不再处理这个按键了。

VC++中忽略所有默认库纯Win32 API编译及链接

原文发表于2008年10月15日 已略作修改

我们在用VC++编写Windows程序的时候可能会发现一般可执行体(.EXE)的文件体积都比较大,于是非常羡慕那些使用Win32汇编编写程序的人,因为他们编写的可执行文件非常小。其实应用程序的体积是一方面,另外应用程序的部署环境则是需要注意的另一方面,这方面我深有体会,曾经使用Visual Studio 2008编译过一个C++的Win32程序,本地测试正常,但是部署到客户机时,出现缺少什么动态库,于是还要安装Visual C++ 2008可再发行组件包(Visual C++ 2005 Redistributable Package),这给软件部署带来了一定的麻烦,另外对于一个功能比较简单的程序,安装如此的组件包,可能心里会不好受,我们希望对于一些比较简单的应用程序可以直接调用系统提供的API,从而降低部署程序的复杂度。

其实对于VC++我们可以采用忽略所有默认库的方式避免编译器引入不必要的动态链接库,当然你可以使用如下的预编译宏。

#pragma comment(linker, "/nodefaultlib")

实际上,我们还需要在属性的 连接器->清单文件 将 生成清单 改为 否;然后选择 清单工具->输入和输出 将嵌入清单改为否;在C/C++中选择代码生成将缓冲区安全检查改为否(/GS-),否则编译会出现一个错误,设定程序的主入口点。注意上述配置一般在Release下,生成文件也在Release下编译链接,Debug可能无法使用,如果需要防止Debug模式编译,可以使用如下宏命令:

#ifdef _DEBUG   
#error Debug is disabled   
#endif    //    _DEBUG

另外对于函数入口的重新定义可以使用如下宏以代替属性配置。

#pragma comment(linker,"/ENTRY:wWinMainCRTStartup")

由于这里指定使用的宽字符(Unicode)API调用,所以我们将入口点定义为wWinMainCRTStartup,ANSI版建议定义为WinMainCRTStartup,另外Windows API有两个版本的API接口,ANSI版和Unicode版,ANSI版主要是为了兼容Windows 98等旧系统,一般ANSI API编译的程序体积比较小,但由于现在的基于NT的新的操作系统基本使用的Unicode API,相对而言,对于这些系统,Unicode API接口的速度要快于ANSI 接口的速度。

入口点做如下定义:

#include <windows.h>
HINSTANCE g_hInst    = NULL;
int WINAPI SimpleMain(VOID)
{
  return 0; 
}
VOID WINAPI wWinMainCRTStartup(VOID)
{
  g_hInst = GetModuleHandle(NULL);
  ExitProcess(SimpleMain());
}

理论上这样可以编译链接了,实际上还有很多错误,主要是链接方面的。由于我们使用了纯Windows API,所以不能使用memset,这时可以调用FillMemory(RtlFillMemory)、ZeroMemory(RtlZeroMemory)等等,这时编译会出现链接错误 是关于_memset符号链接的,实际上我们并没有使用memset,那这个错误是怎么产生的呢?其实微软重新定义了RtlFillMemory等API并使它们挂接到memset这个函数下,为了我们能够顺利编译,我们需要在自己的头文件里做如下处理。

#undef RtlFillMemory 
#undef RtlZeroMemory
extern "C" NTSYSAPI BOOL NTAPI 
RtlFillMemory ( VOID *Source1,DWORD Source2,BYTE Fill );
extern "C" NTSYSAPI BOOL NTAPI 
RtlZeroMemory( PVOID Destination, SIZE_T Length);
#define memset(Destination,Fill,Length) \
    RtlFillMemory((Destination),(Length),(Fill))

下面我们继续我们的编译,在链接这里又出现错误。

error LNK2001: 无法解析的外部符号 __imp__InitCommonControls@0
error LNK2001: 无法解析的外部符号 __imp__InitCommonControlsEx@4

跟踪后发现这两个函数InitCommonControls和InitCommonControlsEx由COMCTL32.dll导出,参考网上的解决方案,加入lib库。

#include <commctrl.h>
#pragma comment(lib, "comctl32.lib")

错误依旧!经过仔细寻找发现在属性配置里 链接器->输入 在附加依赖项里填入 comctl32.lib,编译,通过!

另外对于空间分配建议参考HeapAlloc、HeapFree等等API函数。

如果大家在操作过程中遇到什么问题欢迎讨论!

2011年4月5日更新

这篇文章在网上转载还是比较多的,部分童鞋在转载时没有署名,希望他们在看到这篇文章时及时更新出处,另外文章我略作修改,去掉了一个问题比较大的函数,就是处理命令行的那段,以免误导大家。

最后要说的是,除非你是做病毒木马,否则再小体积的程序势必带来运行的不稳定,请三思,如果对编译小体积的程序感兴趣的话,可以参考TCC : Tiny C Compiler

C/C++使用本机CryptoAPI实现随机字符串

原文发表于2008年9月13日 标题是《C/C++使用本地CryptoAPI实现随机字符串》

随机字符串的应用还是比较广泛的,比如随机的窗体标题(防止被FindWindow),随机的加密密匙,用户注册的随机密码等等。

实现一个随机字符串,很多朋友可能会想到标准的C/C++头文件提供的库函数,但是这通常是需要引入C/C++运行时支持库的,有时在做一个纯Win32 API的程序肯定要考虑程序代码的通用以及较小的体积的问题,下面我用代码说明一下如何使用本地的CryptoAPI实现随机字符串。

如果有朋友对于CryptoAPI不太了解的话可以去查看MSDN的相关说明,这套API为安全方面的加密解密等等提供了良好的支持。

由于CryptoAPI生成的不是随机的ASCII字符串,所以我们需要一个函数,其作用是将16进制转换为ASCII码。(这段代码如需在C语言环境下工作请将函数参数引用以指针方式实现)

继续阅读

VB与C语言的DLL新线程回调问题

原文由本人于2007年8月1日 18:52:59 发布于wyev.com(该域名已停用)。

不使用新线程回调成功,但使用新的线程回调VB出现<程序遇到问题需要关闭…>的错误。我为这个问题已经头疼几天了,问了微软的工程师,答复如下:

In vb6,Calling a function back from a different thread will cause access violations, so, Please prevent such designs.

看来还是要另想办法啦^_^。

—源代码如下—
MyTest.Dll (编译环境:VS2005)
MyTest.c

#include <stdio .h>
#include <stdlib .h>
#include <malloc .h>
#include <string .h>
#include <windows .h>
typedef void (__stdcall *FUNPTR)(BSTR pbstr);
typedef struct _TEST_PARA{
	long pfunA;
	long pfunB;
}UTESTMY;
 
DWORD __stdcall NewRun(UTESTMY *pAt){
	FUNPTR vbFunA,vbFunB;
	vbFunA=(FUNPTR)(pAt->pfunA);
	vbFunB=(FUNPTR)(pAt->pfunB);
	vbFunA(SysAllocString(L""回调A""));
	vbFunB(SysAllocString(L""回调B""));
	free(pAt);
	return 0;
}
//线程结构体传参数回调VB显示程序遇到问题需要关闭
void __stdcall TheRun(long pfunA,long pfunB){
	HANDLE hThread;
	DWORD dwThreadId;
	UTESTMY *pAt;
	pAt=(UTESTMY *)malloc(sizeof(UTESTMY));
	pAt->pfunA=pfunA;
	pAt->pfunB=pfunB;
	hThread = 
CreateThread(NULL,NULL,(LPTHREAD_START_ROUTINE)NewRun,pAt,0,&dwThreadId);
	WaitForSingleObject(hThread,INFINITE); 
	CloseHandle(hThread);
}
 
//下面这个在VB里调用是正常的
DWORD __stdcall ComRun(long pfunA,long pfunB){
	FUNPTR vbFunA,vbFunB;
	vbFunA=(FUNPTR)(pfunA);
	vbFunB=(FUNPTR)(pfunB);
	vbFunA(SysAllocString(L""回调A""));
	vbFunB(SysAllocString(L""回调B""));
	return 0;
}</windows></string></malloc></stdlib></stdio>

继续阅读