dll源代码调试全攻略~包括ISAPI,API HOOK,GINA等dll的源代码调试方法~

首先明白一点的是,只要有模块(exe,dll,sys等是模块)对应的正确符号文件,我们都可以使用代码去调试。

1:普通dll
首先写一个exe加载要调试的dll,下好断点后再按f5,会弹出一个框,让你输入调用该dll的程序,我们只要填入加载该dll的exe的路径即可。

或者在project->setting->debug里填入调用该dll的路径也是一样的。

2:com,activex控件。
和上述基本一致。如果是IE插件,那么就填入IE的路径。

3:shell扩展
这个需要注意一些问题:下面的话来自Windows Shell扩展编程完全指南(系统崩溃请找该文的作者,切勿来找我,谢谢)
当shell扩展被 Explorer调用后, 它会在内存中呆上一段时间, 这会使你无法重新编译并生成Shell扩展DLL文件.

要让 Explorer 更迅速地卸载Shell扩展执行文件,需要创建如下注册表项:
HKLM\Software\Microsoft\Windows\CurrentVersion\Explorer\AlwaysUnloadDLL
并将其值设为 “1”. 对于Win9x, 这是你能做的最好的方法。

而在Win NT/2000上, 你可以找到如下键:
HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer
并创建一个名为DesktopProcess的DWORD值 1. 这会使桌面和任务栏运行在同一个进程中, 而其他每一个 Explorer 窗口都运行在它自己的每一个进程内. 也就是说,你可以在单个的Explorer 窗口内进行调试, 而后只要你关闭该窗口,你的DLL就会被马上卸载, 这就避免了因为DLL正被Windows使用而无法替换更新. 而如果不幸出现这种情况,你就不得不注销登录后再重新登录进Windows从而强制卸载使用中的Shell扩展DLL.

———–
完成上面的任务后,和普通DLL那样调试,不过要选择系统目录里面的explorer.exe

4:ISAPI DLL
建议使用vs2003以及以上版本的vc。
MFC ISAPI都有一个全局类,命名为C+工程名+Extension
在此类的构造函数的第一行之前加上下面三行代码的其中一行
1:)MessageBox(NULL,”Please debug me!”,”Debug”,MB_OK|MB_SERVICE_NOTIFICATION);
2:)DebugBreak();
3:)_asm int 3
然后在生成目录的文件夹上点右键,以debug为例,选择”共享与安全”,在web共享里面选择”共享“该文件夹,权限设置为”执行“最后点确定。
运行IIS,在浏览器里面输入http://127.0.0.1/debug/isapi的名字(如testisapi.dll,如果需要输入密码则运行inetmgr,找到debug虚拟目录,点属性,然后勾选匿名访问。
这时会弹出一个对话框,先别点确定,回到vs2003中,在要调试的地方下好断点,然后在任务管理器中找到用户名为IWAM_XX的DLLHOST的进程ID,然后在vs2003菜单中选择调试->进程->选择刚才找到的进程ID,然后点附加,再点刚才弹出的框的确定(如果使用2或者3则点取消),然后程序就会停在断点下了,如果没停则是没下好断点,或者RPWT。

5:消息钩子
这个简单啊。。都不想再说了,假设写了个键盘钩子,需要勾住计算器的击键操作。先下好断点,在vs2003菜单中选择调试->进程->选择计算器,点附件按钮,最后在计算器上击键,程序就会停在断点处。

6:API HOOK
如果是注入到别的进程的API HOOK,那么调试方法和消息钩子一样。挂钩本进程,则直接启动调试。。。实在太简单了。

7:GINA
这个稍微繁琐。方法多种多样。
1:)参考下面的文章
如何在单个计算机上调试 GINA DLL
http://support.microsoft.com/kb/260901/zh-cn
How to debug a GINA DLL on a single computer
http://support.microsoft.com/default.aspx?scid=kb;EN-US;Q260901
2::)用NTSD调试,修改下面注册表,(来自MSDN,不行请找微软)
HKEY_LOCAL_MACHINE
Software
Microsoft
Windows NT
CurrentVersion
Image File Execution Options
winlogon.exe
Debugger = ntsd -d
3:)双机kernel windbg与user ntsd联合调试,在vm里面设置系统以调试方式启动,然后在目标系统的注册表加上
HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\WinLogon.EXE\Debugger

ntsd -d -x -g

,然后重新启动vm里面的系统,选择调试方式启动,然后开启windbg,选择内核调试,连接上系统。等啊等,winlogon会被ntsd中断,并且反映到windbg里面,这时是打开代码下断点的好机会。这个就是所谓的kernel windbg与user ntsd联合调试。

———-
8:其它调试方式
使用TRACE宏把信息输出到dbgview上。这个方便,并且不干扰程序的执行。

该文章转自CSDN!

在VC中嵌入IE

一、需要在你的程序中嵌入IE浏览器的窗口。有两种实现方法,其一,添加CHtmlView的视类;其二,添加IE浏览器的ActiveX控件。其实不管用什么方法,除了函数名称稍有区别外,它们最终都调用了微软的IWebBrowser2的接口。

二、方法和步骤:
1. 建立一个对话窗形式的MFC应用程序,注意在导航的第2页上,一定要选择ActiveX Controls。这也是默认的选择。
2. 向工程中添加 Microsoft Web 浏览器的ActiveX控件。
执行菜单 Project/Add To Project/Components And Controls… 选择Registered ActiveX Controls目录,找到Microsoft Web 浏览器,并Insert。这样MFC会给你产生一个IWebBrowser2的包装类CWebBrowser2。
3. 在对话窗资源编辑器中,把工具条上的IE控件放到你的对话窗中。启动ClassWizard,映射这个IE控件为CWebBrowser2的一个对象(在演示程序中的对象名称是m_ie)

三、演示程序中一些需要说明介绍的知识:(更多更详细的使用信息,请参考CHtmlView,IWebBrowser2的MSDN说明)
1. 显示浏览一个HTML文件(或ActiveX文档类型的文件,如Word,PDF等),请使用Navigate()/Navigate2()函数
2. 关于背景音乐、图象文件、FLASH等显示,其实是按照HTML的语法标记,直接写在HTML文件中的。
3. 打印功能,需要调用ExecWB()函数。这个函数的功能非常多,根据参数命令(OLECMDID),能够实现诸如打印、保存、剪贴版等30多个功能。
4. IE控件有一个非常重要的事件(当打开HTML的连接之前)。重载虚函数OnBeforeNavigate2()后,可以实现连接重定向;提取HTML中用户输入的表单数据等功能。在演示程序中,也给大家展示了如何取得数据,及安全数组的用法。
5. 演示程序中,为了突出重点。HTML模版文件都非常基本和简单。如果你有深厚的HTML知识的功底,那就尽情发挥吧。

C++处理XML文件

选择一:市面上的XML lib还是有几个的,最有名的当然是libXML。

选择二:MS的MSXML,可以查看下MSDN文档。

选择三:使用开源类CMarkup,今天主要讲这个。

网上有Cmarkup的源代码,你可以下载后在里面include你的头文件,然后创建一个Cmarkup的对象,然后就可以调用里面的成员函数对你所希望操作的XML文件进行操作了。

下面是CMarkup的类成员函数使用方法。这些函数的设计都是基于源EDOM的。

创建一个XML文档

对于创建一个XML文档,需要实例化一个CMarkup对象,并调用AddElem创建根元素。.在这个位置,如果你调用AddElem(“ORDER”) ,你的文档会简单的装一个空ORDER元素. 然后调用AddChildElem 在根元素的下面创建元素 (例如:“进入”根元素内部,层次表示).下面的示例代码创建一个XML文档并返回它(的内容)到一个字符串中。

CMarkup xml;
xml.AddElem( "ORDER" );
xml.AddChildElem( "ITEM" );
xml.IntoElem();
xml.AddChildElem( "SN", "132487A-J" );
xml.AddChildElem( "NAME", "crank casing" );
xml.AddChildElem( "QTY", "1" );
CString csXML = xml.GetDoc();

这些代码产生了下面的XML,这个根结点是ORDER元素;注意它的开始标签 在开头,结束标签在结尾。当一个元素是在一个父下面(深入或被包含),这个父元素的开始标签要在它之前,结束标签要在它之后。ORDER元素包含一个ITEM元素,而ITEM元素包含了三个字子元素:SN、NAME和QTY;

132487A-J

crank casing

1

如例子中所显示的,你也能够在一个子元素下创建新元素,这需要调用IntoElem 移动你的当前主位置到当前子元素位置,然后你就可以在这下面增加一个子元素了。CMarkup在索引中保持了一个当前位置指针,以保证你的源码更加短和更简单,当导航文件时,相同的逻辑位置也会被使用。

导航XML文档

上面的例子所创建的XML字符串,用SetDoc方法加入到CMarkup对象中能够被解析,你也可以引导它正确的进入被创建的同一个CMarkup对象中,如果你要设置当前位置到文档的开始时,需要调用ResetPos.

在下面的例子中,从csXML字符串生成CMarkup对象后,我们循环ORDER元素下的所有ITEM元素,并得到每个项目的序号和数量。

CMarkup xml;
xml.SetDoc( csXML );
while ( xml.FindChildElem("ITEM") )
{
    xml.IntoElem();
    xml.FindChildElem( "SN" );
    CString csSN = xml.GetChildData();
    xml.FindChildElem( "QTY" );
    int nQty = atoi( xml.GetChildData() );
    xml.OutOfElem();
}

对于我们发现的每个元素,在查询它了子元素之前要调用IntoElem,查询完之后再调用OutOfElem ,当你习惯于这种导航类型时,你将知道,检查你的循环时,要确定每个IntoElem 调用都有一个与之对应的OutOfElem 调用 。

增加元素和属性

上面创建文档的例子中仅创建了一个ITEM元素,现在这个例子是创建多个项目,从前一个内容加裁后,再增加数据源,加上SHIPMENT信息元素中有一个属性,这段代码也演示了你能调用调用IntoElem和AddElem来代替AddChildElem,函数调用。虽然这意味着更多的调用,但许多人认为这样更直观。

CMarkup xml;
xml.AddElem( "ORDER" );
xml.IntoElem(); // inside ORDER
for ( int nItem=0; nItem
{
    xml.AddElem( "ITEM" );
    xml.IntoElem(); // inside ITEM
    xml.AddElem( "SN", aItems[nItem].csSN );
    xml.AddElem( "NAME", aItems[nItem].csName );
    xml.AddElem( "QTY", aItems[nItem].nQty );
    xml.OutOfElem(); // back out to ITEM level
}
xml.AddElem( "SHIPMENT" );
xml.IntoElem(); // inside SHIPMENT
xml.AddElem( "POC" );
xml.SetAttrib( "type", csPOCType );
xml.IntoElem(); // inside POC
xml.AddElem( "NAME", csPOCName );
xml.AddElem( "TEL", csPOCTel );

这段代码产生了下面的XML,根元素ORDER包含两个ITEM元素和一个SHIPMENT元素,ITEM元素全都包含SN、NAME、和QTY元素,SHIPMENT元素包含一个带有属性类型的POC元素,和NAME及TEL子元素。

132487A-J

crank casing

1

4238764-A

bearing

15

John Smith

555-1234

查找元素

FindElem 和 FindChildElem方法用于到下一个兄弟元素。如果可选的标签名被指定,那么它们将到下一个与标签名相匹配的元素,被发现的元素是当前元素,并且下次调用Find将会到当前位置后的下一个兄弟或下一个匹配兄弟。

当你无法判断元素的索引时,在调用两个Find方法之间,一定要复位当前位置。看上面的例子中ITEM元素,如果是别的人创建的XML文件,你不能确定SN元素在QTY元素之前,那么在查找QTY元素之前就要调用ResetChildPos();

对于用一个特定的序号去查找元素,你需要完全循环ITEM元素,并比较SN元素的数据和你正在搜索的序号。这个例子不同于先前导航的例子,它调用IntoElem 进入到ORDER元素,并且用FindElem(“ITEM”)替换FindChildElem(“ITEM”);其实两种方式都挺好。需要注意的是,在Find方法中指定ITEM元素的标签名,我们会忽略所有其它的兄弟元素,例如SHIPMENT元素。

CMarkup xml;
xml.SetDoc( csXML );
xml.FindElem(); // ORDER element is root
xml.IntoElem(); // inside ORDER
while ( xml.FindElem("ITEM") )
{
    xml.FindChildElem( "SN" );
    if ( xml.GetChildData() == csFindSN )
        break; // found
}

编码

ASCII编码引用了我们所依靠的字符码128以下的字符,如用英语编程。如果你只使用ASCII码,很方便,UTF-8编程与你拉公共ASCII集相同。

如果你所使用的字符集不在Unicode编码集(UTF-8,UTF-16,UCS-2)中,那么出于交互性以及在IE中很好的显示,你真的需要在XML声明中进行描述。像ISO-8859-1(西欧)字符集指定字符值在一个比特且在128到255之间。以便每个字符仍然使用一个比特。Windows双字节字符集像GB2312,Shift_JIS和EUC-KR,每个字符都是用一个或两个字节,对于这些Windows字符集,在你的预处理中需要定义_MBCS ,并要确定用户的操作系统设置到合适的编码页。

关于用一个XML描述的XML文档前缀,像,需要通过用SetDoc或Cmarkup的构造函数来传递。在结尾要包括回车符,这样根结点会显示在下一行。

xml.SetDoc( "/r/n" );
xml.AddElem( "island", "Curaçao" );

Tonitech版权所有 | 转载请注明出处: http://www.tonitech.com/?p=948

线程之间的通信

        通常情况下,一个次级线程要为主线程完成某种特定类型的任务,这就隐含着表示在主线程和次级线程之间需要建立一个通信的通道。一般情况下,有下面的几种方法实现这种通信任务:使用全局变量、使用事件对象、使用消息。使用全局变量的方法相信有点基础的人都会,在这里不做讲解,我们主要介绍后两种方法。

  (一) 利用用户定义的消息通信

  在Windows程序设计中,应用程序的每一个线程都拥有自己的消息队列,甚至工作线程也不例外,这样一来,就使得线程之间利用消息来传递信息就变的非常简单。首先用户要定义一个用户消息,如下所示:#define WM_USERMSG WMUSER+100;在需要的时候,在一个线程中调用::PostMessage((HWND)param,WM_USERMSG,0,0)或CwinThread::PostThradMessage()来向另外一个线程发送这个消息,上述函数的四个参数分别是消息将要发送到的目的窗口的句柄、要发送的消息标志符、消息的参数WPARAM和LPARAM。下面的代码结果是在线程结束时显示一个对话框,提示线程结束:

UINT ThreadFunction(LPVOID pParam)
{
 while(!bend)
 {
  Beep(100,100);
  Sleep(1000);
 }
 ::PostMessage(hWnd,WM_USERMSG,0,0);
 return 0;
}
////////WM_USERMSG消息的响应函数为OnThreadended(WPARAM wParam, LPARAM lParam)
LONG CTestView::OnThreadended(WPARAM wParam,LPARAM lParam)
{
 AfxMessageBox("Thread ended.");
 Retrun 0;
}

  上面的例子是工作者线程向用户界面线程发送消息,对于工作者线程,如果它的设计模式也是消息驱动的,那么调用者可以向它发送初始化、退出、执行某种特定的处理等消息,让它在后台完成。在控制函数中可以直接使用::GetMessage()这个SDK函数进行消息分检和处理,自己实现一个消息循环。GetMessage()函数在判断该线程的消息队列为空时,线程将系统分配给它的时间片让给其它线程,不无效的占用CPU的时间,如果消息队列不为空,就获取这个消息,判断这个消息的内容并进行相应的处理。

  (二)用事件对象实现通信

  在线程之间传递信号进行通信比较复杂的方法是使用事件对象,用MFC的Cevent类的对象来表示。事件对象处于两种状态之一:有信号和无信号,线程可以监视处于有信号状态的事件,以便在适当的时候执行对事件的操作。上述例子代码修改如下:

////////////////////////////////////////////////////////////////////

Cevent threadStart ,threadEnd;

UINT ThreadFunction(LPVOID pParam)
{
 ::WaitForSingleObject(threadStart.m_hObject,INFINITE);
 AfxMessageBox("Thread start.");
 while(!bend)
 {
  Beep(100,100);
  Sleep(1000);
  Int result=::WaitforSingleObject(threadEnd.m_hObject,0);
  //等待threadEnd事件有信号,无信号时线程在这里悬停
  If(result==Wait_OBJECT_0)
   Bend=TRUE;
 }
 ::PostMessage(hWnd,WM_USERMSG,0,0);
 return 0;
}

/////////////////////////////////////////////////////////////

Void CtestView::OninitialUpdate()
{
 hWnd=GetSafeHwnd();
 threadStart.SetEvent();//threadStart事件有信号
pThread=AfxBeginThread(ThreadFunction,hWnd);//启动线程
 pThread->m_bAutoDelete=FALSE;
 Cview::OnInitialUpdate();
}

////////////////////////////////////////////////////////////////

Void CtestView::OnDestroy()
{
 threadEnd.SetEvent();
 WaitForSingleObject(pThread->m_hThread,INFINITE);
 delete pThread;
 Cview::OnDestroy();
}

  运行这个程序,当关闭程序时,才显示提示框,显示”Thread ended”。

在网页中调用C++接口

    最近在公司做一个关于在网页中调用C++接口的项目,现在项目已经做完。在这期间花了一段时间去学了关于这方面的知识,和大家一起分享下。

    首先是在windows客户端中网页的生成,在这期间需要用到IWebBrowser2接口。早期, IWebBrowser2 继承自 IWebBrowser 和 IWebBrowserApp, 还提供不包含在着两个接口中的功能. 所以你应当使用 IWebBrowser2 接口替代 来操纵WebBrowser 控件或者 Internet Explorer.该块知识用到了windows的COM 技术,具体可查看MSDN。IWebBrowser2 接口有4个方法和8个属性,其中最终要的方法就是Navigate2,Navigate2的功能基于Navigate 方法,不同在于 Navigate2 允许你导航到飞URL表达的地方,例如Windows shell folder. (Windows shell folder 是指向标示符指针, 或者windows shell命名空间中的 PIDL,)。该方法最终的参数是第一个,本地HTML文件所在的位置。

   在调用方法后,你就可以看到你自己写的WEB页面了。下面的代码是我的测试页面:

<html>
<script>
function onBtn1(obj)
{
    var aaaa    = external.closeApp();
}
</script>
<body>
<input type="button" name="btn1" value="btn1" onClick="onBtn1(this)" />
</body>
</html>
WEB页面上只有一个Button,点下按钮调用C++里面的closeApp()函数。
那在客户端中CloseApp这个函数如何写?下面会有详细的说明:

1.首先在头文件中声明函数:

void_closeApp();

2.在CPP文件中写函数的具体实现。

3.对函数进行DISP_FUNCTION。代码如下:

DISP_FUNCTION(CHxBrowser, "closeWindow", _closeWindow, VT_NULL, VTS_NONE);

DISP_FUNCTION参数:

theClass 类的名字。

pszName 函数的外部名字。

pfnMember 成员函数的名字。

vtRetVal 指定了函数返回类型的值。

vtsParams 指定了函数参数表的一个或多个常量的用空格分隔的列表。

说明:

DISP_FUNCTION宏被在调度映射中使用,用来定义一个OLE自动化函数。

vtRetVal参数属于VARTYPE类型。这个参数的可能取值来自VARENUM枚举,如下:

符号 返回类型

VT_EMPTY void

VT_I2 short

VT_I4 long

VT_R4 float

VT_R8 double

VT_CY CY

VT_DATE DATE

VT_BSTR BSTR

VT_DISPATCH LPDISPATCH

VT_ERROR SCODE

VT_BOOL BOOL

VT_VARIANT VARIANT

VT_UNKNOWN LPUNKNOWN

 

vtsParams参数是VTS_ 常量中取值的用空格分隔的列表。有空格分隔的一个或多个取值的列表指定了函数的参数列表。例如:

VTS_I2 VTS_PI2

指定了包含一个短整数以及后面的短整数指针的列表。

VTS_ 常量及其含义如下:

符号 参数类型

VTS_I2 short

VTS_I4 long

VTS_R4 float

VTS_R8 double

VTS_CY Const CY or CY*

VTS_DATE DATE

VTS_BSTR LPCSTR

VTS_DISPATCH LPDISPATCH

VTS_SCODE SCODE

VTS_BOOL BOOL

VTS_VARIANT Const VARIANT* or VARIANT&

VTS_UNKNOWN LPUNKNOWN

VTS_PI2 short*

VTS_PI4 long*

VTS_PR4 float*

VTS_PR8 double*

VTS_PCY CY*

VTS_PDATE DATE*

VTS_PBSTR BSTR*

VTS_PDISPATCH LPDISPATCH*

VTS_PSCODE SCODE*

VTS_PBOOL BOOL*

VTS_PVARIANT VARIANT*

VTS_PUNKNOWN LPUNKNOWN*

OK,所有的步骤已经完成,你就可以在你自己写的网页上调用客户端里面的函数对客户端进行操作了!