分类归档: Programming

编程编程编程。。。

Delphi数据库编程技巧

  我原来在万千的新闻组(news://news.webking.com.cn/)Delphi版上闲逛那段时间,发现经常有人提出一些数据库方面的技巧性问题,问题其实不难,但是要一个简单的解决方案,可能也破费思量,特别在此简单谈谈:

1. 如何动态设置ODBC源

很多时候程序员需要自动生成ODBC数据源,而不是指导客户"打开控制面板… … ",到底如何去做呢?相信很多人会选择编程修改注册表来实现这项功能,因为ODBC的详细信息全部存放在注册表的下述键值内:

"HKEY_LOCAL_MACHINESOFTWAREODBC"

修改一下ODBC的配置,通过前后的注册表比较,你可以发现一定的规律。在这里我只是想说,哥们,别这么犯傻了(如果我让你写一个通用的ODBC源处理程序,你得累死),用这个Windows ODBC API函数吧,

function SQLConfigDataSource(hwndParent: Integer; fRequest: Integer;

lpszDriverString: String; lpszAttributes: String): Integer;

stdcall;external ”ODBCCP32.DLL”;

了解数据库编程的朋友都知道,数据库的访问方式不论DAO、ADO、ODBC或是BDE或是其它第三方的数据库连接控件,归根结底,都是一些个函数集,只要你愿意,你可以编写出自己的数据库访问方式用以替代。深入研究这些底层函数,很多时候会为你提供相当地便利。

SQLConfigDataSource这个函数MSDN有详细的说明,我不想整段翻译下来让你扁我,我只是结合流行的SQL Server谈谈如何有技巧的调用该函数。其它的数据库大同小异。

SQLConfigDataSource(0, ODBC_ADD_SYS_DSN,”SQL Server”,
”DSN=Record_ODBC”+ chr(0) +
”Server=(local)”+ chr(0) +
”Database=master”+ chr(0) +
”Description=DragonPC SQLServer ODBC Source”+ chr(0));

这是我的Delphi程序中调用该函数的一个实例,第一个参数是父窗口句柄,设置为0则该函数不显示任何对话框。第二个参数是操作类型,你需要定义如下的操作类型常量:

Const
ODBC_ADD_DSN = 1; // Add a new user data source.
ODBC_CONFIG_DSN = 2; // Configure (modify) an existing user data source.
ODBC_REMOVE_DSN = 3; // Remove an existing user data source.
ODBC_ADD_SYS_DSN = 4; // Add a new system data source.
ODBC_CONFIG_SYS_DSN = 5; // Modify an existing system data source.
ODBC_REMOVE_SYS_DSN = 6; // Remove an existing system data source.

从名字我们知道,要添加ODBC源,我们需要调用的是ODBC_ADD_SYS_DSN 或是ODBC_ADD_DSN参数。第三个参数也没有什么好说的,我们添加的是SQL Server数据库的ODBC源,所以填入”SQL Server”参数,如果需要建立Excel文件的ODBC数据源,我们可以填入”Excel Files (*.xls)”,这些字符串参数相信各位同志在添加ODBC源时,已经多次见过。

关键的是第三个参数的设置,不同的数据库类型所支持的关键字是不一样的,这里仅仅就SQL Server所支持的关键字作一个简单说明:

DSN:你的ODBC数据源名称。

Server:你的数据库服务器名称,使用(local)指的是本地计算机安装的数据库。注:最新的SQL Server 2000支持一台计算机运行多个SQL Server服务,这个时候你需要指定SqlSever的InstanceName。

Address:指定SQL Server服务器的网络IP地址。

Database:指定默认数据库名称。

Language:指定默认语言。

Description:备注信息。

详细的参数和信息可以查阅微软网站的以下网址。

http://msdn.microsoft.com/library/psdk/dasdk/odch3kit.htm

http://msdn.microsoft.com/library/psdk/sql/od_odbc_c_99yd.htm

  2. 如何动态设置BDE别名

这个问题其实是考察程序员对BDE的 TSession组件的熟悉程度,一个数据库程序的建立,即使你没有显式的添加TSession组件,系统中依然存在一个名字为Session的 TSession对象,你可以在任何位置调用该对象的方法和属性。TSession类的很多方法可以帮助我们的应用程序获取系统BDE环境,下面介绍一个 代码片断用以添加一个BDE别名:

var
BDEList : TStringList ;
...
begin
...
BDEList := TStringList.Create () ;
try
Session.GetAliasNames(BDElist) ; // 获取系统所有BDE别名列表
if BDEList.IndexOf(”DragonPC”)= -1 then begin // 如果没有我们的BDE别名"DragonPC"
BDEList.Clear ;
BDEList.Add(”SERVER NAME=” + ”SQLServerName”)); // 数据库服务器名称
BDEList.Add(”DATABASE NAME=master”) ; // 默认数据库
BDEList.Add(”USER NAME=sa”); // 用户名
Session.AddAlias(”DragonPC”, ”MSSQL”, BDEList) ; // 添加一个MSSQL类型的BDE别名
ShowMessage(”系统即将建立BDE别名!”) ;
Session.SaveConfigFile() ; // 存储BDE配置
end;
finally
BDEList.Free ;
end;

这么简单,用户就可以随时建立、删除和修改BDE别名(有兴趣的朋友可以查看TSession组件的源代码,看看调用了哪些BDE函数)。另外像 DeleteAlias,ModifyAlias,GetDatabaseNames,GetDriverNames, GetStoredProcNames,GetTableNames,GetPassword等等TSession类的方法,使用起来非常简单,通过 Delphi的随机帮助,读者可以试着自己调用一下看看。通过对Session的灵活应用,再配合我下面将要提到的扑捉SQL异常的技巧,你完全可以写一 个媲美SQL Explorer的通用数据库查询工具。

  3. 如何扑捉运行SQL语句时的错误

老是有朋友在开发一些开放的数据库接口(比如Delphi的SQL Explorer工具)时发愁,既然是开放的,当然需要允许用户使用SQL语句访问数据库,这些还好办,一旦用户的运行SQL语句出现错误,程序员如何扑捉该异常呢?很简单,看看下面的函数:

Const
ExecSQLMode = 0 ;
OpenSQLMode = 1 ;
ResultRight = ”SQL query result is right” ;
...
function RunSql(RunQuery: TQuery; Sqls: TStringList; var ErrorMsg: string;
Mode: integer) : integer ;
begin
ErrorMsg := ResultRight ;
Result := 0 ;
try
RunQuery.DatabaseName := ”RecordDB” ;
RunQuery.SQL.Clear() ;
RunQuery.SQL.AddStrings(Sqls) ;
if Mode = ExecSQLMode then
RunQuery.ExecSQL()
else
RunQuery.Open() ;
except
on e:exception do
ErrorMsg := e.Message + #13 + #10 +”— 错误是俺发现的 —” ;
end;
end;

朋友看明白了吧,我的函数很简单,将SQL语句代码段作为参数传递给TQuery组件,通过设置查询方式(ExecSQLMode、 OpenSQLMode)来处理有结果集返回的数据查询语句(select)或是没有结果集返回的数据操作语言(update、delete、 insert、create等)。而异常的扑捉呢,我们扑捉所有异常的老祖宗Exception,因为Delphi的所有异常都是从Exception继 承下来的,这样一个简单的SQL语句运行和异常处理函数就完成了,一旦返回的ErrorMsg的值不是ResultRight,ErrorMsg就会返回 异常的信息,程序员就可以加以判断以处理不同的情况。

Read: 1089

[转]visual c++对大型数据文件的读取

  笔者前不久曾遇到一个问题,解决之后的经验愿与大家分享。问题是这样的,有一批数据文件,数据格式如下:

日期,开盘,最高,最低,收盘,成交量,成交金额

1996年5月13日,636.96,636.96,636.96,636.96,0,0,

1996年5月14日,641.61,641.61,641.61,641.61,0,0,

1996年5月15日,637.83,637.83,637.83,637.83,0,0,

………….

要求将数据填写到四张表中,以便作相应的分析。笔者开始用CFile和CStdioFile类的方法读取件。Cfile类提供了基于二进制流的文件操 作,功能类似于C语言中的fread()和fwrite()函数。CStdioFile提供了基于字符串流的文件操作,功能类似于C语言中fgets() 和fputs()函数。但是笔者发现,使用这两个类进行文件操作时,对于一次文件读写的数据量的大小必须限制在65535字节以内。究其原因是在VC中访 问大于65535字节的缓冲区需要Huge型指针,而在CFile和CStdioFile类中,使用的是Far型的指针。由于Far型指针不具有跨段寻址 的能力,因此限制了一次文件读写的长度小于65535字节。如果传递给CFile和CStdioFile两个类的成员函数的数据缓冲区的大小大于 65535字节的时候,VC就会产生ASSERT错误。

针对文件格式特点,笔者改用CArchive类进行读取如下:

CFile SourceFile;//数据文件

CString SourceData;//定义一临时变量保存一条记录

SourceFile.Open(…….);

CArchive ar(&SourceFile,CArchive::load);

while(NULL!=ar.ReadString(SourceData))//循环读取文件,直到文件结束

{

if(SourceData=="日期,开盘,最高,最低,收盘,成交量,成交金额"||SourceData=="")

continue;//跳过文件头部的提示信息

//分析并填充//

}

在进行分析时,笔者采取了逐步分析并修改的办法,过程如下:

int nYear;

CString Year= SourceData.Left(SourceData.Find("年"));//截取年前面的字符串

nYear=atoi(Year);//类型转换

SourceData=SourceData.Righ(SourceData.GetLength()-SourceData.Find("年")-2);//将年以及前面的字符删除。

重复上面分析过程,直到记录末尾。

通过上述方法,笔者成功地将文件读取并分析填充。

Read: 658

用拷贝钩子实现对文件夹的监控

本文原出处已不知。原作:webber84, 经ccrun(老妖)修改并在BCB在调试通过。
ICopyHook 是一个用于创建拷贝钩子处理程序COM接口,它决定一个文件夹或者打印机对象是否可以被移动,拷贝,重命名或删除。Shell在执行这些操作之前,会调用 ICopyHook接口的CopyCallback方法对它们进行验证。CopyCallback返回一个int值指示Shell是否应该继续执行这个操 作。返回值IDYES表示继续,而返回值IDNO和IDCANCEL则表示终止。

一个文件夹对象可以安装多个拷贝钩子处理程序。如果出现这种情况,Shell会依次调用每个处理程序。只有当每个处理程序都返回IDYES时,Shell才真正执行用户请求的操作。

拷 贝钩子处理程序的作用是在上述四种操作执行前对它们进行验证,但是Shell并不会把操作的结果通知给拷贝钩子处理程序。而windows提供的API函 数FindFirstChangeNotification和FindNextChangeNotification却可以实现这个功能。因此,只有把这 种两种方法结合起来,才能对一个文件夹的状态进行完全的监控。

拷贝钩子处理程序实现并不困难,首先创建一个作为进程内组件的COM对象, 它只需要暴露一个ICopyHook接口(当然还有IUnknown)。然后用regsrv32.exe注册这个COM组件。最后一步是向Shell注册 你的这个拷贝钩子处理程序,方法是在注册表HKEY_CLASSES_ROOTDirectoryShellexCopyHookHandlers 下创建一个名称任意的sub key,在此sub key中创建一个类型为REG_SZ的项并将你的COM对象的CLSID作为它的默认值就可以了。

下面就是一个拷贝钩子的实现程序(注:以下代码经老妖改动并添加了详细操作过程,在BCB6中成功编译并通过测试)

1. 从ICopyHook接口创建TCopyHook,从IClassFactory接口创建TClassFactory:

// TCopyHook.h
// TCopyHook类实现了ICopyHook接口,TClassFactory实现了IClassFactory接口
//—————————————————————————
#define NO_WIN32_LEAN_AND_MEAN
#include <shlobj.h>
//—————————————————————————
class TCopyHook: public ICopyHook
{
public:
     TCopyHook():m_refcnt(0) {}
     STDMETHODIMP QueryInterface(REFIID iid,void **ppvObject);
     STDMETHODIMP_(ULONG) AddRef();
     STDMETHODIMP_(ULONG) Release();
     STDMETHODIMP_(UINT) CopyCallback(HWND hwnd, UINT wFunc, UINT wFlags,
             LPCTSTR pszSrcFile, DWORD dwSrcAttribs,
             LPCTSTR pszDestFile, DWORD dwDestAttribs);
private:
     int m_refcnt;
};
//—————————————————————————
class TClassFactory : public IClassFactory
{
public:
     TClassFactory():m_refcnt(0) {}
     STDMETHODIMP QueryInterface(REFIID iid, void **ppvObject);
     STDMETHODIMP_(ULONG) AddRef();
     STDMETHODIMP_(ULONG) Release();
     STDMETHODIMP CreateInstance(IUnknown *pUnkOuter, REFIID riid, void **ppvObject);
     STDMETHODIMP LockServer(BOOL fLock);
private:
     int m_refcnt;
};

// TCopyHook.cpp
// TCopyHook对象和TClassFactory对象的实现文件
#include <stdio.h>
#include "TCopyHook.h"
//—————————————————————————
extern LONG nLocks;           // 对象计数,用于DllCanUnloadNow
ULONG __stdcall TCopyHook::AddRef()
{
     if(m_refcnt == 0)
         nLocks++;
     m_refcnt++;
     return m_refcnt;
}
//—————————————————————————
ULONG __stdcall TCopyHook::Release()
{
     int nNewCnt = –m_refcnt;
     if(nNewCnt <= 0)
     {
         nLocks–;
         delete this;
     }
     return nNewCnt;
}
//—————————————————————————
HRESULT __stdcall TCopyHook::QueryInterface(REFIID dwIID, void **ppvObject)
{
     if(dwIID == IID_IUnknown)
         *ppvObject = static_cast<IUnknown*>(this);
     else
         if(dwIID == IID_IShellCopyHook)
             *ppvObject = static_cast<ICopyHook*>(this);
         else
             return E_NOINTERFACE;
     reinterpret_cast<IUnknown*>(*ppvObject)->AddRef();
     return S_OK;
}
//—————————————————————————
// 这就是CopyCallback方法,拷贝钩子的所有功能由它实现。参数的具体值参看MSDN
UINT __stdcall TCopyHook::CopyCallback(HWND hwnd, UINT wFunc, UINT wFlags,
         LPCTSTR pszSrcFile, DWORD dwSrcAttribs,
         LPCTSTR pszDestFile, DWORD dwDestAttribs)
{
     char szMessage[MAX_PATH+14];
     sprintf(szMessage, "对%s进行的操作,是否继续?", pszSrcFile);
     return MessageBox(NULL, szMessage, "确认", MB_YESNO | MB_ICONEXCLAMATION);
}
//—————————————————————————
ULONG __stdcall TClassFactory::AddRef()
{
     if(m_refcnt==0)
         nLocks++;
     m_refcnt++;
     return m_refcnt;
}
//—————————————————————————
ULONG __stdcall TClassFactory::Release()
{

     int nNewCnt = –m_refcnt;

     if(nNewCnt <= 0)
     {
         nLocks–;
         delete this;
     }
     return nNewCnt;
}
//—————————————————————————
HRESULT __stdcall TClassFactory::QueryInterface(REFIID dwIID, void **ppvObject)
{
     if(dwIID == IID_IUnknown)
         *ppvObject = static_cast<IUnknown*>(this);
     else
         if(dwIID == IID_IClassFactory)
             *ppvObject = static_cast<IClassFactory*>(this);
         else
             return E_NOINTERFACE;
     reinterpret_cast<IUnknown*>(*ppvObject)->AddRef();
     return S_OK;
}
//—————————————————————————
HRESULT __stdcall TClassFactory::CreateInstance(IUnknown* pUnkownOuter,
         REFIID riid, void** ppvObj)
{
     if(pUnkownOuter != NULL)
         return CLASS_E_NOAGGREGATION;
     TCopyHook *pObj = new TCopyHook;
     pObj->AddRef();
     HRESULT hr = pObj->QueryInterface(riid, ppvObj);
     pObj 文章

Read: 727

软件看门狗–别让你的程序没有响应

原作者姓名 陆其明
文章原始出处 http://hqtech.nease.net

正文
一.概述
一些重要的程 序,必须让它一直跑着;而且还要时时关心它的状态——不能让它出现死锁现象。当然,如果一个主程序会出现死锁,肯定是设计或者编程上的失误。我们首要做的 事是,把这个Bug揪出来。但如果时间紧迫,这个Bug又“飘忽不定”,那么,我们还是先写一个软件“看门狗”,暂时应一下急吧。

“看门狗”的需求描述:“看门狗”的运行不出现界面窗口,具有一定的隐蔽性;定时判断目标进程是否运行在当前系统中,如果没有则启动目标进程;判断目标进程是否“没有响应”,如果是则终止目标进程;如果目标进程“没有响应”的次数超过一定的数量,则将计算机系统重启。

二.预备知识
首 先要介绍两个主要的函数,能够判断目标进程是否“没有响应”。在User32.dll中(没有文档公开),Win2k/NT下的 IsHungAppWindow和Win9X下的IsHungThread;前者是以一个窗口句柄作为参数,后者是以线程ID作为参数。我们可以通过VC 开发工具的Depends查到这两个函数。
要使用这两个函数,我们必须先动态导入,如下:
if (m_hUser32 == NULL)
{
     m_hUser32 = GetModuleHandle("USER32.DLL");
}
if (m_hUser32)
{
     m_IsHungNT   = (HUNG_FUNNT) GetProcAddress(m_hUser32, "IsHungAppWindow");
     m_IsHung9X   = (HUNG_FUN9X) GetProcAddress(m_hUser32, "IsHungThread");
}
另外,还有如下知识点:
1.     如何让窗口隐藏(当然通过Windows任务管理器还是可以看到的)
在框架窗口类的PreCreateWindow中修改窗口风格,如下:
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
     if( !CFrameWnd::PreCreateWindow(cs) )
         return FALSE;
     // TODO: Modify the Window class or styles here by modifying
     //   the CREATESTRUCT cs

     cs.dwExStyle |= WS_EX_TOOLWINDOW;   // Make invisible in taskbar
     cs.style       = WS_POPUP;           // Hide the main window

     return TRUE;
}
2.     如何让“看门狗”只运行一个进程
使用互斥量。在CWatchDogApp::InitInstance()中,执行如下代码:
bool CWatchDogApp::IsUniqueCopyInProc()
{
     m_Mutex = CreateMutex(NULL, TRUE, "System Watch Dog");
     if (GetLastError() == ERROR_ALREADY_EXISTS)
     {
         return false;
     }
     return true;
}
该函数如果返回false,说明已经有一个WatchDog进程在运行了,当前进程就没有必要再执行下去了。在InitInstance如下处理:
if (!IsUniqueCopyInProc())
return FALSE;
3.     如何判断当前操作系统类型
bool CWatchDogApp::IsWinNT()
{
     OSVERSIONINFO OSVersionInfo;
     OSVersionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
     GetVersionEx(&OSVersionInfo);
     if (OSVersionInfo.dwPlatformId == VER_PLATFORM_WIN32_NT)
     {
         return true;
     }
     return false;
}
4.     如何自动重启计算机
在Win9x和Win2k/NT下,重启计算机的处理略有不同:
if (theApp.IsWinNT())
{
     // 在Win NT/2000下赋予关闭系统的权限
     static HANDLE hToken;
     static TOKEN_PRIVILEGES tp;
     static LUID luid;
::OpenProcessToken(GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY, &hToken ) ;
     ::LookupPrivilegeValue( NULL, SE_SHUTDOWN_NAME, &luid );
     tp.PrivilegeCount            = 1;
     tp.Privileges[0].Luid        = luid;
     tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
     ::AdjustTokenPrivileges( hToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), NULL, NULL );
     return ::ExitWindowsEx(EWX_REBOOT | EWX_FORCE, 0);
}
else
{
     return ::ExitWindowsEx(EWX_REBOOT | EWX_FORCE, 0);
}
5.     如何启动、结束其他进程
启动进程用CreateProcess,终止进程用TerminateProcess。参考代码如下:
bool CWatchDogView::RunTheSysProc()
{
     char     szPath[MAX_PATH];
     GetModuleFileName(NULL, szPath, MAX_PATH);
     CString strPath = szPath;
     strPath = strPath.Left(strPath.ReverseFind(‘\’)) + "\HungDemo.exe";

     STARTUPINFO             StartInfo;
     PROCESS_INFORMATION     procStruct;
     memset(&StartInfo,0,sizeof(STARTUPINFO));
     StartInfo.cb = sizeof(STARTUPINFO);

     if (!::CreateProcess(
         (LPCTSTR) strPath,
         NULL,
         NULL,
         NULL,
         FALSE,
         NORMAL_PRIORITY_CLASS,
         NULL,
         NULL,
         &StartInfo,
         &procStruct))
         return false;
     return true;
}
需要提醒的是,TerminateProcess是在万不得已的情况下使用的,它不会进入进程使用的DLL的入口点通知“脱离”(Detaching)状态。有时候,这样做是很危险的(DLL内部的全局数据可能受影响较大)。
6.     如何让Win2k/NT自动登录
修 改注册表。在HKEY_LOCAL_MACHINE目录下的SoftwareMicrosoftWindows NT CurrentVersionWinLogon下的AutoAdminLogon(字符串型)设置成1,并在DefaultUserName设置默认登 录用户,DefaultPassword设置默认用户的密码。
7.     如何让Win2k/NT登录成功后直接执行你的程序(而不是默认的文件浏览器)
修改注册表。在注册表HKEY_LOCAL_MACHINESOFTWAREMicrosoftWindows NT CurrentVersionWinlogonShell的值从原先的explorer.exe修改为自己程序的绝对路径。

三.功能演示(Win2k/NT下)
友 情提醒:开始演示之前,请先将你目前的工作保存。运行“看门狗”WatchDog;同时使用Ctrl+Alt+Del打开“Windows任务管理器”。 稍候片刻,可以看到目标程序HungDemo会被启动(这个程序模拟了“没有响应”)。然后,WatchDog发现这个程序“没有响应”,则把它杀掉,然 后重新启动一个新的HungDemo进程。如此的处理重复六次以后,系统会自动重启。

正文完
源程序:http://www.vchelp.net/ASP/ibr_upload/357.zip

Read: 774

钩子的应用: 程序运行监视

作者:Victor Chen
来自:C++ 爱好者
http://www.cppfans.com

——————————————————————————–

程序介绍:

利用这个程序:
1.可以监视在你的电脑运行的程序, 把在你的电脑运行过的程序的时间和名字记录下来;
2.可以阻止你规定的禁用程序的执行, 比如不让玩游戏。
3.这个程序需要加入注册表, 在系统启动时就运行, 达到监视的目的。注册表大概都不陌生,就是这里:
HKEY_LOCAL_MACHINESOFTWAREMicrosoftWindowsCurrentVersionRun

程序的记录格式:

2003-02-03 17:31:25 – [System Startup – Windows XP 5.01.2600]
2003-02-03 17:31:29 "CabinetWClass" -> "我的电脑"
2003-02-03 17:31:59 "Red Alert" -> "Red Alert" (关闭禁用程序)
2003-02-03 17:32:19 "扫雷" -> "扫雷" (关闭禁用程序)
2003-02-03 17:32:35 "OpusApp" -> "Microsoft Word"
2003-02-03 17:32:50 – [System Shutdown – 0 days, 0 hrs, 1 mins, 25 secs]

2003-02-03 17:35:37 – [System Startup – Windows 98 SE 4.10.2222]
2003-02-03 17:35:53 "扫雷" -> "扫雷" (关闭禁用程序)
2003-02-03 17:36:05 "CabinetWClass" -> ""
2003-02-03 17:36:31 "Red Alert" -> "Red Alert" (关闭禁用程序)
2003-02-03 17:36:56 "ExploreWClass" -> ""
2003-02-03 17:37:07 – [System Shutdown – 0 days, 0 hrs, 1 mins, 30 secs]

程序运行只需要3个文件:
hwhpapp.exe 可执行文件
hwhpdrv.dll 安装钩子的动态链接库
hwhpapp.cfg 禁用软件黑名单, 可用记事本修改
程序运行会自动产生记录文件:
hwhpapp.sys 可以用记事本打开看

程序原理:

一.钩子
利用 API 函数 SetWindowsHookEx() 安装一个全局钩子, 钩子类型为 WH_SHELL。
WH_SHELL 钩子可监视所有应用程序的主窗口创建或者关闭。
最典型的应用就是 Windows 的状态栏,当程序运行时,把主窗口的标题加入状态栏,程序退出时,从状态栏删除。
如果你截获这个钩子,可以做到禁止状态栏的显示,也可以自己作一个状态栏,或者作一个历史记录,把所有在你电脑曾经运行的程序记录下来。如果运行的程序不是你希望的,你可以直接关闭这个程序,达到禁止运行的目的。

二.动态链接库
由于钩子是全局的,必须把这个钩子定义到 .DLL 的动态链接库里,这就涉及到建立动态链接库。

三.共享内存
由于钩子是安装到系统里的,钩子的运行是在操作系统里面,因此这个钩子不能使用你的程序所定义的任何全局变量!
既 然如此,有什么办法解决呢?在本程序里利用共享内存技术,利用 API 函数 CreateFileMapping() 可创建共享内存,这个内存可以在任何运行的程序中使用,也就是说任何运行的 .EXE、.DLL 和其他程序都可以使用这块内存。在本程序中直接使用了 Victor 串口 VCL 控件里的 TSharedMemory 共享内存类。

四.记录文件和软件黑名单文件
把所有在你的电脑执行的程序记录保存在一个文本文件里, 因为扩展名为 .txt 很容易被发现,因此采用扩展名 .sys
软件黑名单文件保存在 .cfg 文件里,同样因为 .ini 文件很容易发现并且打开修改。
这两个文件都保存在与你的 .exe 文件的相同文件夹里,并且与 .exe 文件同名.

五.保证你的程序只能同时运行一个
如果你同时运行两个程序,记录文件就会乱套,所以必须保证只能运行一个。
当你的程序刚开始运行的时候,就是在 WinMain() 函数的最开始,就要判断是否已经运行了,如果已经运行,就直接退出。
判断的方法很简单,就是检查程序共享的内存是否存在,如果检查到共享的内存已经存在,那就是已经运行了。

六.程序隐身, 不能显示在任务栏和任务管理器里面
这个也很简单,只要在主程序的 Application->Run(); 前面加一句话:
SetWindowLong(Application->Handle, GWL_EXSTYLE, GetWindowLong(Application->Handle, GWL_EXSTYLE)|WS_EX_TOOLWINDOW);
就可以了。

——————————————————————————–

程序介绍
.DLL 文件:这是最关键的钩子的代码:

#include <vcl.h>
#include "yb_base.h" //Victor 串口控件里面的一个头文件

#define MYAPPMARK "VICTOR_APPMONI_20010612" //共享内存标志

class __export THookedProcs
{
    public:
        THookedProcs();
        ~THookedProcs();
        void WINAPI InitFuncs(void);
        void WINAPI UninitFuncs(void);
    private:
        HHOOK hThisHook; //保存钩子的句柄
        static LRESULT CALLBACK HookedShellProc(int nCode, WPARAM wParam, LPARAM lParam);
};

//定义共享的数据结构
typedef struct
{
   HHOOK hHook; //当前使用的 HOOK
//… 此处可增加其他共享的数据
} THookSharedData;

THookedProcs::THookedProcs()
{
   hThisHook = NULL;
}

THookedProcs::~THookedProcs()
{
    UninitFuncs();
}

void WINAPI THookedProcs::InitFuncs(void)
{
    UninitFuncs();
    hThisHook = SetWindowsHookEx(WH_SHELL, (HOOKPROC) HookedShellProc, HInstance, 0);
    TSharedMemory AppMem(MYAPPMARK,4096); //在 EXE 文件里共享的内存
    THookSharedData *HookSharedData = ((THookSharedData*)(AppMem.AppInfo->Data)); //共享的数据
    HookSharedData->hHook = hThisHook; //把 hThisHook 保存到共享内存里
}

void WINAPI THookedProcs::UninitFuncs(void)
{
    if(hThisHook)
    {
        UnhookWindowsHookEx(hThisHook);
        hThisHook = NULL;
    }
}

LRESULT CALLBACK THookedProcs::HookedShellProc(int nCode, WPARAM wParam, LPARAM lParam)
{
    TSharedMemory AppMem(MYAPPMARK, 4096); //使用在 .EXE 文件里共享的内存
    if(AppMem.Valid)if(AppMem.Exists) //如果共享内存存在
    {
        HWND hMainWnd = AppMem.AppInfo->hMainForm;

        if(hMainWnd)
        {
            if(nCode==HSHELL_WINDOWCREATED)
            {
                PostMessage(hMainWnd, WM_USERCMD, UC_WINHOOK, wParam);
            }
        }
    }
    //在 Hook 里无法调用 hThisHook, 必须用共享内存里面的 hHook
    THookSharedData *HookSharedData = ((THookSharedData*)(AppMem.AppInfo->Data)); //共享的数据
    return CallNextHookEx(HookSharedData->hHook, nCode, wParam, lParam);
}

——————————————————————————–

EXE文件主程序的代码:

WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR lpCmdLine, int)
{
    if(!AppMem.Valid)
    {
        return 1;
    }
    if(AppMem.Exists) //已经存在 (程序以前已经运行过了, 并且正在运行)
    {
        if(stricmp(lpCmdLine, "/SHOW")==0) //如果监测到命令行参数 /SHOW 就显示出已经运行的程序的主窗口
        {
            PostMessage(AppMem.AppInfo->hMainForm, WM_USERCMD, UC_SHOWWIN, 0);
        }
        return 0;
    }
    AppMem.ClearBuffer();

    try
    {
        Application->Initialize();

Read: 630