HOOK消息的使用

2009/03/29
VC++编程技术连载--- VC++金山词霸抓词机理 — HOOK消息功能的使用
2007-06-29 08:14:10
VC++编程技术连载--- VC++金山词霸抓词机理 — HOOK消息功能的使用
Windows系统是建立在事件驱动的机制上的,说穿了就是整个系统都是通过消息的传递来实现的。而钩子是Windows系统中非常重要的系统接口,用它可以截获并处理送给其他应用程序的消息,来完成普通应用程序难以实现的功能。钩子的种类很多,每种钩子可以截获并处理相应的消息,如键盘钩子可以截获键盘消息,外壳钩子可以截取、启动和关闭应用程序的消息等。本文在VC5编程环境下实现了一个简单的鼠标钩子程序,并对Win32全局钩子的运行机制、Win32 DLL的特点、VC5环境下的MFC DLL以及共享数据等相关知识进行了简单的阐述。
文章正文
一.Win32全局钩子的运行机制
钩子实际上是一个处理消息的程序段,通过系统调用,把它挂入系统。每当特定的消息发出,在没有到达目的窗口前,钩子程序就先捕获该消息,亦即钩子函数先得到控制权。这时钩子函数即可以加工处理(改变)该消息,也可以不作处理而继续传递该消息,还可以强制结束消息的传递。对每种类型的钩子由系统来维护一个钩子链,最近安装的钩子放在链的开始,而最先安装的钩子放在最后,也就是后加入的先获得控制权。要实现Win32的系统钩子,必须调用SDK中的API函数SetWindowsHookEx来安装这个钩子函数,这个函数的原型是HHOOK SetWindowsHookEx(int idHook,HOOKPROC lpfn,HINSTANCE hMod,DWORD dwThreadId);,其中,第一个参数是钩子的类型;第二个参数是钩子函数的地址;第三个参数是包含钩子函数的模块句柄;第四个参数指定监视的线程。如果指定确定的线程,即为线程专用钩子;如果指定为空,即为全局钩子。其中,全局钩子函数必须包含在DLL(动态链接库)中,而线程专用钩子还可以包含在可执行文件中。得到控制权的钩子函数在完成对消息的处理后,如果想要该消息继续传递,那么它必须调用另外一个SDK中的API函数CallNextHookEx来传递它。钩子函数也可以通过直接返回TRUE来丢弃该消息,并阻止该消息的传递。
二.Win32 DLL的特点
Win32 DLL与 Win16 DLL有很大的区别,这主要是由操作系统的设计思想决定的。一方面,在Win16 DLL中程序入口点函数和出口点函数(LibMain和WEP)是分别实现的;而在Win32 DLL中却由同一函数DLLMain来实现。无论何时,当一个进程或线程载入和卸载DLL时,都要调用该函数,它的原型是BOOL WINAPI DllMain(HINSTANCE hinstDLL,DWORD fdwReason, LPVOID lpvReserved);,其中,第一个参数表示DLL的实例句柄;第三个参数系统保留;这里主要介绍一下第二个参数,它有四个可能的值:DLL_PROCESS_ATTACH(进程载入),DLL_THREAD_ATTACH(线程载入),DLL_THREAD_DETACH(线程卸载),DLL_PROCESS_DETACH(进程卸载),在DLLMain函数中可以对传递进来的这个参数的值进行判别,并根据不同的参数值对DLL进行必要的初始化或清理工作。举个例子来说,当有一个进程载入一个DLL时,系统分派给DLL的第二个参数为DLL_PROCESS_ATTACH,这时,你可以根据这个参数初始化特定的数据。另一方面,在Win16环境下,所有应用程序都在同一地址空间;而在Win32环境下,所有应用程序都有自己的私有空间,每个进程的空间都是相互独立的,这减少了应用程序间的相互影响,但同时也增加了编程的难度。大家知道,在Win16环境中,DLL的全局数据对每个载入它的进程来说都是相同的;而在Win32环境中,情况却发生了变化,当进程在载入DLL时,系统自动把DLL地址映射到该进程的私有空间,而且也复制该DLL的全局数据的一份拷贝到该进程空间,也就是说每个进程所拥有的相同的DLL的全局数据其值却并不一定是相同的。因此,在Win32环境下要想在多个进程中共享数据,就必须进行必要的设置。亦即把这些需要共享的数据分离出来,放置在一个独立的数据段里,并把该段的属性设置为共享。
三.VC5中MFC DLL的分类及特点
在VC5中有三种形式的MFC DLL(在该DLL中可以使用和继承已有的MFC类)可供选择,即Regular statically linked to MFC DLL(标准静态链接MFC DLL)和Regular using the shared MFC DLL(标准动态链接MFC DLL)以及Extension MFC DLL(扩展MFC DLL)。第一种DLL的特点是,在编译时把使用的MFC代码加入到DLL中,因此,在使用该程序时不需要其他MFC动态链接类库的存在,但占用磁盘空间比较大;第二种DLL的特点是,在运行时,动态链接到MFC类库,因此减少了空间的占用,但是在运行时却依赖于MFC动态链接类库;这两种DLL既可以被MFC程序使用也可以被Win32程序使用。第三种DLL的特点类似于第二种,做为MFC类库的扩展,只能被MFC程序使用。
四.在VC5中全局共享数据的实现
在主文件中,用#pragma data_seg建立一个新的数据段并定义共享数据,其具体格式为:
#pragma data_seg (”shareddata”)
HWND sharedwnd=NULL;//共享数据
#pragma data_seg()
仅定义一个数据段还不能达到共享数据的目的,还要告诉编译器该段的属性,有两种方法可以实现该目的(其效果是相同的),一种方法是在.DEF文件中加入如下语句:
SETCTIONS
shareddata READ WRITE SHARED
另一种方法是在项目设置链接选项中加入如下语句:
/SECTION:shareddata,rws
五.具体实现步骤
由于全局钩子函数必须包含在动态链接库中,所以本例由两个程序体来实现。
1.建立钩子Mousehook.DLL
(1)选择MFC AppWizard(DLL)创建项目Mousehook;
(2)选择MFC Extension DLL(共享MFC拷贝)类型;
(3)由于VC5没有现成的钩子类,所以要在项目目录中创建Mousehook.h文件,在其中建立钩子类:
class AFX_EXT_CLASS Cmousehook:public CObject
{
public:
Cmousehook();
//钩子类的构造函数
~Cmousehook();
//钩子类的析构函数
BOOL starthook(HWND hWnd);
//安装钩子函数
BOOL stophook();
卸载钩子函数
};
(4)在Mousehook.app文件的顶部加入#include”Mousehook.h”语句;
(5)加入全局共享数据变量:
#pragma data_seg(“mydata”)
HWND glhPrevTarWnd=NULL;
//上次鼠标所指的窗口句柄
HWND glhDisplayWnd=NULL;
//显示目标窗口标题编辑框的句柄
HHOOK glhHook=NULL;
//安装的鼠标勾子句柄
HINSTANCE glhInstance=NULL;
//DLL实例句柄
#pragma data_seg()
(6)在DEF文件中定义段属性:
SECTIONS
mydata READ WRITE SHARED
(7)在主文件Mousehook.cpp的DllMain函数中加入保存DLL实例句柄的语句:
DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
//如果使用lpReserved参数则删除下面这行
UNREFERENCED_PARAMETER(lpReserved);
if (dwReason == DLL_PROCESS_ATTACH)
{
TRACE0(“MOUSEHOOK.DLL Initializing!\n”);
//扩展DLL仅初始化一次
if (!AfxInitExtensionModule(MousehookDLL, hInstance))
return 0;
new CDynLinkLibrary(MousehookDLL);
//把DLL加入动态MFC类库中
glhInstance=hInstance;
//插入保存DLL实例句柄
}
else if (dwReason == DLL_PROCESS_DETACH)
{
TRACE0(“MOUSEHOOK.DLL Terminating!\n”);
//终止这个链接库前调用它
AfxTermExtensionModule(MousehookDLL);
}
return 1;
}
(8)类Cmousehook的成员函数的具体实现:
Cmousehook::Cmousehook()
//类构造函数
{
}
Cmousehook::~Cmousehook()
//类析构函数
{
stophook();
}
BOOL Cmousehook::starthook(HWND hWnd)
//安装钩子并设定接收显示窗口句柄
{
BOOL bResult=FALSE;
glhHook=SetWindowsHookEx(WH_MOUSE,MouseProc,glhInstance,0);
if(glhHook!=NULL)
bResult=TRUE;
glhDisplayWnd=hWnd;
//设置显示目标窗口标题编辑框的句柄
return bResult;
}
BOOL Cmousehook::stophook()
//卸载钩子
{
BOOL bResult=FALSE;
if(glhHook)
{
bResult= UnhookWindowsHookEx(glhHook);
if(bResult)
{
glhPrevTarWnd=NULL;
glhDisplayWnd=NULL;//清变量
glhHook=NULL;
}
}
return bResult;
}
(9)钩子函数的实现:
LRESULT WINAPI MouseProc(int nCode,WPARAM wparam,LPARAM lparam)
{
LPMOUSEHOOKSTRUCT pMouseHook=(MOUSEHOOKSTRUCT FAR *) lparam;
if (nCode>=0)
{
HWND glhTargetWnd=pMouseHook->hwnd;
//取目标窗口句柄
HWND ParentWnd=glhTargetWnd;
while (ParentWnd !=NULL)
{
glhTargetWnd=ParentWnd;
ParentWnd=GetParent(glhTargetWnd);
//取应用程序主窗口句柄
}
if(glhTargetWnd!=glhPrevTarWnd)
{
char szCaption[100];
GetWindowText(glhTargetWnd,szCaption,100);
//取目标窗口标题
if(IsWindow(glhDisplayWnd))
SendMessage(glhDisplayWnd,WM_SETTEXT,0,(LPARAM)(LPCTSTR)szCaption);
glhPrevTarWnd=glhTargetWnd;
//保存目标窗口
}
}
return CallNextHookEx(glhHook,nCode,wparam,lparam);
//继续传递消息
}
(10)编译项目生成mousehook.dll。
2.创建钩子可执行程序
(1)用MFC的AppWizard(EXE)创建项目Mouse;
(2)选择“基于对话应用”并按下“完成”键;
(3)编辑对话框,删除其中原有的两个按钮,加入静态文本框和编辑框,用鼠标右键点击静态文本框,在弹出的菜单中选择“属性”,设置其标题为“鼠标所在的窗口标题”;
(4)在Mouse.h中加入对Mousehook.h的包含语句#Include”..\Mousehook\Mousehook.h”;
(5)在CMouseDlg.h的CMouseDlg类定义中添加私有数据成员:
CMouseHook m_hook;//加入钩子类作为数据成员
(6)修改CmouseDlg::OnInitDialog()函数:
BOOL CMouseDlg::OnInitDialog()
{
CDialog::OnInitDialog();
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX < 0xF000);
CMenu* pSysMenu = GetSystemMenu(FALSE);
if (pSysMenu != NULL)
{
CString strAboutMenu;
strAboutMenu.LoadString(IDS_ABOUTBOX);
if (!strAboutMenu.IsEmpty())
{
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
}
}
SetIcon(m_hIcon, TRUE);//Set big icon
SetIcon(m_hIcon, FALSE);//Set small icon
//TODO: Add extra initialization here
CWnd * pwnd=GetDlgItem(IDC_EDIT1);
//取得编辑框的类指针
m_hook.starthook(pwnd->GetSafeHwnd());
//取得编辑框的窗口句柄并安装钩子
return TRUE;
//return TRUE unless you set the focus to a control
}
(7)链接DLL库,即把..\Mousehook\debug\Mousehook.lib加入到项目设置链接标签中;
(8)编译项目生成可执行文件;
(9)把Mousehook.DLL拷贝到..\mouse\debug目录中;
(10)先运行几个可执行程序,然后运行Mouse.exe程序,把鼠标在不同窗口中移动,在Mouse.exe程序窗口中的编辑框内将显示出鼠标所在的应用程序主窗口的标题。

HOOK编程指南

2009/03/29
1.Hooks
hook指出了系统消息处理机制。利用hook,可以在应用程序中安装子程序监视系统和进程之间的消息传递,这个监视过程是在消息到达目的窗口过程之前。
下面简述hook,并且解释在Win32系统下,如何使用hook编程。
2.About Hooks

hook将使程序效率降低,因为它们增加了系统必须处理的消息总数。你应该在需要时才使用,并及时删除它。我将以下面的主题描述hook。

Hook Chains(hook链)

系统支持很多不同类型的hooks;不同的hook提供不同的消息处理机制。比如,应用程序可以使用WH_MOUSE_hook来监视鼠标消息的传递。
系统为不同类型的hook提供单独的hook链。hook链是一个指针列表,这个列表的指针指向指定的,应用程序定义的,被hook过程调用的回调函数。当与指定的hook类型关联的消息发生时,系统就把这个消息传递到hook过程。一些hook过程可以只监视消息,或者修改消息,或者停止消息的前进,避免这些消息传递到下一个hook过程或者目的窗口。

Hook Procedures(hook过程)
为了利用特殊的hook类型,开发者提供了hook过程,使用SetWindowsHookEx函数来把hook过程安装到关联的hook链。hook过程必须按照以下的语法:
LRESULT CALLBACK HookProc(
int nCode,
WPARAM wParam,
LPARAM lParam
);
HookProc是应用程序定义的名字。
nCode参数是hook代码,hook过程使用这个参数来确定任务。这个参数的值依赖于hook类型,每一种hook都有自己的hook代码特征字符集。wParam和lParam参数的值依赖于hook代码,但是它们的典型值是包含了关于发送或者接收消息的信息。
SetWindowsHookEx函数总是在hook链的开头安装hook过程。当指定类型的hook监视的事件发生时,系统就调用与这个hook关联的hook链的开头的hook过程。每一个hook链中的hook过程都决定是否把这个事件传递到下一个hook过程。hook过程传递事件到下一个hook过程需要调用CallNextHookEx函数。
有些类型hook的hook过程只能监视消息,不管是否调用了CallNextHookEx函数,系统都把消息传递到每一个hook过程。
全局hook监视同一桌面的所有线程。而特定线程的hook只能监视单独的线程。全局hook过程可以被同一桌面的任何应用程序调用,就象调用线程一样,所以这个过程必须和DLL模块分开。特定线程hook过程只可以被相关线程调用。只有在有调试目的的时候才使用全局hook,应该避免使用,全局hook损害了系统性能。
Hook Types
每一种类型的hook可以使应用程序能够监视不同类型的系统消息处理机制。下面描述所有可以利用的hook类型。
WH_CALLWNDPROC and WH_CALLWNDPROCRET Hooks
WH_CALLWNDPROC and WH_CALLWNDPROCRET Hook使你可以监视发送到窗口过程的消息。系统在消息发送到接收窗口过程之前调用WH_CALLWNDPROC hook过程,并且在窗口过程处理完消息之后调用WH_CALLWNDPROCRET Hook过程。
WH_CALLWNDPROCRET Hook传递指针到CWPRETSTRUCT结构,再传递到hook过程。CWPRETSTRUCT结构包含了来自处理消息的窗口过程的返回值,同样也包括了与这个消息关联的消息参数。
WH_CBT Hook
在以下事件之前,系统都会调用WH_CBT Hook过程,这些事件包括:激活,建立,销毁,最小化,最大化,移动,改变尺寸等窗口事件;完成系统指令;来自系统消息队列中的移动鼠标,键盘事件;设置输入焦点事件;同步系统消息队列事件。hook过程的返回值确定系统是否允许或者防止这些操作中的一个。
WH_DEBUG Hook
在系统调用系统中与其他hook关联的hook过程之前,系统会调用WH_DEBUG Hook过程。你可以使用这个hook来决定是否允许系统调用与其他hook关联的hook过程。
WH_FOREGROUNDIDLE Hook
当应用程序的前景线程处于空闲状态时,可以使用WH_FOREGROUNDIDLE Hook执行低优先级的任务。当应用程序的前景线程大概要变成空闲状态时,系统就会调用WH_FOREGROUNDIDLE Hook过程。
WH_GETMESSAGE Hook
应用程序使用WH_GETMESSAGE Hook来监视从GetMessage or PeekMessage函数返回的消息。你可以使用WH_GETMESSAGE Hook去监视鼠标和键盘输入,以及其他发送到消息队列中的消息。
WH_JOURNALPLAYBACK Hook
WH_JOURNALPLAYBACK Hook使应用程序可以插入消息到系统消息队列。可以使用这个hook回放通过使用WH_JOURNALRECORD hook记录下来的连续的鼠标和键盘事件。只要WH_JOURNALPLAYBACK hook已经安装,正常的鼠标和键盘事件就是无效的。WH_JOURNALPLAYBACK hook是全局hook,它不能象线程特定hook一样使用。WH_JOURNALPLAYBACK hook返回超时值,这个值告诉系统在处理来自回放hook当前消息之前需要等待多长时间(毫秒)。这就使hook可以控制实时事件的回放。
WH_JOURNALRECORD Hook
WH_JOURNALRECORD Hook用来监视和记录输入事件。典型的,可以使用这个hook记录连续的鼠标和键盘事件,然后通过使用WH_JOURNALPLAYBACK Hook来回放。WH_JOURNALRECORD hook是全局hook,它不能象线程特定hook一样使用。
WH_KEYBOARD Hook
在应用程序中,WH_KEYBOARD Hook用来监视WM_KEYDOWN and WM_KEYUP消息,这些消息通过GetMessage or PeekMessage function返回。可以使用这个hook来监视输入到消息队列中的键盘消息。
WH_KEYBOARD_LL Hook
WH_KEYBOARD_LL Hook监视输入到线程消息队列中的键盘消息。
WH_MOUSE Hook
WH_MOUSE Hook监视从GetMessage or PeekMessage function返回的鼠标消息。使用这个hook监视输入到消息队列中的鼠标消息。
WH_MOUSE_LL Hook
WH_MOUSE_LL Hook监视输入到线程消息队列中的鼠标消息。
WH_MSGFILTER and WH_SYSMSGFILTER Hooks
WH_MSGFILTER and WH_SYSMSGFILTER Hooks使我们可以监视菜单,滚动条,消息框,对话框消息并且发现用户使用ALT+TAB or ALT+ESC 组合键切换窗口。WH_MSGFILTER hook只能监视传递到菜单,滚动条,消息框的消息,以及传递到通过安装了hook过程的应用程序建立的对话框的消息。WH_SYSMSGFILTER Hook监视所有应用程序消息。
WH_MSGFILTER and WH_SYSMSGFILTER Hooks使我们可以在模式循环期间过滤消息,这等价于在主消息循环中过滤消息。
通过调用CallMsgFilter function可以直接的调用WH_MSGFILTER hook。通过使用这个函数,应用程序能够在模式循环期间使用相同的代码去过滤消息,如同在主消息循环里一样。
WH_SHELL Hook
外壳应用程序可以使用WH_SHELL Hook去接收重要的通知。当外壳应用程序是激活的并且当顶层窗口建立或者销毁时,系统调用WH_SHELL Hook过程。
按照惯例,外壳应用程序都不接收WH_SHELL消息。所以,在应用程序能够接收WH_SHELL消息之前,应用程序必须调用SystemParametersInfo function注册它自己。
3.Using Hooks
Installing and Releasing Hook Procedures
可以使用SetWindowsHookEx function安装hook过程并且指定hook类型,指定是否需要把hook过程与所有线程关联,或者关联指定的线程,并且指向hook过程入口点。
必须把全局hook过程放进DLL,以和应用程序安装的hook过程分开。在应用程序安装hook过程之前,它必须有一个指向DLL模块的句柄。为了得到这个句柄,可以在调用LoadLibrary函数时使用DLL名字参数。在得到这个句柄以后,可以调用GetProcAddress函数来得到hook过程的指针。最后,使用SetWindowsHookEx函数安装hook过程地址进应用程序hook链。这个过程可以用下面的事例说明:
HOOKPROC hkprcSysMsg;
static HINSTANCE hinstDLL;
static HHOOK hhookSysMsg;

hinstDLL = LoadLibrary((LPCTSTR) “c:\\windows\\sysmsg.dll”); file://loading DLL
hkprcSysMsg = (HOOKPROC)GetProcAddress(hinstDLL, “SysMessageProc”); file://get address
hhookSysMsg = SetWindowsHookEx(WH_SYSMSGFILTER,hkprcSysMsg,hinstDLL,0); file://install hook

当应用程序不再需要与特定线程相关hook时,需要调用UnhookWindowsHookEx函数删除相关的hook过程。对于全局hook,也需要调用UnhookWindowsHookEx函数,但是这个函数不能释放DLL包含的hook过程。这是因为全局hook过程是被所有应用程序进程调用的,这就导致了所有的进程都隐性的调用了LoadLibrary函数。所以必须调用FreeLibrary函数释放DLL。

Monitoring System Events

下面的例子使用了不同的特定线程hook过程去监视系统事件。它示范了怎样使用下面的hook过程去处理事件:
WH_CALLWNDPROC
WH_CBT
WH_DEBUG
WH_GETMESSAGE
WH_KEYBOARD
WH_MOUSE
WH_MSGFILTER
用户可以通过使用菜单安装或者移走hook过程。当hook过程已经安装并且过程监视的时间发生时,hook过程将在应用程序主窗口客户区写出事件信息。原代码如下:

#define NUMHOOKS 7

// Global variables

typedef struct _MYHOOKDATA
{
int nType;
HOOKPROC hkprc;
HHOOK hhook;
} MYHOOKDATA;

MYHOOKDATA myhookdata[NUMHOOKS];

LRESULT WINAPI MainWndProc(HWND hwndMain, UINT uMsg, WPARAM wParam,
LPARAM lParam)
{
static BOOL afHooks[NUMHOOKS];
int index;
static HMENU hmenu;

switch (uMsg)
{
case WM_CREATE:

// Save the menu handle.

hmenu = GetMenu(hwndMain);

// Initialize structures with hook data. The menu-item
// identifiers are defined as 0 through 6 in the
// header file. They can be used to identify array
// elements both here and during the WM_COMMAND
// message.

myhookdata[IDM_CALLWNDPROC].nType = WH_CALLWNDPROC;
myhookdata[IDM_CALLWNDPROC].hkprc = CallWndProc;
myhookdata[IDM_CBT].nType = WH_CBT;
myhookdata[IDM_CBT].hkprc = CBTProc;
myhookdata[IDM_DEBUG].nType = WH_DEBUG;
myhookdata[IDM_DEBUG].hkprc = DebugProc;
myhookdata[IDM_GETMESSAGE].nType = WH_GETMESSAGE;
myhookdata[IDM_GETMESSAGE].hkprc = GetMsgProc;
myhookdata[IDM_KEYBOARD].nType = WH_KEYBOARD;
myhookdata[IDM_KEYBOARD].hkprc = KeyboardProc;
myhookdata[IDM_MOUSE].nType = WH_MOUSE;
myhookdata[IDM_MOUSE].hkprc = MouseProc;
myhookdata[IDM_MSGFILTER].nType = WH_MSGFILTER;
myhookdata[IDM_MSGFILTER].hkprc = MessageProc;

// Initialize all flags in the array to FALSE.

memset(afHooks, FALSE, sizeof(afHooks));

return 0;

case WM_COMMAND:
switch (LOWORD(wParam))
{
// The user selected a hook command from the menu.

case IDM_CALLWNDPROC:
case IDM_CBT:
case IDM_DEBUG:
case IDM_GETMESSAGE:
case IDM_KEYBOARD:
case IDM_MOUSE:
case IDM_MSGFILTER:

// Use the menu-item identifier as an index
// into the array of structures with hook data.

index = LOWORD(wParam);

// If the selected type of hook procedure isn””t
// installed yet, install it and check the
// associated menu item.

if (!afHooks[index])
{
myhookdata[index].hhook = SetWindowsHookEx(
myhookdata[index].nType,
myhookdata[index].hkprc,
(HINSTANCE) NULL, GetCurrentThreadId());
CheckMenuItem(hmenu, index,
MF_BYCOMMAND | MF_CHECKED);
afHooks[index] = TRUE;
}

// If the selected type of hook procedure is
// already installed, remove it and remove the
// check mark from the associated menu item.

else
{
UnhookWindowsHookEx(myhookdata[index].hhook);
CheckMenuItem(hmenu, index,
MF_BYCOMMAND | MF_UNCHECKED);
afHooks[index] = FALSE;
}

default:
return (DefWindowProc(hwndMain, uMsg, wParam,
lParam));
}
break;

//
// Process other messages.
//

default:
return DefWindowProc(hwndMain, uMsg, wParam, lParam);
}
return NULL;
}

/****************************************************************
WH_CALLWNDPROC hook procedure
****************************************************************/

LRESULT WINAPI CallWndProc(int nCode, WPARAM wParam, LPARAM lParam)
{
CHAR szCWPBuf[256];
CHAR szMsg[16];
HDC hdc;
static int c = 0;
int cch;

if (nCode < 0)  // do not process message
return CallNextHookEx(myhookdata[CALLWNDPROC].hhook, nCode,
wParam, lParam);

// Call an application-defined function that converts a message
// constant to a string and copies it to a buffer.

LookUpTheMessage((PMSG) lParam, szMsg);

hdc = GetDC(hwndMain);

switch (nCode)
{
case HC_ACTION:
cch = wsprintf(szCWPBuf,
“CALLWNDPROC – tsk: %ld, msg: %s, %d times   “,
wParam, szMsg, c++);
TextOut(hdc, 2, 15, szCWPBuf, cch);
break;

default:
break;
}

ReleaseDC(hwndMain, hdc);
return CallNextHookEx(myhookdata[CALLWNDPROC].hhook, nCode,
wParam, lParam);
}

/****************************************************************
WH_GETMESSAGE hook procedure
****************************************************************/

LRESULT CALLBACK GetMsgProc(int nCode, WPARAM wParam, LPARAM lParam)
{
CHAR szMSGBuf[256];
CHAR szRem[16];
CHAR szMsg[16];
HDC hdc;
static int c = 0;
int cch;

if (nCode < 0) // do not process message
return CallNextHookEx(myhookdata[GETMESSAGE].hhook, nCode,
wParam, lParam);

switch (nCode)
{
case HC_ACTION:
switch (wParam)
{
case PM_REMOVE:
lstrcpy(szRem, “PM_REMOVE”);
break;

case PM_NOREMOVE:
lstrcpy(szRem, “PM_NOREMOVE”);
break;

default:
lstrcpy(szRem, “Unknown”);
break;
}

// Call an application-defined function that converts a
// message constant to a string and copies it to a
// buffer.

LookUpTheMessage((PMSG) lParam, szMsg);

hdc = GetDC(hwndMain);
cch = wsprintf(szMSGBuf,
“GETMESSAGE – wParam: %s, msg: %s, %d times   “,
szRem, szMsg, c++);
TextOut(hdc, 2, 35, szMSGBuf, cch);
break;

default:
break;
}

ReleaseDC(hwndMain, hdc);
return CallNextHookEx(myhookdata[GETMESSAGE].hhook, nCode,
wParam, lParam);
}

/****************************************************************
WH_DEBUG hook procedure
****************************************************************/

LRESULT CALLBACK DebugProc(int nCode, WPARAM wParam, LPARAM lParam)
{
CHAR szBuf[128];
HDC hdc;
static int c = 0;
int cch;

if (nCode < 0)  // do not process message
return CallNextHookEx(myhookdata[DEBUG].hhook, nCode,
wParam, lParam);

hdc = GetDC(hwndMain);

switch (nCode)
{
case HC_ACTION:
cch = wsprintf(szBuf,
“DEBUG – nCode: %d, tsk: %ld, %d times   “,
nCode,wParam, c++);
TextOut(hdc, 2, 55, szBuf, cch);
break;

default:
break;
}

ReleaseDC(hwndMain, hdc);
return CallNextHookEx(myhookdata[DEBUG].hhook, nCode, wParam,
lParam);
}

/****************************************************************
WH_CBT hook procedure
****************************************************************/

LRESULT CALLBACK CBTProc(int nCode, WPARAM wParam, LPARAM lParam)
{
CHAR szBuf[128];
CHAR szCode[128];
HDC hdc;
static int c = 0;
int cch;

if (nCode < 0)  // do not process message
return CallNextHookEx(myhookdata[CBT].hhook, nCode, wParam,
lParam);

hdc = GetDC(hwndMain);

switch (nCode)
{
case HCBT_ACTIVATE:
lstrcpy(szCode, “HCBT_ACTIVATE”);
break;

case HCBT_CLICKSKIPPED:
lstrcpy(szCode, “HCBT_CLICKSKIPPED”);
break;

case HCBT_CREATEWND:
lstrcpy(szCode, “HCBT_CREATEWND”);
break;

case HCBT_DESTROYWND:
lstrcpy(szCode, “HCBT_DESTROYWND”);
break;

case HCBT_KEYSKIPPED:
lstrcpy(szCode, “HCBT_KEYSKIPPED”);
break;

case HCBT_MINMAX:
lstrcpy(szCode, “HCBT_MINMAX”);
break;

case HCBT_MOVESIZE:
lstrcpy(szCode, “HCBT_MOVESIZE”);
break;

case HCBT_QS:
lstrcpy(szCode, “HCBT_QS”);
break;

case HCBT_SETFOCUS:
lstrcpy(szCode, “HCBT_SETFOCUS”);
break;

case HCBT_SYSCOMMAND:
lstrcpy(szCode, “HCBT_SYSCOMMAND”);
break;

default:
lstrcpy(szCode, “Unknown”);
break;
}

cch = wsprintf(szBuf, “CBT – nCode: %s, tsk: %ld, %d times   “,
szCode, wParam, c++);
TextOut(hdc, 2, 75, szBuf, cch);
ReleaseDC(hwndMain, hdc);
return CallNextHookEx(myhookdata[CBT].hhook, nCode, wParam,
lParam);
}

/****************************************************************
WH_MOUSE hook procedure
****************************************************************/

LRESULT CALLBACK MouseProc(int nCode, WPARAM wParam, LPARAM lParam)
{
CHAR szBuf[128];
CHAR szMsg[16];
HDC hdc;
static int c = 0;
int cch;

if (nCode < 0)  // do not process the message
return CallNextHookEx(myhookdata[MOUSE].hhook, nCode,
wParam, lParam);

// Call an application-defined function that converts a message
// constant to a string and copies it to a buffer.

LookUpTheMessage((PMSG) lParam, szMsg);

hdc = GetDC(hwndMain);
cch = wsprintf(szBuf,
“MOUSE – nCode: %d, msg: %s, x: %d, y: %d, %d times   “,
nCode, szMsg, LOWORD(lParam), HIWORD(lParam), c++);
TextOut(hdc, 2, 95, szBuf, cch);
ReleaseDC(hwndMain, hdc);
return CallNextHookEx(myhookdata[MOUSE].hhook, nCode, wParam,
lParam);
}

/****************************************************************
WH_KEYBOARD hook procedure
****************************************************************/

LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
{
CHAR szBuf[128];
HDC hdc;
static int c = 0;
int cch;

if (nCode < 0)  // do not process message
return CallNextHookEx(myhookdata[KEYBOARD].hhook, nCode,
wParam, lParam);

hdc = GetDC(hwndMain);
cch = wsprintf(szBuf, “KEYBOARD – nCode: %d, vk: %d, %d times “,
nCode, wParam, c++);
TextOut(hdc, 2, 115, szBuf, cch);
ReleaseDC(hwndMain, hdc);
return CallNextHookEx(myhookdata[KEYBOARD].hhook, nCode, wParam,
lParam);
}

/****************************************************************
WH_MSGFILTER hook procedure
****************************************************************/

LRESULT CALLBACK MessageProc(int nCode, WPARAM wParam, LPARAM lParam)
{
CHAR szBuf[128];
CHAR szMsg[16];
CHAR szCode[32];
HDC hdc;
static int c = 0;
int cch;

if (nCode < 0)  // do not process message
return CallNextHookEx(myhookdata[MSGFILTER].hhook, nCode,
wParam, lParam);

switch (nCode)
{
case MSGF_DIALOGBOX:
lstrcpy(szCode, “MSGF_DIALOGBOX”);
break;

case MSGF_MENU:
lstrcpy(szCode, “MSGF_MENU”);
break;

case MSGF_SCROLLBAR:
lstrcpy(szCode, “MSGF_SCROLLBAR”);
break;

default:
wsprintf(szCode, “Unknown: %d”, nCode);
break;
}

// Call an application-defined function that converts a message
// constant to a string and copies it to a buffer.

LookUpTheMessage((PMSG) lParam, szMsg);

hdc = GetDC(hwndMain);
cch = wsprintf(szBuf,
“MSGFILTER  nCode: %s, msg: %s, %d times    “,
szCode, szMsg, c++);
TextOut(hdc, 2, 135, szBuf, cch);
ReleaseDC(hwndMain, hdc);
return CallNextHookEx(myhookdata[MSGFILTER].hhook, nCode,
wParam, lParam);
}

利用简单Win32窗口函数捕获用户修改文件夹权限的动作

2009/03/29
今天早上去公司上班,老板去的很早,在我吃带过去的饼的时候,老板和我聊了一下下一步该怎么做。我原以为老板会让我把昨天写的代码集成到现有的系统里去的。哪知道老板说,现在的系统已经很稳定了,希望我能把我写的代码作为一个独立的进程来运行,以免影响到原有的系统的稳定。这下可难着我了,我怎么知道用户什么时候修改了文件夹的权限呢?本来我的如意算盘是:当用户把文件上传到服务器前,我检测一下文件夹的权限,然后做相应的修改。这个时机是可以把握住的。现在却要让我捕获用户修改权限的动作。幸好以前看的书多,多少知道些概念,似乎可以用HOOK来实现。单独的进程也可以作为服务隐藏进Services.exe中。看看老板期待的眼神,我只好说,让我尽力试试吧。
于是开始查资料了。首先看的还是Jeffrey Richter先生的书:Programming Applications for Microsoft Windows, Fourth Edition,准备研究一下怎么写一个HOOK DLL。但是后来因为没有例子程序,再加上这个我想肯定要花很长的时间来研究。时间长了,老板肯定不会很高兴的。还是找个简单的方法吧。不知怎的,突然想起FindWindow函数了,后来查查资料,换成了GetForegroundWindow。我想用户修改文件夹的权限肯定是通过文件夹的属性的安全选项卡来进行的。这样只要用户打开了安全编辑框,就假设用户想修改权限了,接着就获得文件夹的路径,检测权限有没有被修改。嗯,似乎也可以成为一个解决方案。
刚开始,进展都很顺利,顺利得到文件夹名。但是文件夹的路径却难到我了。这个文件夹的名字是通过对话框的标题中得到的。路径一下在找不到可以从哪里得到。查找MSDN,也没有找到有这样的函数。再仔细看看文件夹的属性对话框,发现“常规”选项卡里有这个信息。哈哈。信息是有了,但是怎么才能得到呢?首先想到的当然是微软有没有提供通过文件夹名字来返回文件夹属性对话框中显示的信息的函数呢?查找了一下,很失望的发现:答案是NO。想了想,也只能通过窗口之间的关系来得到了。这次还第一次用到了EnumChildWindows函数。可气的是,一开始,通过调用GetWindowText只能得到“常规”选项卡左边的信息,右边返回的都是空。后来仔细用鼠标点了点,发现光标并不一样。左边的不可以选中,右边的是可以的。看来窗口类不一样了。后来摸索了好久,终于通过SendMessage(hwnd, WM_GETTEXT, 256, (LPARAM)szBufText);完成了任务。:)
下面是我的代码。基本属于原创。嗯,不错!^_^
以下是DLL代码

#include "stdafx.h"
#include <Windows.h>
#include <tchar.h>
#include <atlstr.h>
#include <string>
using namespace std;
BOOL CALLBACK EnumChildProc(HWND hwnd, LPARAM lParam);
TCHAR szBufText[1024];
int _tmain(int argc, _TCHAR* argv[])
{
 HWND hActiveWnd;
 CString szPath, szFoldName, szLocation;
 while (1) 
 {
  if ((hActiveWnd = GetForegroundWindow())) 
  {
   int len = GetWindowTextLength(hActiveWnd);
   GetWindowText(hActiveWnd, szBufText, len + 1);
   CString str(szBufText);
   int idx;
   if ((idx = str.Find(_T("高级安全设置"), 0)) != -1) 
   {
    idx -= 2;
    // 获得文件夹的名字。
    szBufText[idx] = '''';
    szFoldName = szBufText;
    // 从文件夹属性对话框中获得文件夹路径。-
    HWND hWnd = GetParent(hActiveWnd);
    hWnd = FindWindowEx(GetParent(hActiveWnd), NULL, NULL, _T("常规"));
    if (hWnd) 
    {
     while (1) 
     {
      if (!EnumChildWindows(hWnd, EnumChildProc, NULL)) 
      {
       szLocation = szBufText;
       if (szLocation[szLocation.GetLength() - 1] == ''\'')
        szPath = szLocation + szFoldName;
       else
        szPath = szLocation + "\" + szFoldName;
        _tprintf(szBufText);
       _tprintf(_T(" "));
       MessageBox(NULL, szPath, _T("文件夹路径:"), MB_ICONQUESTION);
       memset(szBufText, 0, sizeof(TCHAR));
       break;
      }
     }
    }
   }
  }
  return 0;
 }
}
BOOL CALLBACK EnumChildProc(HWND hwnd, LPARAM lParam)
{
 SendMessage(hwnd, WM_GETTEXT, 256, (LPARAM)szBufText);
 CString str(szBufText);
 CString strPat(_T(":\"));
  if (str.Find(strPat, 0) != -1)
   return FALSE; // stop.
 return TRUE; // continue.
} 

COM编程基本知识

2009/03/29

用VC进行COM编程,必须要掌握哪些COM理论知识

我见过很多人学COM,看完一本书后觉得对COM的原理比较了解了,COM也不过如此,可是就是不知道该怎么编程序,我自己也有这种情况,我也是经历了这样的阶段走过来的。要学COM的基本原理,我推荐的书是《COM技术内幕》。但仅看这样的书是远远不够的,我们最终的目的是要学会怎么用COM去编程序,而不是拼命的研究COM本身的机制。所以我个人觉得对COM的基本原理不需要花大量的时间去追根问底,没有必要,是吃力不讨好的事。其实我们只需要掌握几个关键概念就够了。这里我列出了一些我自己认为是用VC编程所必需掌握的几个关键概念。(这里所说的均是用C++语言条件下的COM编程方式)

(1) COM组件实际上是一个C++类,而接口都是纯虚类。组件从接口派生而来。我们可以简单的用纯粹的C++的语法形式来描述COM是个什么东西:

class IObject
{
public:
virtual Function1(…) = 0;
virtual Function2(…) = 0;
….
};
class MyObject : public IObject
{
public:
virtual Function1(…){…}
virtual Function2(…){…}
….
};

看清楚了吗?IObject就是我们常说的接口,MyObject就是所谓的COM组件。切记切记接口都是纯虚类,它所包含的函数都是纯虚函数,而且它没有成员变量。而COM组件就是从这些纯虚类继承下来的派生类,它实现了这些虚函数,仅此而已。从上面也可以看出,COM组件是以 C++为基础的,特别重要的是虚函数和多态性的概念,COM中所有函数都是虚函数,都必须通过虚函数表VTable来调用,这一点是无比重要的,必需时刻牢记在心。为了让大家确切了解一下虚函数表是什么样子,从《COM+技术内幕》中COPY了下面这个示例图:

(2) COM组件有三个最基本的接口类,分别是IUnknown、IClassFactory、IDispatch。

COM规范规定任何组件、任何接口都必须从IUnknown继承,IUnknown包含三个函数,分别是 QueryInterface、AddRef、Release。这三个函数是无比重要的,而且它们的排列顺序也是不可改变的。QueryInterface用于查询组件实现的其它接口,说白了也就是看看这个组件的父类中还有哪些接口类,AddRef用于增加引用计数,Release用于减少引用计数。引用计数也是COM中的一个非常重要的概念。大体上简单的说来可以这么理解,COM组件是个DLL,当客户程序要用它时就要把它装到内存里。另一方面,一个组件也不是只给你一个人用的,可能会有很多个程序同时都要用到它。但实际上DLL只装载了一次,即内存中只有一个COM组件,那COM组件由谁来释放?由客户程序吗?不可能,因为如果你释放了组件,那别人怎么用,所以只能由COM组件自己来负责。所以出现了引用计数的概念,COM维持一个计数,记录当前有多少人在用它,每多一次调用计数就加一,少一个客户用它就减一,当最后一个客户释放它的时侯,COM知道已经没有人用它了,它的使用已经结束了,那它就把它自己给释放了。引用计数是COM编程里非常容易出错的一个地方,但所幸VC的各种各样的类库里已经基本上把AddRef的调用给隐含了,在我的印象里,我编程的时侯还从来没有调用过AddRef,我们只需在适当的时侯调用Release。至少有两个时侯要记住调用Release,第一个是调用了 QueryInterface以后,第二个是调用了任何得到一个接口的指针的函数以后,记住多查MSDN 以确定某个函数内部是否调用了AddRef,如果是的话那调用Release的责任就要归你了。 IUnknown的这三个函数的实现非常规范但也非常烦琐,容易出错,所幸的事我们可能永远也不需要自己来实现它们。

IClassFactory的作用是创建COM组件。我们已经知道COM组件实际上就是一个类,那我们平常是怎么实例化一个类对象的?是用‘new’命令!很简单吧,COM组件也一样如此。但是谁来new它呢?不可能是客户程序,因为客户程序不可能知道组件的类名字,如果客户知道组件的类名字那组件的可重用性就要打个大大的折扣了,事实上客户程序只不过知道一个代表着组件的128位的数字串而已,这个等会再介绍。所以客户无法自己创建组件,而且考虑一下,如果组件是在远程的机器上,你还能new出一个对象吗?所以创建组件的责任交给了一个单独的对象,这个对象就是类厂。每个组件都必须有一个与之相关的类厂,这个类厂知道怎么样创建组件,当客户请求一个组件对象的实例时,实际上这个请求交给了类厂,由类厂创建组件实例,然后把实例指针交给客户程序。这个过程在跨进程及远程创建组件时特别有用,因为这时就不是一个简单的new操作就可以的了,它必须要经过调度,而这些复杂的操作都交给类厂对象去做了。IClassFactory最重要的一个函数就是CreateInstance,顾名思议就是创建组件实例,一般情况下我们不会直接调用它,API函数都为我们封装好它了,只有某些特殊情况下才会由我们自己来调用它,这也是VC编写COM组件的好处,使我们有了更多的控制机会,而VB给我们这样的机会则是太少太少了。

IDispatch叫做调度接口。它的作用何在呢?这个世上除了C++还有很多别的语言,比如VB、 VJ、VBScript、JavaScript等等。可以这么说,如果这世上没有这么多乱七八糟的语言,那就不会有IDispatch。:-) 我们知道COM组件是C++类,是靠虚函数表来调用函数的,对于VC来说毫无问题,这本来就是针对C++而设计的,以前VB不行,现在VB也可以用指针了,也可以通过VTable来调用函数了,VJ也可以,但还是有些语言不行,那就是脚本语言,典型的如 VBScript、JavaScript。不行的原因在于它们并不支持指针,连指针都不能用还怎么用多态性啊,还怎么调这些虚函数啊。唉,没办法,也不能置这些脚本语言于不顾吧,现在网页上用的都是这些脚本语言,而分布式应用也是COM组件的一个主要市场,它不得不被这些脚本语言所调用,既然虚函数表的方式行不通,我们只能另寻他法了。时势造英雄,IDispatch应运而生。:-) 调度接口把每一个函数每一个属性都编上号,客户程序要调用这些函数属性的时侯就把这些编号传给IDispatch接口就行了,IDispatch再根据这些编号调用相应的函数,仅此而已。当然实际的过程远比这复杂,仅给一个编号就能让别人知道怎么调用一个函数那不是天方夜潭吗,你总得让别人知道你要调用的函数要带什么参数,参数类型什么以及返回什么东西吧,而要以一种统一的方式来处理这些问题是件很头疼的事。IDispatch接口的主要函数是Invoke,客户程序都调用它,然后Invoke再调用相应的函数,如果看一看MS的类库里实现 Invoke的代码就会惊叹它实现的复杂了,因为你必须考虑各种参数类型的情况,所幸我们不需要自己来做这件事,而且可能永远也没这样的机会。:-)

(3) dispinterface接口、Dual接口以及Custom接口

这一小节放在这里似乎不太合适,因为这是在ATL编程时用到的术语。我在这里主要是想谈一下自动化接口的好处及缺点,用这三个术语来解释可能会更好一些,而且以后迟早会遇上它们,我将以一种通俗的方式来解释它们,可能并非那么精确,就好象用伪代码来描述算法一样。-:)

所谓的自动化接口就是用IDispatch实现的接口。我们已经讲解过IDispatch的作用了,它的好处就是脚本语言象VBScript、 JavaScript也能用COM组件了,从而基本上做到了与语言无关它的缺点主要有两个,第一个就是速度慢效率低。这是显而易见的,通过虚函数表一下子就可以调用函数了,而通过Invoke则等于中间转了道手续,尤其是需要把函数参数转换成一种规范的格式才去调用函数,耽误了很多时间。所以一般若非是迫不得已我们都想用VTable的方式调用函数以获得高效率。第二个缺点就是只能使用规定好的所谓的自动化数据类型。如果不用IDispatch我们可以想用什么数据类型就用什么类型,VC会自动给我们生成相应的调度代码。而用自动化接口就不行了,因为Invoke的实现代码是VC事先写好的,而它不能事先预料到我们要用到的所有类型,它只能根据一些常用的数据类型来写它的处理代码,而且它也要考虑不同语言之间的数据类型转换问题。所以VC自动化接口生成的调度代码只适用于它所规定好的那些数据类型,当然这些数据类型已经足够丰富了,但不能满足自定义数据结构的要求。你也可以自己写调度代码来处理你的自定义数据结构,但这并不是一件容易的事。考虑到IDispatch的种种缺点(它还有一个缺点,就是使用麻烦,:-) )现在一般都推荐写双接口组件,称为dual接口,实际上就是从IDispatch继承的接口。我们知道任何接口都必须从 IUnknown继承,IDispatch接口也不例外。那从IDispatch继承的接口实际上就等于有两个基类,一个是IUnknown,一个是IDispatch,所以它可以以两种方式来调用组件,可以通过 IUnknown用虚函数表的方式调用接口方法,也可以通过IDispatch::Invoke自动化调度来调用。这就有了很大的灵活性,这个组件既可以用于C++的环境也可以用于脚本语言中,同时满足了各方面的需要。

相对比的,dispinterface是一种纯粹的自动化接口,可以简单的就把它看作是IDispatch接口 (虽然它实际上不是的),这种接口就只能通过自动化的方式来调用,COM组件的事件一般都用的是这种形式的接口。

Custom接口就是从IUnknown接口派生的类,显然它就只能用虚函数表的方式来调用接口了

(4) COM组件有三种,进程内、本地、远程。对于后两者情况必须调度接口指针及函数参数。

COM是一个DLL,它有三种运行模式。它可以是进程内的,即和调用者在同一个进程内,也可以和调用者在同一个机器上但在不同的进程内,还可以根本就和调用者在两台机器上。这里有一个根本点需要牢记,就是COM组件它只是一个DLL,它自己是运行不起来的,必须有一个进程象父亲般照顾它才行,即COM组件必须在一个进程内.那谁充当看护人的责任呢?先说说调度的问题。调度是个复杂的问题,以我的知识还讲不清楚这个问题,我只是一般性的谈谈几个最基本的概念。我们知道对于WIN32程序,每个进程都拥有4GB的虚拟地址空间,每个进程都有其各自的编址,同一个数据块在不同的进程里的编址很可能就是不一样的,所以存在着进程间的地址转换问题。这就是调度问题。对于本地和远程进程来说,DLL 和客户程序在不同的编址空间,所以要传递接口指针到客户程序必须要经过调度。Windows 已经提供了现成的调度函数,就不需要我们自己来做这个复杂的事情了。对远程组件来说函数的参数传递是另外一种调度。DCOM是以RPC为基础的,要在网络间传递数据必须遵守标准的网上数据传输协议,数据传递前要先打包,传递到目的地后要解包,这个过程就是调度,这个过程很复杂,不过Windows已经把一切都给我们做好了,一般情况下我们不需要自己来编写调度DLL。

我们刚说过一个COM组件必须在一个进程内。对于本地模式的组件一般是以EXE的形式出现,所以它本身就已经是一个进程。对于远程DLL,我们必须找一个进程,这个进程必须包含了调度代码以实现基本的调度。这个进程就是dllhost.exe。这是COM默认的DLL代理。实际上在分布式应用中,我们应该用MTS来作为DLL代理,因为MTS有着很强大的功能,是专门的用于管理分布式DLL组件的工具。

调度离我们很近又似乎很远,我们编程时很少关注到它,这也是COM的一个优点之一,既平台无关性,无论你是远程的、本地的还是进程内的,编程是一样的,一切细节都由COM自己处理好了,所以我们也不用深究这个问题,只要有个概念就可以了,当然如果你对调度有自己特殊的要求就需要深入了解调度的整个过程了,这里推荐一本《COM+技术内幕》,这绝对是一本讲调度的好书。

(5) COM组件的核心是IDL。

我们希望软件是一块块拼装出来的,但不可能是没有规定的胡乱拼接,总是要遵守一定的标准,各个模块之间如何才能亲密无间的合作,必须要事先共同制订好它们之间交互的规范,这个规范就是接口。我们知道接口实际上都是纯虚类,它里面定义好了很多的纯虚函数,等着某个组件去实现它,这个接口就是两个完全不相关的模块能够组合在一起的关键试想一下如果我们是一个应用软件厂商,我们的软件中需要用到某个模块,我们没有时间自己开发,所以我们想到市场上找一找看有没有这样的模块,我们怎么去找呢?也许我们需要的这个模块在业界已经有了标准,已经有人制订好了标准的接口,有很多组件工具厂商已经在自己的组件中实现了这个接口,那我们寻找的目标就是这些已经实现了接口的组件,我们不关心组件从哪来,它有什么其它的功能,我们只关心它是否很好的实现了我们制订好的接口。这种接口可能是业界的标准,也可能只是你和几个厂商之间内部制订的协议,但总之它是一个标准,是你的软件和别人的模块能够组合在一起的基础,是COM组件通信的标准。

COM具有语言无关性,它可以用任何语言编写,也可以在任何语言平台上被调用。但至今为止我们一直是以C++的环境中谈COM,那它的语言无关性是怎么体现出来的呢?或者换句话说,我们怎样才能以语言无关的方式来定义接口呢?前面我们是直接用纯虚类的方式定义的,但显然是不行的,除了C++谁还认它呢?正是出于这种考虑,微软决定采用IDL来定义接口。说白了,IDL实际上就是一种大家都认识的语言,用它来定义接口,不论放到哪个语言平台上都认识它。我们可以想象一下理想的标准的组件模式,我们总是从IDL开始,先用IDL制订好各个接口,然后把实现接口的任务分配不同的人,有的人可能善长用VC,有的人可能善长用VB,这没关系,作为项目负责人我不关心这些,我只关心你把最终的DLL 拿给我。这是一种多么好的开发模式,可以用任何语言来开发,也可以用任何语言来欣赏你的开发成果。

(6) COM组件的运行机制,即COM是怎么跑起来的。

这部分我们将构造一个创建COM组件的最小框架结构,然后看一看其内部处理流程是怎样的

IUnknown *pUnk=NULL;
IObject *pObject=NULL;
CoInitialize(NULL);
CoCreateInstance(CLSID_Object, CLSCTX_INPROC_SERVER, NULL, IID_IUnknown, (void**)&pUnk);
pUnk->QueryInterface(IID_IOjbect, (void**)&pObject);
pUnk->Release();
pObject->Func();
pObject->Release();
CoUninitialize();

这就是一个典型的创建COM组件的框架,不过我的兴趣在CoCreateInstance身上,让我们来看看它内部做了一些什么事情。以下是它内部实现的一个伪代码:

CoCreateInstance(….)
{
…….
IClassFactory *pClassFactory=NULL;
CoGetClassObject(CLSID_Object, CLSCTX_INPROC_SERVER, NULL, IID_IClassFactory, (void **)&pClassFactory);
pClassFactory->CreateInstance(NULL, IID_IUnknown, (void**)&pUnk);
pClassFactory->Release();
……..
}

这段话的意思就是先得到类厂对象,再通过类厂创建组件从而得到IUnknown指针。继续深入一步,看看CoGetClassObject的内部伪码:

CoGetClassObject(…..)
{
//通过查注册表CLSID_Object,得知组件DLL的位置、文件名
//装入DLL库
//使用函数GetProcAddress(…)得到DLL库中函数DllGetClassObject的函数指针。
//调用DllGetClassObject
}
DllGetClassObject是干什么的,它是用来获得类厂对象的。只有先得到类厂才能去创建组件.
下面是DllGetClassObject的伪码:
DllGetClassObject(…)
{
……
CFactory* pFactory= new CFactory; //类厂对象
pFactory->QueryInterface(IID_IClassFactory, (void**)&pClassFactory);
//查询IClassFactory指针
pFactory->Release();
……
}
CoGetClassObject的流程已经到此为止,现在返回CoCreateInstance,看看CreateInstance的伪码:
CFactory::CreateInstance(…..)
{
………..
CObject *pObject = new CObject; //组件对象
pObject->QueryInterface(IID_IUnknown, (void**)&pUnk);
pObject->Release();
………..
}

下图是从COM+技术内幕中COPY来的一个例图,从图中可以清楚的看到CoCreateInstance的整个流程。

(7) 一个典型的自注册的COM DLL所必有的四个函数

DllGetClassObject:用于获得类厂指针

DllRegisterServer:注册一些必要的信息到注册表中

DllUnregisterServer:卸载注册信息

DllCanUnloadNow:系统空闲时会调用这个函数,以确定是否可以卸载DLL

DLL还有一个函数是DllMain,这个函数在COM中并不要求一定要实现它,但是在VC生成的组件中自动都包含了它,它的作用主要是得到一个全局的实例对象。

(8) 注册表在COM中的重要作用

首先要知道GUID的概念,COM中所有的类、接口、类型库都用GUID来唯一标识,GUID是一个128位的字串,根据特制算法生成的GUID可以保证是全世界唯一的。 COM组件的创建,查询接口都是通过注册表进行的。有了注册表,应用程序就不需要知道组件的DLL文件名、位置,只需要根据CLSID查就可以了。当版本升级的时侯,只要改一下注册表信息就可以神不知鬼不觉的转到新版本的DLL。

本文是本人一时兴起的涂鸭之作,讲得并不是很全面,还有很多有用的体会没写出来,以后如果有时间有兴趣再写出来。希望这篇文章能给大家带来一点用处,那我一晚上的辛苦就没有白费了。-:)

用ATL的注册器来定制注册表入口

2009/03/29

有时候用单独定制的注册脚本添加河删除注册表键值是常有的事,不依赖ATL的_Module.RegisterServer() 和 _Module.UnregisterServer()默认例程。
通常在注册每一个COM类时,这些ATL函数都要遍历定义在模块对象映射中的对象清单,同时还要编辑相应的注册表入口。然后,DECLARE_REGISTRY_RESOURCEID宏在每一个类中定义一个静态方法,UpdateRegistry(),又注册例程调用,而UpdateRegistry()本身则调用_Module.UpdateRegistryFromResource()来处理注册脚本资源。
本文说的另外一种可能的方法是独立于_Module.[Un]RegisterServer()调用进行ATL的注册。方法和步骤如下:

在工程中插入新的”REGISTRY”类型资源。
为资源指定新的.rgs外部文件(在属性中指定文件名)。
添加新的注册脚本到.rgs文件。
调用CComModule::UpdateRegistryFromResource添加或删除在新注册脚本中指定的注册入口,例如:

HRESULT hr = _Module.UpdateRegistryFromResource(IDR_MyRegistryEntries, TRUE);
_ASSERTE(SUCCEEDED(hr));

最后一个参数表示示添加(TRUE)还是删除(FALSE)。记住一定要指定”NoRemove”标示脚本中的共享键,以便在注销注册表键时只删除你自己安装的入口。UpdateRegistryFromResource() 实际上是一个宏,如果你定义了_ATL_STATIC_REGISTRY ,则这个宏被定义成UpdateRegistryFromResourceS()(静态链接注册器),否则这个宏被定义成UpdateRegistryFromResourceD()(通过ATL.DLL动态链接注册器)。

ATL初探

2009/03/29

介绍
本教程的目的是告诉你如何使用ATL创建一个COM服务器,并使用Visual C++和Visual Basic程序来分别调用这个服务器。我并不想深入探讨COM的细节,也不想让你深陷于IDL之中。这一教程只是为VC++的新手程序员设计的,告诉他们利用ATL来创建一个COM对象有多么简单,并让他们能对ATL产生更多的兴趣。

第1步:启动ATL COM Wizard

你所需要做的第一件事情就是启动Visual C++并创建一个新的工程,选择“ATL COM Wizard”,工程名为“Simple_ATL”。设置好工程的路径之后,单击OK按钮。你会看到,屏幕上给了你若干选项。第一个选项为“Server Type”。我们将要创建一个服务器DLL,所以请确认服务器的类型选为“Dynamic Link Library”。我们并不需要关心下面的其它三个复选框,所以我们可以将它们忽略掉。按下Finish按钮,这样向导就会为你产生适当的文件了。之后,一个“New Project Information”窗口就会出现,你可以从上面得知向导都会创建什么文件,按下“OK”接受这一切。

第2步:创建一个新的ATL对象

请确认你能在VC++的IDE中看到Workspace View,如果不能的话则请单击“View”菜单,然后选择“Workspace”。在这个视图中你会看到三个选项卡,请单击“ClassView”栏,你应该会看到“Simple_ATL Classes”。请在此右击鼠标键,并在弹出菜单中选择“New ATL Object”,你将会看到下面这样的窗口:

默认的选择项“Simple Object”就是我们所要的了,请单击next按钮,你会来到“ATL Object Wizard Properties”窗口中。在“Short Name”文本框中输入“First_ATL”。请注意,这时候向导就会自动地填写其它的文本框。然后,单击顶部的“Attributes”标签,在这里你需要做一些选择。第一项线程模型(Threading Model)的选择,我们选择默认的单元(Apartment)模型。对于接口(Interface),我们选择双重(Dual)。最后,因为我们的程序与聚合(Aggregation)无关,所以我们选择“No”的单选按钮。你不必关心底部的三个复选框,直接单击OK就可以了,这时候向导就会为我们创建一个新的ATL简单对象。

第3步:添加一个方法

如果你现在在工作区中单击了“ClassView”标签,那么你会注意到向导在其中添加了一串东西。我们想添加的第一个东西是一个方法,可以在“IFirst_ATL”上右击鼠标键,并选择“Add Method”。

一旦你单击了“Add Method”之后,你就会看到“Add Method to Interface”窗口。在返回值类型(Return Type)处你会看到,这个方法会默认返回HRESULT,在大多数情况下你不需要改变它。下一个文本框允许我们输入方法的名称,我们可以输入“AddNumbers”。最后一个文本框是让我们输入参数的,由于我们想做两个数的相加并获得一个返回的结果,所以我们需要三个参数,并且最后一个参数是一个指针。现在,我们不必看那关于IDL的300页教程了,可以直接在参数文本框中输入:
[in] long Num1, [in] long Num2, [out] long *ReturnVal
简单地说来,我们声明了两个long类型的参数,这两个值是传入的([in]),还有一个最后传出的返回值结果([out])。(你第一次看到这样的东西可能会有些奇怪,但是如果你读了一两本关于COM的书的话,就会觉得亲切多了。)现在就可以单击OK按钮了。然后,单击“ClassView”标签,并展开所有的“+”标志,使得树型视图完全展开。你会在接口(IFirst_ATL)的顶部看到我们的“AddNumbers”方法以及我们给予它的参数。在这个方法上双击鼠标键,并插入以下的代码:
STDMETHODIMP CFirst_ATL::AddNumbers(long Num1,
long Num2, long *ReturnVal)
{
// TODO: Add your implementation code here
*ReturnVal = Num1 + Num2;

return S_OK;
}

第4步:编译DLL

无论你相信与否,你已经拥有一个用ATL编写的COM服务器了!当然,我们还需要编译它。请按下F7键,这样VC++就可以编译了。编译器工作片刻后就会在注册表中注册你的新DLL了,这样一来其它的程序就可以使用它了。让我们来测试一下。

第5步:在Visual Basic中测试COM服务器

那么,先让我们用VB来测试这个COM服务器吧。(如果你没有VB的话,你可以跳过这一节,直接在VC++中测试。)启动VB,并选择“标准EXE(Standard EXE)”建立工程,并在对话框上放置一个命令按钮。现在,我们需要添加COM服务器的引用,请单击“工程(Project)”菜单并选择“引用(References)”,找到“Simple ATL 1.0 Type Library”并选择它。

单击确定(OK)按钮之后,你可以双击先前放置的命令按钮,VB会切换到这个按钮的代码窗口。添加以下的代码:
Private Sub Command1_Click()
Dim objTestATL As SIMPLE_ATLLib.First_ATL
Set objTestATL = New First_ATL

Dim lngReturnValue As Long

objTestATL.AddNumbers 5, 7, lngReturnValue

MsgBox “The value of 5 + 7 is: ” & lngReturnValue

Set objTestATL = Nothing
End Sub
如果你是个VB的程序员,那么这些代码对于你是很直观的:我们声明了一个对象,并从COM服务器调用“AddNumbers”的方法,然后显示结果。现在按下F5来运行这个VB工程,单击命令按钮,你就会看到期望的结果了:

并不是很难吧?那么我们再来一次,这一次用VC++。

第6步:在Visual C++中测试COM服务器

如果你的Simple_ATL工程仍然开着,那么就关了它并创建一个新工程。选择“Win32 Console Application”,起名为“Test_ATL”,在下一个窗口中单击OK按钮接受所有默认值,最后单击Finish按钮。现在,你应该有了一个空工程。那么,按下Ctrl+N为工程加入一个新文件,选择“C++ Source File”并命名为“Test_ATL.cpp”,单击OK接受。你现在应该有了一个打开的空白文件,我们需要在其中添加一些代码来测试COM服务器。代码如下:
// 你需要指明Simple_ATL工程的路径来引用这个头文件

#include “..\Simple_ATL\Simple_ATL.h”
#include <iostream.h>

// 把以下的内容从Simple_ATL工程目录的Simple_ATL_i.c文件中复制过来
// 注意:你也可以直接包含Simple_ATL_i.c文件,我在此只想清楚地表明这些const常量来自何处以及它们的样子

const IID IID_IFirst_ATL =
{0xC8F6E230,0×2672,0x11D3,
{0xA8,0xA8,0×00,0×10,0x5A,0xA9,0×43,0xDF}};

const CLSID CLSID_First_ATL =
{0x970599E0,0×2673,0x11D3,
{0xA8,0xA8,0×00,0×10,0x5A,0xA9,0×43,0xDF}};

void main(void)
{
// 声明一个HRESULT变量以及一个Simple_ATL接口的指针
HRESULT          hr;
IFirst_ATL       *IFirstATL = NULL;

// 现在初始化COM
hr = CoInitialize(0);

// 使用SUCCEEDED宏来看看我们是否能够获得接口的指针
if(SUCCEEDED(hr))
{
hr = CoCreateInstance( CLSID_First_ATL, NULL,
CLSCTX_INPROC_SERVER,
IID_IFirst_ATL, (void**) &IFirstATL);

// 如果成功了,那么调用AddNumbers方法
// 否则给用户显示一条适当的信息
if(SUCCEEDED(hr))
{
long ReturnValue;

IFirstATL->AddNumbers(5, 7, &ReturnValue);
cout << “The answer for 5 + 7 is: “
<< ReturnValue << endl;
IFirstATL->Release();
}
else
{
cout << “CoCreateInstance Failed.” << endl;
}
}
// 卸载COM
CoUninitialize();
}

第7步:编译并运行程序

现在你可以按下F5键来编译程序,然后按下Ctrl+F5来运行之。你应该可以看到一个DOS窗口,给出了你所期望的结果。

实现和IE浏览器交互的几种方法的介绍

2009/03/29

实现和IE浏览器交互的几种方法的介绍

Posted by dengwei under doc

内容
实现和浏览器交互的几种方法的介绍
—- 1.引言

—- 如何实现对浏览器中对象的操作是一个很有实际意义问题,通过和绑定的DLL我们可以记录浏览过的网页的顺序,分析用户的使用行为和模式。我们可以对网页的内容进行过滤和翻译,可以自动填写网页中经常需要用户填写的Form内容等等,我们所有的例子代码都是通过VC来表示的,采用的原理是通过和对象的接口的交互来实现对的访问。实际上是采用COM的技术,我们知道COM是和语言无关的一种二进制对象交互的模式,所以实际上我们下面所描述的内容都可以用其他的语言来实现,比如VB,DELPHI,C++ Builder等等。

—- 2.实例遍历实现

—- 首先我们来看系统是如何知道当前有多少个的实例在运行。

—- 我们知道在体系结构下,一个应用程序可以通过操作系统的运行对象表来和这些应用的实例进行交互。但是当前的实现机制是不在运行对象表中进行注册,所以需要采用其他的方法。我们知道可以通过ShellWindows集合来代表属于的当前打开的窗口的集合,而就是属于的一个应用程序。

—- 下面我们描述一下用VC实现对当前 实例的进行遍历的方法。IShellWindows是关于系统的一个接口,我们可以定义一个如下的接口变量:

SHDocVw::IShellWindowsPtr m_spSHWinds;
然后创建变量的实例:
m_spSHWinds.CreateInstance
(__uuidof(SHDocVw::ShellWindows));
通过IShellWindows接口的方法GetCount
可以得到当前实例的数目:
long nCount = m_spSHWinds- >GetCount();
通过IShellWindows接口的方法Item
可以得到每一个实例对象
IDispatchPtr spDisp;
_variant_t va(i, VT_I4);
spDisp = m_spSHWinds->Item(va);
然后我们可以判断实例对象是不是
属于浏览器对象,通过下面的语句实现:
SHDocVw::IWebBrowser2Ptr spBrowser(spDisp);
assert(spBrowser != NULL)

—-在得到了浏览器对象以后,我们可以调用IWebBrowser2Ptr接口的方法来得到当前的文档对象的指针: MSHTML::IHTMLDocument2Ptr spDoc(spBrowser->GetDocument());

—- 然后我们就可以通过这个接口对这个文档对象进行操作,比如通过Gettitle得到文档的标题。

—- 我们在浏览网络的时候,一般总会同时开很多的实例,如果这些页面都是很好的话,我们可能想保存在硬盘上,这样,我们需要对每一个实例进行保存,而如果我们采用上面的原理,我们可以得到每一个的实例及其网页对象的接口,这样就可以通过一个简单的程序来批量的保存当前的所有打开的网页。采用上面介绍的方法实现了对当前实例的遍历,但是我们希望得到每一个实例所产生的事件,这就需要通过DLL的机制来实现。

—- 3.和相绑定的DLL的实现

—- 我们介绍一下如何建立和进行绑定的DLL的实现的过程。为了和的运行实例进行绑定,我们需要建立一个能够和每一个实例进行绑定的DLL。的启动过程是这样的,当每一个的实例启动的时候,它都会在注册表中去寻找这个的一个CLSID,具体的注册表的键位置为:

HKEY_LOCALL_MACHINESOFTWAREMicrosoftWindows
CurrentVersionExplorerBrowser Helper Objects

—- 当在这个键位置下存在CLSIDs的时候,会通过使用CoCreateInstance()方法来创建列在该键位置下的每一个对象的实例。注意对象的CLSIDs必须用子键而非启动过程是这样的,当每一个的实例启动的时候名字值的形式表现,比如{DD41D66E-CE4F-11D2-8DA9-00A0249EABF4} 就是一个有效的子键。我们使用DLL的形式而非EXE的形式的原因是因为DLL和实例运行在同一个进程空间里面。每一个这种形式的DLL必须实现接口IObjectWithSite,其中方法SetSite必须被实现。通过这个方法,我们自己的DLL就可以得到一个指向 COM对象的IUnknown的指针,实际上通过这个指针我们就可以通过COM对象中的方法QueryInterface来遍历所有可以得到的接口,这是COM的基本的机制。当然我们需要的只是IWebBrowser2这个接口。

—- 实际上我们建立的是一个COM对象,DLL只不过是COM对象的一种表现形式。我们建立的COM对象需要建立和实现的方法有:

—-1. IOleObjectWithSite接口的方法SetSite必须实现。实际上实例通过这个方法向我们的COM对象传递一个接口的指针。假设我们有一个接口指针的变量,不妨设为:

—-CComQIPtr< IWebBrowser2, &IID_IWebBrowser2 > m_myWebBrowser2;

—- 我们就可以在方法SetSite中把这个传进来的接口指针赋给m_myWebBrowser2。 2. 在我们得到了指向 COM对象的接口后,我们需要把自己的DLL和实例所发生的事件相关连,为了实现这个目的,需要介绍两个接口:

—-(1) IConnectionPointContainer。:

—-CComQIPtr< IWebBrowser2, &这里使用这个接口的目的是用来根据它得到的IID来建立和DLL的一个特定的连接。比如我们可以进行如下的定义:

CComQIPtr< IConnectionPointContainer,
&IID_IConnectionPointContainer >
spCPContainer(m_myWebBrowser2);

—-然后,我们需要把所有中发生的事件和我们的DLL进行通讯,可以使用 IConnectPoint。

—-(2) IConnectPoint。通过这个接口,客户可以对连接的对象开始或者是终止一个advisory循环。IConnectPoint有两个主要的方法,一个为Advice,另一个为Unadvise。对于我们的应用来说,Advise是用来在每一个发生的事件和DLL之间建立一个通道。而Unadvise就是用来终止以前用Advise建立的通知关系。比如我们可以定义IConnectPoint接口如下: CComPtr< IConnectionPoint > spConnectionPoint;

—- 然后,我们要使所有在实例中发生的事件和我们的DLL相关,可以使用 如下的方法:

hr = spCPContainer->FindConnectionPoint(
DIID_DWebBrowserEvents2, &spConnectionPoint);

—-然后我们通过IConnectPoint接口的方法Advice使每当有一个新的事件发生的时候,都能够让我们的DLL知道。可以用如下的语句实现:

hr = spConnectionPoint- >Advise(
(IDispatch*)this, &m_dwIDCode);

—-在把实例中的事件和我们的DLL之间建立联系以后,我们可以通过IDispatch接口的Invoke()方法来处理所有的的事件。

—-3. IDispatch接口的Invoke()方法。IDispatch是从IUnknown中继承的一个接口的类型,通过COM接口提供的任何服务都可以通过IDispatch接口来实现。IDispatch::Invoke的工作方式同vtbl幕后的工作方式是类似的,Invoke将实现一组按索引来访问的函数,我们可以对Invoke方法进行动态的定制以提供不同的服务。Invoke方法的表示如下:

STDMETHOD(Invoke)(DISPID dispidMember,REFIID
riid, LCID lcid, WORD wFlags,
DISPPARAMS * pdispparams, VARIANT * pvarResult,
EXCEPINFO * pexcepinfo, UINT * puArgErr);

—-其中,DISPID是一个长整数,它标识的是一个函数。对于IDispatch的某一个特定的实现,DISPID都是唯一的。IDispatch的每一个实现都有其自己的IID,这里dispidMemeber实际上是可以认为是和实例所发生的每一个事件相关的方法,比如:DISPID_BEFORENAVIGATE2,DISPID_NAVIGATECOMPLETE2等等。 这个方法中另外一个比较重要的参数是DISPPARAMS,它的结构如下:

typedef struct tagDISPPARAMS
{
VARIANTARG* rgvarg;
//VARIANTARG是同VARAIANT相同的,可以在
//OAIDL.IDL中找到。所以实际上rgvarg是一个参数数
//组
DISPID*  rgdispidNameArgs;  //命名参数的DISPID
unsigned int cArgs;    //表示数组中元素的个数
unsigned int CnameArgs;  //命名元素的个数
}DISPPARAMS

—-要注意的是每一个参数的类型都是VARIANTARG,所以在和我们DLL之间可以传递的参数类型的数目是有限的。只有那些能够被放到VARIANTARG结构中的类型才可以通过调度接口进行传递。 比如对于事件DISPID_NAVIGATECOMPLETE2来说:第一个参数表示在访问的URL的值,类型是VT_BYREF|VT_VARIANT。注意DISPID_NAVIGATECOMPLETE2等DISPID已经在VC中被定义,我们可以直接进行使用。 如上说述,我们在方法Invoke中可以得到所有实例所发生的事件,我们可以把这些数据放到文件中进行事后的分析,也可以放到一个列表框中实时的显示。

—- 4.微软的文档对象模型和应用分析

—- 下面我们来看如何得到网页文档的接口:网页文档的接口为IHTMLDocument2,可以通过调用 COM对象的get_Document方法来得到网页的接口。使用如下的语句:

hr = m_spWebBrowser2- >get_Document(&spDisp);
CComQIPtr< IHTMLDocument2,
&IID_IHTMLDocument2 > spHTML;
spHTML = spDisp;

—- 这样我们就得到了网页对象的接口,然后我们就可以对网页进行分析,比如通过IHTMLDocument2提供的方法get_URL我们可以得到和该网页相关的URL的地址值,通过get_forms方法可以该网页中所有的Form对象的集合。实际上W3C组织已经制定了一个DOM(Document Objec Model)标准,当然这个标准不仅仅是针对,同时还是针对XML制定的。W3C组织只是定义了网页对象的接口,不同的公司可以采用不同的语言和方法进行具体的实现。按照W3C组织定义的网页对象被认为是动态的,即用户可以动态的对网页对象里面所包含的每一个对象进行操作。这里的对象可以是指一个输入框,也可以是图象和声音等对象。同时按照W3C的正式文档的说明,网页对象是可以动态增加和删除的。事实上,很少有厂商实现了DOM定义的所有功能。微软对网页对象的定义也基本上是按照这个标准实现的。但是当前的接口还不支持动态的增加和删除元素,但是可以对网页中的基本元素进行属性的修改。比如IHTMLElementCollection表示网页中一些基本的元素的集合,IHTMLElement表示网页中的一个基本的元素。而象IHTMLOptionElement接口就表示一个特定的元素Option。基本的元素都有setAttribute和geAttribute方法来动态的设置和得到元素的名称和值。

—- 较为常见的一个应用是我们能够分析网页中是否有需要填写的Forms,如果这个网址的Forms以前已经填写过而且数据我们已经保存下来的话,我们就可以把数据自动放到和该URL下的Forms的相关的位置中去。另外,我们可以总结网页上需要填写的Form的数据项,先对这些数据项进行赋值,以后碰到有相同的数据项的时候就自动把我们赋值的内容填写进去。实际上Form是对象,Form中包含的元素,比如INPUT,OPTION,SELECT等类型的输入元素都是对象。

—- 另外一个可以想到的应用是自动对网页中的文本进行翻译,因为我们可以修改网页中任何对象的属性,所以我们可以把里面不属于本国语言的部分自动翻译成本国语言,当然真正的实现还要靠自然语言理解方面技术的突破,但是浏览器的接口和对象的形式使我们能够灵活的控制整个,无论是从事件对象还是到网页对象。

—- 5.小结

—- 上面我们分析了如何得到所有的实例,同时介绍了和实例相捆绑的DLL的详细的实现机制,同时对网页的对象化进行了分析。并且介绍了几个相关的应用和实现的方法及存在的技术问题。是一个组件化的以COM为基础的浏览器,它具有强大的功能,同时为应用开发者留下了广阔的空间,当然它也存在体积比较大,速度相对比较慢的缺点。但是它的体系结构代表了微软先进的创新的技术,因此具有强大的生命力。

ATL 实现定制的 IE 浏览器栏、工具栏和桌面工具栏

2009/03/29

ATL 实现定制的 IE 浏览器栏、工具栏和桌面工具栏

作者:杨老师

下载源代码

关键字:Band,Desk Band,Explorer Band,Tool Band,浏览器栏,工具栏,桌面工具栏

一、引言
最近,由于工作的要求,我需要在 IE 上做一些开发工作。于是在 MSDN 上翻阅了一些资料,根据 MSDN 上的说明我用 ATL 胜利完成了“资本家老板”分配的任务。
(并且在白天睡觉的过程中梦到了老板给我加工资啦……)
现在,我把 MSDN 上的原文资料,经过翻译整理并把一个 ATL 的实现奉贤给 VCKBASE 上的朋友们。

二、概念
在翻译的过程中,有两个词汇非常不好理解。第一个词是 Band 对象,词典中翻译为“镶边、裙子边、带子、乐队……”我的英文水平有限,实在不知道应该翻译为什么词汇更合适。于是我毅然决然地决定:在如下的论述中,依然使用 band 这个词!(什么?没听明白?我的意思就是说,我不翻译这个词了)但到底 Band 对象应该如何理解那?请看图一:


图一

图一中画红圈的地方,分别称作“垂直的浏览器栏”、“水平的浏览器栏”、“工具栏”和“桌面工具栏”。这些“栏”,都可以在 IE 的“查看”菜单中或鼠标右键的上下文快捷方式菜单中显示或隐藏起来。这些界面窗口的实现,其实就是实现一种 COM 接口对象,而这个对象叫 band。这个概念实在是只能意会而无法言传的,我总不能在文章中把它翻译为“总是靠在 IE 主窗口边上的对象”吧?^_^
另外,还有一个词叫 site。这个很好翻译,叫“站点”!。呵呵,我敢打包票,如果你要能理解这个翻译在计算机类文章中的含义,那就只能恭喜你了,你的智慧太高了。(都是学计算机软件的人,做人的差距咋就这么大呢?)在本篇文章中,site 可以这样理解:IE 的主框架四周,就好比是“汽车站”,那些 band 对象,就好比是“汽车”。band 汽车总是可以停靠在“汽车站”上。所以,site 就是“站点”,它也是 COM 接口的对象(IObjectWithSite、IInputObjectSite)。

三、原理

3.1 基本 band 对象
Band 对象,从 Shell 4.71(IE 5.0) 开始提供支持。Band 是一个 COM 对象,必须放在一个容器中去使用,当然使用它们就好象使用普通窗口是一样的。IE 就是一个容器,桌面 Shell 也是一个容器,它们提供不同的函数功能,但基本的实现是相似的。
Band 对象分三种类型,浏览器栏 band(Explorer bands)、工具栏 band(Tool Bands)和桌面工具栏(Desk bands),而浏览器栏 band 又有两种表现形式:垂直和水平的。那么 IE 和 Shell 如何区分并加载这些 bands 对象呢?方法是:你要对不同的 band 对象,在注册表中注册不同的组件类型(CATID)。

Band 样式

组件类型

CATID

垂直的浏览器栏 CATID_InfoBand 00021493-0000-0000-C000-000000000046
水平的浏览器栏 CATID_CommBand 00021494-0000-0000-C000-000000000046
桌面的工具栏 CATID_DeskBand 00021492-0000-0000-C000-000000000046

IE 工具栏不使用组件类型注册,而是使用在注册进行 CLSID 的登记方式。详细情况见 3.3。
在例子程序中,实现了全部四个类型的 band 对象,垂直浏览器栏(CVerticalBar)显示了一个 HTML 文件,并且实现了对 IE 主窗口浏览网页的导航等功能;水平的浏览器栏(CHorizontalBar)是一个编辑窗,它同步显示当前网页的 BODY 源文件内容;IE 工具栏(CToolBar)最简单,只是添加了一个空的工具栏;桌面工具栏(CDeskBar)实现了一个单行编辑窗口,你可以在上面输入命令行或文件名称,回车后它会执行 Shell 的打开动作。

3.2 必须实现的 COM 接口
Band 对象是 IE 或 Shell 的进程内服务器,所以它被包装在 DLL 中。而作为 COM 对象,它必须要实现 IUnknown 和 IClassFactory 接口。(大家可以不同操心,因为我们用 ATL 写程序,这两个接口是不用我们自己写代码的。)另外,Band 对象还必须实现 IDeskBand、IObjectWithSite 和 IPersistStream 三个接口:
IPersistStream 是持续性接口的一种。当 IE 加载 band 对象的时候,它通过这个接口的 Load 方法传递属性值给对象,让其进行初始化;而当卸载前,IE 则调用这个接口的 Save 方法保存对象的属性。用 ATL 实现这个接口很简单:

class ATL_NO_VTABLE Cxxx : 
	......
	public IPersistStreamInitImpl, // 添加继承
	......
{
public:
	BOOL m_bRequiresSave; // IPersistStreamInitImpl 所必须的变量
......
BEGIN_COM_MAP(CVerticalBar)
	......
	COM_INTERFACE_ENTRY2(IPersist, IPersistStreamInit)
	COM_INTERFACE_ENTRY2(IPersistStream, IPersistStreamInit)
	COM_INTERFACE_ENTRY(IPersistStreamInit)
	......
END_COM_MAP()

BEGIN_PROP_MAP(Cxxx)
...... // 添加需要持续性的属性
END_PROP_MAP()

上面的代码,其实实现的是 IPersistStreamInit 接口,不过没有关系,因为 IPersistStreamInit 派生自 IPersistStream,实例化了派生类,自然就实例化了基类。在例子程序中,我只在桌面工具栏对象中添加了持续性属性,用来保存和初始化“命令行”。另外 COM_INTERFACE_ENTRY2(A,B)表示的含义是:如果想查询A接口的指针,则提供B接口指针来代替。为什么可以这样那?因为B接口派生自A接口,那么B接口的前几个函数必然就是A接口的函数了,自然B接口的地址其实和A接口的地址是一样的了。
IObjectWithSite 是 IE 用来对插件进行管理和通讯用的一个接口。必须要实现这个接口的2个函数:SetSite() 和 GetSite()。当 IE 加载 band 对象和释放 band 对象的时候,都要调用 SetSite()函数,那么在这个函数里正好是写初始化和释放操作代码的地方:

STDMETHODIMP Cxxx::SetSite(IUnknown *pUnkSite)
{
	if( NULL == pUnkSite )	// 释放 band 的时候
	{
		// 如果加载的时候,保存了一些接口
		// 那么现在:释放它
	}
	else	// 加载 band 的时候
	{
		m_hwndParent = NULL;	// 装载 band 的父窗口(就是带有标题的那个框架窗口)

		// 这个窗口的句柄,是调用 IUnknown::QueryInterface() 得到 IOleWindow
		// 然后调用 IOleWindow::GetWindow() 而获得的。
		CComQIPtr< IOleWindow, &IID_IOleWindow > spOleWindow(pUnkSite);
		if( spOleWindow )	spOleWindow->GetWindow(&m_hwndParent);
		if( !m_hwndParent )	return E_FAIL;

		// 现在,正好是建立子窗口的时机。
		// 注意,子窗口建立的时候,不要使用 WS_VISIBLE 属性
		... ...
		// 在例子程序中,用 CAxWindow 实现了一个能包容ActiveX的容器窗口(垂直浏览器栏)
		// 在例子程序中,用 WIN API 函数 CreateWindow 实现了标准窗口(水平浏览器栏、工具栏)
		// 在例子程序中,用 CWindowImpl 实现了一个包容窗口(桌面工具栏)

		/*********************************************************/
		   以下部分,根据 band 对象特有的功能,是可以选择实现的
		**********************************************************/		
		// 如果子窗口实现了用户输入,那么必须实现 IInputObject 接口,
		// 而该接口是被 IE 的 IInputObjectSite 调用的,因此在你的对象
		// 中,应该保存 IInputObjectSite 的接口指针。
		// 在类的头文件中,定义:
		// CComQIPtr< IInputObjectSite, &IID_IInputObjectSite > m_spSite;

		m_spSite = pUnkSite;	// 保存 IInputObjectSite 指针
		if( !m_spSite )		return E_FAIL;

		// 你需要控制 IE 的主框架吗?
		// 那么在类的头文件中,定义:
		// CComQIPtr< IWebBrowser2, &IID_IWebBrowser2 > m_spFrameWB;
		// 然后,先取得 IServiceProvider,再取得 IWebBrowser2

		CComQIPtr < IServiceProvider, &IID_IServiceProvider> spSP(pUnkSite);
		if( !spSP )	return E_FAIL;
		spSP->QueryService( SID_SWebBrowserApp, &m_spFrameWB );
		if( !m_spFrameWB)	return E_FAIL;

		// 如果你取得了 IE 主框架的 IWebBrowser2 指针
		// 那么,当它发生了什么事情,你难道不想知道吗?
		// 定义:CComPtr m_spCP;

		CComQIPtr< IConnectionPointContainer,
			&IID_IConnectionPointContainer> spCPC( m_spFrameWB );
		if( spCPC )
		{
			spCPC->FindConnectionPoint( DIID_DWebBrowserEvents2, &m_spCP );
			if( m_spCP )
			{
				m_spCP->Advise( reinterpret_cast< IDispatch * >( this ), &m_dwCookie );
			}
		}

		// 咳~~~ 不说了,看源码去吧。这里能干的事情太多了... ...
	}
	return S_OK;
}

IDeskBand 是一个特殊的 band 对象接口,有一个方法函数:GetBarInfo();
IDockingWindow 是 IDeskBank 的基类,有3个方法函数:ShowDW()、CloseDW()、ResizeBorderDW();
IOleWindow 又是 IDockingWindow 的基类,有2个方法函数:GetWindow()、ContextSensitiveHelp();

首先声明 IDeskBand ,然后要实现 IDeskBand 接口的共6个函数,这些函数比较简单,不同类型的 band 对象,其实现方法也都基本一致:

class ATL_NO_VTABLE Cxxx : 
	......
	public IDeskBand,
	......
{
......
BEGIN_COM_MAP(Cxxx)
	......
	COM_INTERFACE_ENTRY_IID(IID_IDeskBand, IDeskBand)
	......
END_COM_MAP()

// IOleWindow
STDMETHODIMP Cxxx::GetWindow(HWND * phwnd)
{	// 取得 band 对象的窗口句柄
	// m_hWnd 是建立窗口时候保存的
	*phwnd = m_hWnd;	
	return S_OK;
}

STDMETHODIMP Cxxx::ContextSensitiveHelp(BOOL fEnterMode)
{	// 上下文帮助,参考 IContextMenu 接口
	return E_NOTIMPL;
}

// IDockingWindow
STDMETHODIMP CVerticalBar::ShowDW(BOOL bShow)
{	// 显示或隐藏 band 窗口
	if( m_hWnd )
		::ShowWindow( m_hWnd, bShow ? SW_SHOW : SW_HIDE);

	return S_OK;
}

STDMETHODIMP CVerticalBar::CloseDW(DWORD dwReserved)
{	// 销毁 band 窗口
	if( ::IsWindow( m_hWnd ) )
		::DestroyWindow( m_hWnd );

	m_hWnd = NULL;

    return S_OK;
}

STDMETHODIMP CVerticalBar::ResizeBorderDW(LPCRECT prcBorder, IUnknown* punkToolbarSite, BOOL fReserved)
{	// 当框架窗口的边框大小改变时
	return E_NOTIMPL;
}

// IDeskBand
STDMETHODIMP CVerticalBar::GetBandInfo(DWORD dwBandID, DWORD dwViewMode,  DESKBANDINFO* pdbi)
{	
         // 取得 band 的基本信息,你需要填写 pdbi 参数作为返回
	if( NULL == pdbi )		return E_INVALIDARG;

	// 如果将来需要调用 IOleCommandTarget::Exec() 则需要保存这2个参数
	m_dwBandID = dwBandID;
	m_dwViewMode = dwViewMode;

	if(pdbi->dwMask & DBIM_MINSIZE)
	{	// 最小尺寸
		pdbi->ptMinSize.x = 10;
		pdbi->ptMinSize.y = 10;
	}

	if(pdbi->dwMask & DBIM_MAXSIZE)
	{	// 最大尺寸 (-1 表示 4G)
		pdbi->ptMaxSize.x = -1;
		pdbi->ptMaxSize.y = -1;
	}

	if(pdbi->dwMask & DBIM_INTEGRAL)
	{
		pdbi->ptIntegral.x = 1;
		pdbi->ptIntegral.y = 1;
	}

	if(pdbi->dwMask & DBIM_ACTUAL)
	{
		pdbi->ptActual.x = 0;
		pdbi->ptActual.y = 0;
	}

	if(pdbi->dwMask & DBIM_TITLE)
	{	// 窗口标题
		wcscpy(pdbi->wszTitle,L"窗口标题");
	}

	if(pdbi->dwMask & DBIM_MODEFLAGS)
	{
		pdbi->dwModeFlags = DBIMF_VARIABLEHEIGHT;
	}

	if(pdbi->dwMask & DBIM_BKCOLOR)
	{	// 如果使用默认的背景色,则移除该标志
		pdbi->dwMask &= ~DBIM_BKCOLOR;
	}

	return S_OK;
}

3.3 选择实现的 COM 接口
有两个接口不是必须实现的,但也许很有用:IInputObject 和 IContextMenu。如果 band 对象需要接收用户的输入,那么必须实现 IInputObject 接口。IE 实现了 IInputObjectSite 接口,当容器中有多个输入窗口时,它调用 IInputObject 接口方法去负责管理用户的输入焦点。
在浏览器栏中需要实现3个函数:UIActivateIO()、HasFocusIO()、TranslateAcceleratorIO()。
当浏览器栏激活或失去活性的时候,IE 调用 UIActivateIO 函数,当激活的时候,浏览器栏一般调用 SetFocus 去设置它自己窗口的焦点。当 IE 需要判断哪个窗口有焦点的时候,它调用 HasFocusIO 。当浏览器栏的窗口或其子窗口有输入焦点时,则应返回 S_OK,否则返回 S_FALSE。TranslateAcceleratorIO 允许对象处理加速键,例子程序中没有实现,所以直接返回 S_FALSE。

STDMETHODIMP CExplorerBar::UIActivateIO(BOOL fActivate, LPMSG pMsg)
{
    if(fActivate)
        SetFocus(m_hWnd);

    return S_OK;
}

STDMETHODIMP CExplorerBar::HasFocusIO(void)
{
    if(m_bFocus)
        return S_OK;

    return S_FALSE;
}

STDMETHODIMP CExplorerBar::TranslateAcceleratorIO(LPMSG pMsg)
{
    return S_FALSE;
}

Band 对象能够通过包容器的 IOleCommandTarget::Exec() 调用执行命令。而 IOleCommandTarget 接口指针,则可以通过调用包容器的 IInputOjbectSite::QueryInterface(IID_IOleCommandTarget,…) 函数得到。CGID_DeskBand 是命令组,当一个 band 对象的 GetBandInfo 被调用的时候,包容器通过 dwBandID 参数指定一个 ID 给 band 对象,对象要保存住这个ID,以便调用 IOleCommandTarget::Exec()的时候使用。ID 的命令有:

  • DBID_BANDINFOCHANGED
    Band 的信息变化。设置参数 pvaIn 为 band ID, 该 ID 就是最近一次调用 GetBandInfo 所得到的值,容器会调用 band 对象的 GetBandInfo 函数来更新请求信息。
  • DBID_MAXIMIZEBAND
    最大化 band。设置参数 pvaIn 为 band ID,该 ID 就是最近一次调用 ?GetBandInfo ?所得到的值。
  • DBID_SHOWONLY
    打开或关闭容器中其它的 bands。 设置参数 pvaIn 为VT_UNKNOWN 类型,它可以是如下的值:

    描述
    pUnk band 对象的 IUnknown 指针,其它的桌面 bands 将被隐藏
    0 隐藏所有的桌面 bands
    1 显示所有的桌面 bands
  • DBID_PUSHCHEVRON
    在菜单项左边显示“v”的选择标志。容器发送一个 RB_PUSHCHEVRON 消息,当 band 对象接收到通知消息 RBN_CHEVRONPUSHED 提示它显示一个”v”的标志。设置 IOleCommandTarget::Exec 函数中 nCmdExecOpt 参数为 band ID,该 ID 是最近一次调用 GetBandInfo ?所得到的值,设置 IOleCommandTarget::Exec 函数中 pvaIn 参数为 VT_I4 类型,这是应用程序定义的一个值,它通过通知消息 RBN_CHEVRONPUSHED 中lAppValue 回传给 band 对象。

3.4 Band 对象注册
Band 对象必须注册为一个 OLE 进程内的服务器,并且支持 apartment 线程公寓。注册表中默认键的值是表示菜单的文字。对于浏览器栏,它加到 IE 菜单的“查看\浏览器栏”中;对于工具栏 band ,它加到 IE 菜单的“查看\工具栏”中;对于桌面 band, 它加到系统任务栏的快捷菜单中。在菜单资源中,可以使用“&”指明加速键。

通常,一个基本的 band 对象的注册表项目是:

HKEY_CLASSES_ROOT
CLSID
{你的 band 对象的 CLSID}

(Default) = 菜单的文字
InProcServer32
(Default) = DLL 的全路径文件名
ThreadingModel= Apartment

工具栏 bands 还必须把它们的 CLSID 注册到 IE 的注册表中。

HKEY_LOCAL_MACHINE\Software\Microsoft\Internet Explorer\Toolbar 下给出 CLSID 作为键名,而其键值是被忽略的。

HKEY_LOCAL_MACHINE
Software
Microsoft
Internet Explorer
Toolbar

{你的 band 对象的 CLSID}

还有几个可选的注册表项目(例子程序并不是这样实现的)。比如,你想让浏览器栏显示 HTML 的话,必须要如下设置注册表:

HKEY_CLASSES_ROOT
CLSID
{你的 Band 对象的 CLSID}
Instance
CLSID
(Default) = {4D5C8C2A-D075-11D0-B416-00C04FB90376}

同时,如果要指定一个本地的 HTML 文件,那么要如下设置:

HKEY_CLASSES_ROOT
CLSID
{你的 Band 对象的 CLSID}
Instance
InitPropertyBag
Url

另外,还可以指定浏览器栏的宽和高,当然,它是依赖于这个栏是纵向还是横向的。其实这个项目无所谓,因为当用户调整了浏览器栏的大小后,会自动保存在注册表中的。

HKEY_CURRENT_USER
Software
Microsoft
Internet Explorer
Explorer Bars
{你的 Band 对象的 CLSID}
BarSize

BarSize 键的类型必须是 REG_BINARY 类型,它有8个字节。左起前4个字节,是用16进制表示的像素宽度或高度,后4个字节保留,你应该设置为0。下面是一个可以在浏览器栏上显示 HTML 文件的全部注册表项目的例子,默认宽度为291(0×123)个像素点:

HKEY_CLASSES_ROOT
CLSID
{你的 Band 对象的 CLSID}

(Default) = 菜单文字
InProcServer32
(Default) = DLL 的全路径文件名
ThreadingModel= Apartment
Instance
CLSID

(Default) = {4D5C8C2A-D075-11D0-B416-00C04FB90376}
InitPropertyBag
Url= 你的 HTML 文件名

HKEY_CURRENT_USER
Software
Microsoft
Internet Explorer
Explorer Bars
{你的 Band 对象的 CLSID}

BarSize= 23 01 00 00 00 00 00 00

对于注册表的设置,用 ATL 实现其实是异常简单的。打开工程的 xxx.rgs 文件,并手工编辑一下就可以了。 下面这个文件源码,是例子程序中 IE 工具栏的注册表样式,HKLM 是需要手工添加的,因为它不使用组件类型方式注册。而对于其它类型的 band 对象只要在类声明中添加:

BEGIN_CATEGORY_MAP(Cxxx)			// 向注册表中注册 COM 类型
	IMPLEMENTED_CATEGORY(CATID_InfoBand)	// 垂直样式的浏览器栏
END_CATEGORY_MAP()

IE 工具栏类型 band 对象的“.rgs”文件

HKCR	// 这个项目是 ATL 帮你生成的,你只要手工修改“菜单上的文字”就可以了
{
	Bands.ToolBar.1 = s ''ToolBar Class''
	{
		CLSID = s ''{ 你的 CLSID }''
	}
	Bands.ToolBar = s ''ToolBar Class''
	{
		CLSID = s ''{ 你的 CLSID }''
		CurVer = s ''Bands.ToolBar.1''
	}
	NoRemove CLSID
	{
		ForceRemove { 你的 CLSID } = s ''用在菜单上的文字(&T)''
		{
			ProgID = s ''Bands.ToolBar.1''
			VersionIndependentProgID = s ''Bands.ToolBar''
			ForceRemove ''Programmable''
			InprocServer32 = s ''%MODULE%''
			{
				val ThreadingModel = s ''Apartment''
			}
			''TypeLib'' = s ''{xxxx-xxxx-xxxxxxxxxxxxxxx}''
		}
	}
}

HKLM	// 这个项目是手工添加的IE工具栏所特有的
{
	Software
	{
		Microsoft
		{
			''Internet Explorer''
			{
				NoRemove Toolbar
				{
					ForceRemove val { 你的 CLSID } = s ''随便给个说明性文字串''
				}
			}
		}
	}
}

四、 ATL 实现
下载代码后(VC 6.0 工程),请参照前面的说明仔细阅读,代码中也有一些关键点的注释。如果想运行,则可以用 regsvr32.exe 进行注册,然后打开 IE 浏览器或资源浏览器就可以看到效果了。如果想自己实践一下,可以按照如下的步骤构造工程:

4.1 建立一个 ATL DLL 工程
4.2 添加 New ATL Object…,选择 Internet Explorer Object,选这个类型的目的是让向导给我们添加 IObjectWithSite 的支持。如果你使用的是 .net 环境,则不要忘记选择支持这个接口。

4.3 输入对象名称,比如我想建立一个垂直的浏览器栏,不妨叫它 VerBar

4.4 线程模型必须选择 Apartment,接口类型的选择无所谓,看你想不想支持 IDispatch 接口功能了。在例子程序中的垂直浏览器栏中,由于想更简单的操纵 IE 和从 IE 中接受事件(连接点),选择 Dual 是必要的。聚合选项,你只要别选择 Only 就可以了。

4.5 展现你无穷的智慧,开始输入程序吧。如果是 Debug 方式编译,可能会出现一个连接错误,报告找不到_AtlAxCreateControl,那么你要在菜单 Project\Settings…\Link 中增加对 Atl.lib 的连接。或者使用 #pragma comment ( lib, “atl” )加入连接库。
4.6 如果想调试代码,在菜单 Project\Settings…\Debug 中输入 IE 的路径名称,比如:“C:\Program Files\Internet Explorer\IEXPLORE.EXE”,然后就可以跟踪断点调试了。 编译和调试桌面工具栏的 band 对象,是非常麻烦的,因为计算机启动时自动运行 Shell,而 Shell 就会加载活动的桌面对象。

五、结束语
好了,到这里,就到这里了。祝大家学习快乐^_^

IE插件开发

2009/03/29

Windows区对象(Bands)的创建与定制
编译/赵湘宁

本文例子代码

1 简介
1.1 浏览栏区对象
1.2 工具栏区对象

1.3 桌面区对象

2 实现区对象
2.1 注册

3 一个简单的例子
3.1 DLL函数
3.2 注册定制的浏览栏
3.3 必须实现的接口
3.3.1 IUnknown
3.3.2 IObjectWithSite
3.3.3 IPersistStream
3.3.4 IDeskBand
3.4 可选择的接口实现
3.4.1 IInputObject
3.5 窗口过程
4 总结

一、 简介
Windows的区(Bands)对象有三种:既浏览栏(Explorer Bar)区对象,工具栏(Tools Bands)区对象,和桌面区对象(Desk Bands)。

浏览栏区对象
浏览栏区对象简称浏览栏,它是从IE4.0引入的,它是邻近浏览器窗格的一个显示区域。实际上它是IE窗口中的一个子窗口,可以用它来显示信息及与用户交互。浏览栏即可以是以垂直方式定位在浏览器窗格的左边。也可以水平方式定位在浏览器窗格下面。(如图一)

图一
在浏览栏中可以创建很多子菜单或选项,用户能以不同方式选择这些子菜单或选项提供的功能,打开IE或者资源管理器,从“查看”菜单中选择“浏览栏”,可以看到Windows提供了几种标准的浏览栏菜单,如“搜索(Search)”,“收藏夹(Favorites)”, 和“历史记录(History)”,以及“文件夹(All Folders)”。(如图二)

图二
为了创建定制的浏览栏,必须编程实现,然后注册它们。Windows在外壳(Shell)4.71中引入了区对象。它提供与普通窗口一样的功能。但因为它是以IE或外壳为容器的COM对象,所以实现起来就与普通窗口有所不同。图一中显示的就是一个简单的浏览栏例子。图中有一个垂直的浏览栏和一个水平的浏览栏。

工具栏区对象
工具栏区对象简称工具栏,它是在IE5.0中引入用以支持单选工具栏(radio toolbar)特性的。IE工具栏实际上是一个Rebar控件,它包含了几个工具栏(toolbar)控件。通过创建工具栏,你可以将某个区对象功能添加到Rebar控件中。不论是在IE中还是在资源管理器中,区对象都是一样的,所以工具栏也是一个通用窗口。(如图三)

图三

用户可以从“查看”菜单中的“工具栏”子菜单中选择显示单选工具栏,也可以在工具栏区域单击鼠标右键从它的上下文菜单中选择显示单选工具栏。

桌面区对象
区对象也可以用在桌面,也就是创建桌面区对象。虽然它们的基本实现与浏览栏类似,但桌面区与IE没有关系,它不用IE作为容器。它主要用来创建桌面浮动窗口。通过在任务栏上单击右键,然后在弹出的菜单中选择“工具栏”的子菜单选项。(如图四)

图四

桌面区的初始浮动位置在任务栏:(如图五

图五

用户可以将桌面区拖到桌面上,这时它就成了一个普通窗口:(如图六)

图六

二、实现区对象
尽管可以像使用普通窗口一样使用区对象,但它们毕竟是COM对象,存在于某个容器之中。如浏览栏和工具栏位于IE之中,桌面区位于外壳之中。虽然它们的功能不同,但其基本实现非常相似。一个主要的差别是它们的注册方式不同,而注册方式的不同又决定了对象的类型及其容器。这一部分我们先讨论所有区对象实现的共性。其它的实现细节可参考垂直浏览栏例子程序
区对象除了要实现 IUnknown 和 IClassFactory 两个接口之外,所有的区对象还必须实现以下这几个接口:

  • IDeskBand
  • IObjectWithSite
  • IPersistStream

另外,在注册时除了注册它们的CLSID之外,浏览栏和桌面区对象还必须进行组件类别(category)的注册。它决定了对象的类型及其容器。工具栏不需要进行种类注册。归纳起来,需要进行CATID注册的三种区对象是:

区对象类型 组件类型
垂直浏览栏 CATID_InfoBand
水平浏览栏 CATID_CommBand
桌面区 CATID_DeskBand

对于如何注册区对象的进一步讨论请参见注册部分
如果某个区对象接受用户输入,它还必须实现IInputObject接口。如果要往上下文菜单中添加菜单项目,还必须实现IContextMenu接口。注意:工具栏区对象不支持上下文菜单。
因为区对象实现的是子窗口,所以它们还必须有窗口过程来处理Windows的消息。
区对象可以通过其IOleCommandTarget接口发送命令到它的容器。为了得到这个接口的指针,必须调用容器的IInputObjectSite::QueryInterface方法来请求IID_IoleCommandTarget。然后用IOleCommandTarget::Exec把命令发送到容器。命令组是CGID_DeskBand。当某个区对象的IDeskBand::GetBandInfo方法被调用时,容器用dwBandID参数将一个标示符赋给这个对象。这个标示符被用于IOleCommandTarget::Exec方法调用时所用命令组中的三个命令。目前命令组共支持四个IOleCommandTarget::Exec命令IDs。这四个命令的解释如下:
DBID_BANDINFOCHANGED——Band的信息已改变。参数pvaIn的值应该是最近一次调用所用的band标示符。容器将调用这个标示符所指的band对象的IDeskBand::GetBandInfo方法请求更新的信息。
DBID_MAXIMIZEBAND——容器将最大化band。参数pvaIn的值应该是最近一次调用所用的band标示符。
DBID_SHOWONLY——关闭或打开容器中其它band。参数pvaIn的值为VT_UNKNOWN类型,可以取下列值之一:

描述
pUnk 这个对象IUnknown接口的指针。所有其它的桌面band将被隐藏。
0 隐藏所有桌面band。
1 显示所有桌面band。

DBID_PUSHCHEVRON——目前没有实现。

注册
区对象必须作为进程内服务器(in-process)注册。其线程模型必须为“Apartment”。也就是说区对象必须以DLL的形式来实现。用来描述服务器注册条目的缺省值是一个菜单文本串。就拿浏览栏来说。这个菜单出现在资源管理器或IE “查看(View)”菜单的“浏览栏(Explorer Bar)”子菜单中。而工具栏的菜单则出现在资源管理器或IE “查看(View)”菜单的“工具栏(Toolbars)”子菜单中。桌面区出现在任务栏上下文菜单的“工具栏(Toolbars)”子菜单中。作为菜单资源,提供键盘快捷的方法与一般菜单快捷键相同。也就是将“&”字符放在某个单词字母前表示这个字母显示下划线来指示快捷键。
通常区对象的注册条目如下:

HKEY_CLASSES_ROOT
...
    CLSID
	...
        {Band 对象的 CLSID GUID} = "菜单文本串"
            InProcServer32 = "DLL 路径名"
                ThreadingModel = "Apartment"

工具栏区对象必须还要注册对象的CLSID。为此必须在HKEY_LOCAL_MACHINE\Software\Microsoft\Internet Explorer\Toolbar下创建一个REG_SZ值,用工具栏区对象的CLSID GUID串命名。如:

HKEY_LOCAL_MACHINE
    Software
        Microsoft
            Internet Explorer
                Toolbar
                    { Band 对象的 CLSID GUID }

除此之外,还有几个可选的注册值可以加到注册表中,本文的例子中未使用这些值。

  • HKEY_CLASSES_ROOT\CLSID\{Band 对象的 CLSID GUID}\Instance\CLSID, 它应该被设置为 “{4D5C8C2A-D075-11D0-B416-00C04FB90376}”.
  • HKEY_CLASSES_ROOT\CLSID\{Band对象的CLSID GUID}\Instance\InitPropertyBag\Url 它应该被设置为要在浏览栏显示的包含HTML内容的文件位置。
  • \HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\Explorer Bars\{Band 对象的 CLSID GUID}\BarSize 它应该被设置为栏目的高和宽,它需要八个字节才能作为串放入注册表,字节之间用逗号分开。开始的四个字节一像素为单位指定大小,格式要用十六进制,从最左边字节开始。最后四个字节是保留字节,应该将它置为零。例如,垂直浏览栏的缺省宽度为291(0×123)像素,则BarSize 的值应该是”23,01,00,00,00,00,00,00″

如果要用浏览栏显示HTML,则前两个注册项是必须的。最后一个注册项则根据垂直的或者水平的浏览栏定义相应的缺省宽度和高度。
能显示HTML的浏览栏(缺省宽度为291各像素单位)注册表条目的形式如下:

HKEY_CLASSES_ROOT
...
    CLSID
	...
        {Band 对象的 CLSID GUID} = "菜单文本串"
            InProcServer32 = "DLL 路径名"
                ThreadingModel = "Apartment"
            Instance
                CLSID = "{4D5C8C2A-D075-11D0-B416-00C04FB90376}"
                InitPropertyBag
                    Url = "HTML文件"
...
HKEY_CURRENT_USER
...
    Software
    ...
        Microsoft
        ...
            Internet Explorer
            ...
                Explorer Bars
                    { Band 对象的 CLSID GUID }
                        BarSize = "23,01,00,00,00,00,00,00"

你可以通过编程的方式来处理区对象类别 CATID 的注册。创建一个组件类别管理器对象(CLSID_StdComponentCategoriesMgr)并请求一个指向ICatRegister接口的指针。将区对象的CLSID和CATID传递到ICatRegister::RegisterClassImplCategories。
三、定制浏览栏的一个简单例子
这个例子展示了前面所介绍过的垂直浏览栏的整个实现过程。它借助了平台SDK(Platform SDK——在msdn中可以找到)中关于band对象示范代码。其中还包括了水平浏览栏和桌面band的实现代码。详细实现细节请参见:CommBand.cpp和DeskBand.cpp。
创建定制浏览栏的基本过程是这样的:

  1. 实现DLL需要的函数。
  2. 实现必须的COM接口。
  3. 实现任何想要的可选接口。
  4. 注册对象的CLSID。
  5. 进行恰当的组件种类注册。
  6. 创建IE子窗口,调整窗口大小适合浏览栏的显示区域。
  7. 使用子窗口显示信息并与用户交互。

实际上,只要通过恰当的组件种类注册,浏览栏例子代码便既可用于浏览栏的实现,也能用于桌面band实现。更加复杂的实现将需要定制每种对象类型的显示区域和容器。但大多数的定制工作都能通过范例代码以及Windows子窗口的编程技术来完成。例如,你可以添加用户交互控制或者进行色彩丰富的图形显示处理。

DLL函数
所有三种区对象被打包在一个DLL中,它输出以下的函数:

  • DllMain
  • DllCanUnloadNow
  • DllGetClassObject
  • DllRegisterServer

这些函数可以在BandObjs.cpp中找到,它们服务于所有三种区对象。前三个函数乃标准的实现,我们不再本文中讨论。类工厂也是标准实现,代码可以在ClsFact.cpp中找到

注册定制的浏览栏

有了COM对象后,必须对浏览栏的CLSID进行注册。另外如果要与IE或资源管理器
协调运行,还必须进行的恰当的组件种类(CATID_InfoBand)注册。这个工作由DllRegisterServer处理。浏览栏例子代码有关的处理部分如下:

...
//注册浏览栏对象
if(!RegisterServer(CLSID_SampleExplorerBar, TEXT("垂直浏览栏例子")))
return SELFREG_E_CLASS;

//注册浏览栏的对象组件种类
if(!RegisterComCat(CLSID_SampleExplorerBar, CATID_InfoBand))
return SELFREG_E_CLASS;
...

区对象的注册使用通常的COM过程,它由私有函数RegisterServer处理。
除了CLSID之外,这个区对象服务器还必须注册一个以上的组件种类。这实际上是垂直浏览栏和水平浏览栏实现之间的主要差别。这个过程的处理是通过创建一个组件种类管理器对象(CLSID_StdComponentCategoriesMgr),并用ICatRegister::RegisterClassImplCategories方法来注册区对象服务器。在这个例子中,组件种类注册的处理是通过将浏览栏的CLSID和CATID传递到私有函数RegisterComCat完成的:

BOOL RegisterComCat(CLSID clsid, CATID CatID)
{
ICatRegister   *pcr;
HRESULT        hr = S_OK ;

CoInitialize(NULL);

hr = CoCreateInstance(  CLSID_StdComponentCategoriesMgr, 
                        NULL, 
                        CLSCTX_INPROC_SERVER, 
                        IID_ICatRegister, 
                        (LPVOID*)&pcr);
if(SUCCEEDED(hr))
   {
   hr = pcr->RegisterClassImplCategories(clsid, 1, &CatID);
   pcr->Release();
   }

CoUninitialize();

return SUCCEEDED(hr);
}
必须实现的接口
垂直浏览栏例子实现了四个必须的接口:IUnknown, IObjectWithSite, IPersistStream, 和IDeskBand,它们都在CExplorerBar类中实现。
IUnknown
构造函数,析构函数和IUnknown实现比较简单,本文在此不讨论。细节请参见源代码。
IObjectWithSite接口
当用户选择某个浏览栏时,容器调用相应band对象的IObjectWithSite::SetSite方法。参数将被设置成这个现场(Site)的IUnknown指针。
通常,SetSite实现应该完成下列步骤:

  1. 释放当前所把持的任何现场指针。
  2. 如果传递到SetSite的指针被置为NULL,此则区对象被删除。SetSite可以返回S_OK。
  3. 如果传递到SetSite的指针被置为非NULL,则建立新的现场。SetSite应该做以下的事情:
  1. 调用现场QueryInterface方法请求IOleWindow接口。
  2. 调用IOleWindow::GetWindow获取父窗口句柄,并存储它,以便以后使用。如果不再使用的话,就释放IOleWindow接口。
  3. 创建此band对象的窗口为一个子窗口,其父窗口就是上一步获得的那个窗口。注意在此不能将它创建成可见窗口。
  4. 如果此band对象实现IInputObject,调用现场QueryInterface方法请求IInputObjectSite接口,存储这个接口的指针以备后用。
  5. 如果所有步骤都成功,则返回S_OK,否则返回OLE定义的错误代码以指示错误类型。

以下是浏览栏实现SetSite的方法。m_pSite是私有成员变量,用它来保存IInputObjectSite指针,而m_hwndParent保存父窗口句柄。

STDMETHODIMP CExplorerBar::SetSite(IUnknown* punkSite)
{
//如果某个现场被把持,则释放它
if(m_pSite)
   {
   m_pSite->Release();
   m_pSite = NULL;
   }

//如果punkSite 不为NULL, 建立一个新的现场
if(punkSite)
   {
   //获取父窗口
   IOleWindow  *pOleWindow;

   m_hwndParent = NULL;

   if(SUCCEEDED(punkSite->QueryInterface(IID_IOleWindow, (LPVOID*)&pOleWindow)))
      {
      pOleWindow->GetWindow(&m_hwndParent);
      pOleWindow->Release();
      }

   if(!m_hwndParent)
      return E_FAIL;

   if(!RegisterAndCreateWindow())
      return E_FAIL;

   //获取柄保存IInputObjectSite指针
   if(SUCCEEDED(punkSite->QueryInterface(IID_IInputObjectSite, (LPVOID*)&m_pSite)))
      {
      return S_OK;
      }  
   return E_FAIL;
   }
return S_OK;
}

这个例子的GetSite只简单地用SetSite保存的现场指针实现了对现场QueryInterface方法的调用。

STDMETHODIMP CExplorerBar::GetSite(REFIID riid, LPVOID *ppvReturn)
{
*ppvReturn = NULL;

if(m_pSite)
   return m_pSite->QueryInterface(riid, ppvReturn);

return E_FAIL;
}

窗口创建由私有方法RegisterAndCreateWindow负责。如果这个窗口不存在,此方法将浏览栏窗口创建成一个大小适当的子窗口,它的父窗口就是由SetSite获得的那个窗口。子窗口的句柄存储在m_hwnd变量中。

BOOL CExplorerBar::RegisterAndCreateWindow(void)
{
//如果这个窗口不存在,则创建它
if(!m_hWnd)
   {
   //子窗口不能没有父窗口
   if(!m_hwndParent)
      {
      return FALSE;
      }

   //如果窗口类没有注册,则必须注册
   WNDCLASS wc;
   if(!GetClassInfo(g_hInst, EB_CLASS_NAME, &wc))
      {
      ZeroMemory(&wc, sizeof(wc));
      wc.style          = CS_HREDRAW | CS_VREDRAW | CS_GLOBALCLASS;
      wc.lpfnWndProc    = (WNDPROC)WndProc;
      wc.cbClsExtra     = 0;
      wc.cbWndExtra     = 0;
      wc.hInstance      = g_hInst;
      wc.hIcon          = NULL;
      wc.hCursor        = LoadCursor(NULL, IDC_ARROW);
      wc.hbrBackground  = (HBRUSH)CreateSolidBrush(RGB(0, 0, 192));
      wc.lpszMenuName   = NULL;
      wc.lpszClassName  = EB_CLASS_NAME;

      if(!RegisterClass(&wc))
         {
         //如果注册失败,下面的CreateWindow函数将失败
         }
      }

   RECT  rc;

   GetClientRect(m_hwndParent, &rc);

   //创建这个窗口。WndProc 将建立m_hWnd变量
   CreateWindowEx(   0,
                     EB_CLASS_NAME,
                     NULL,
                     WS_CHILD | WS_CLIPSIBLINGS | WS_BORDER,
                     rc.left,
                     rc.top,
                     rc.right - rc.left,
                     rc.bottom - rc.top,
                     m_hwndParent,
                     NULL,
                     g_hInst,
                     (LPVOID)this);

   }
return (NULL != m_hWnd);
}

IPersistStream接口
IE将调用浏览栏的IPersistStream接口,以便允许这个浏览栏加载或存储持久性数据。如果没有持久性数据,这个方法仍然必须返回一个成功代码。IPersistStream接口从IPersist继承而来,所以要实现五个方法:
GetClassID, IsDirty, Load, Save, GetSizeMax。
本文的这个浏览栏例子不使用持久性数据,并且只有IPersistStream的最小实现。GetClassID返回对象的CLSID(CLSID_SampleExplorerBar),其余的方法返回S_OK, 或者S_FALSE, 或者 E_NOTIMPL。有关细节请参见IPersistStream的实现。

IDeskBand接口
IDeskBand接口是区对象专用接口。它只有一个方法。IDeskBand接口从IDockingWindow继承而来,而IDockingWindow又从IOleWindow继承而来。
IOleWindow有两个方法:GetWindow 和 ContextSensitiveHelp。浏览栏例子的GetWindow实现返回浏览栏的子窗口句柄m_hwnd。因为不实现上下文敏感帮助,所以ContextSensitiveHelp返回E_NOTIMPL。
IDockingWindow接口有三个方法:ShowDW, CloseDW, 和 ResizeBorder。ResizeBorder不在任何区对象中使用,应该返回E_NOTIMPL。ShowDW方法根据其不同的参数值控制浏览栏窗口的显示或隐藏:

STDMETHODIMP CExplorerBar::ShowDW(BOOL fShow)
{
if(m_hWnd)
   {
   if(fShow)
      {
      //显示窗口
      ShowWindow(m_hWnd, SW_SHOW);
      }
   else
      {
      //隐藏窗口
      ShowWindow(m_hWnd, SW_HIDE);
      }
   }

return S_OK;
}

CloseDW方法摧毁浏览栏窗口:
STDMETHODIMP CExplorerBar::CloseDW(DWORD dwReserved)
{
ShowDW(FALSE);

if(IsWindow(m_hWnd))
   DestroyWindow(m_hWnd);

m_hWnd = NULL;

return S_OK;
}

其余的方法,如GetBandInfo是IDeskBand专用的。IE使用它来指定浏览栏的标示符以及视图模式。IE还可能填写DESKBANDINFO结构的dwMask成员从浏览栏请求更多的信息,这个结构用第三个参数传递。GetBandInfo应该存储这个标示符和视图模式并用所请求的数据填写DESKBANDINFO结构。下面是本文浏览栏例子所实现GetBandInfo:

STDMETHODIMP CExplorerBar::GetBandInfo(DWORD dwBandID, DWORD dwViewMode, DESKBANDINFO* pdbi)
{
if(pdbi)
   {
   m_dwBandID = dwBandID;
   m_dwViewMode = dwViewMode;

   if(pdbi->dwMask & DBIM_MINSIZE)
      {
      pdbi->ptMinSize.x = MIN_SIZE_X;
      pdbi->ptMinSize.y = MIN_SIZE_Y;
      }
   if(pdbi->dwMask & DBIM_MAXSIZE)
      {
      pdbi->ptMaxSize.x = -1;
      pdbi->ptMaxSize.y = -1;
      }
   if(pdbi->dwMask & DBIM_INTEGRAL)
      {
      pdbi->ptIntegral.x = 1;
      pdbi->ptIntegral.y = 1;
      }
   if(pdbi->dwMask & DBIM_ACTUAL)
      {
      pdbi->ptActual.x = 0;
      pdbi->ptActual.y = 0;
      }
   if(pdbi->dwMask & DBIM_TITLE)
      {
      lstrcpyW(pdbi->wszTitle, L"浏览栏例子");
      }
   if(pdbi->dwMask & DBIM_MODEFLAGS)
      {
      pdbi->dwModeFlags = DBIMF_VARIABLEHEIGHT;
      }
   if(pdbi->dwMask & DBIM_BKCOLOR)
      {
      //通过移开这个标志来使用默认的背景颜色
      pdbi->dwMask &= ~DBIM_BKCOLOR;
      }
   return S_OK;
   }
return E_INVALIDARG;
}

可选择的接口实现
由两个接口的实现是可选择的,一个是IInputObject,另一个是 IContextMenu。本文的浏览栏例子实现了IInputObject。对于IContextMenu的实现细节请参考有关文档。

IInputObject接口
如果某个band对象要接受用户输入。那就必须实现IInputObject接口。IE实现IInputObjectSite并用IInputObject维护用户的输入焦点。浏览栏需要实现三个方法:UIActivateIO, HasFocusIO, 和 TranslateAcceleratorIO。
IE调用UIActivateIO通知浏览栏它以被激活或者被置灰。当被激活时,浏览栏例子调用SetFocus来设置窗口输入焦点。
当要确定哪个窗口有输入焦点时,IE调用HasFocusIO。如果浏览栏的窗口或它的子窗口之一有输入焦点,HasFocusIO返回S_OK。否则,它返回S_FALSE。
TranslateAcceleratorIO允许对象处理键盘加速键。本文浏览栏例子没有实现这个方法,所以它返回S_FALSE。
浏览栏例子实现IInputObjectSite的细节如下:

STDMETHODIMP CExplorerBar::UIActivateIO(BOOL fActivate, LPMSG pMsg)
{
if(fActivate)
   SetFocus(m_hWnd);

return S_OK;
}

STDMETHODIMP CExplorerBar::HasFocusIO(void)
{
if(m_bFocus)
   return S_OK;

return S_FALSE;
}

STDMETHODIMP CExplorerBar::TranslateAcceleratorIO(LPMSG pMsg)
{
return S_FALSE;
}

窗口过程
因为区对象的显示用的是子窗口,所以它必须实现窗口过程来处理Windows消息。浏览栏例子实现了一个最简单的版本,它的窗口过程只处理了五个消息:WM_NCCREATE, WM_PAINT, WM_COMMAND, WM_SETFOCUS, 和 WM_KILLFOCUS。如果要实现更多的功能,很容易扩充使它处理其它的消息。

LRESULT CALLBACK CExplorerBar::WndProc(HWND hWnd, UINT uMessage, WPARAM wParam, LPARAM lParam)
{
CExplorerBar  *pThis = (CExplorerBar*)GetWindowLong(hWnd, GWL_USERDATA);

switch (uMessage)
   {
   case WM_NCCREATE:
      {
      LPCREATESTRUCT lpcs = (LPCREATESTRUCT)lParam;
      pThis = (CExplorerBar*)(lpcs->lpCreateParams);
      SetWindowLong(hWnd, GWL_USERDATA, (LONG)pThis);

      //设置窗口句柄
      pThis->m_hWnd = hWnd;
      }
      break;  
   case WM_PAINT:
      return pThis->OnPaint();  
   case WM_COMMAND:
      return pThis->OnCommand(wParam, lParam);  
   case WM_SETFOCUS:
      return pThis->OnSetFocus();
   case WM_KILLFOCUS:
      return pThis->OnKillFocus();
   }
return DefWindowProc(hWnd, uMessage, wParam, lParam);
}

这里WM_COMMAND消息处理器简单地返回零。WM_PAINT消息处理器创建文本并显示在资源管理器或IE的区对象中。

LRESULT CExplorerBar::OnPaint(void)
{
PAINTSTRUCT ps;
RECT        rc;

BeginPaint(m_hWnd, &ps);
GetClientRect(m_hWnd, &rc);
SetTextColor(ps.hdc, RGB(255, 255, 255));
SetBkMode(ps.hdc, TRANSPARENT);
DrawText(ps.hdc, TEXT("浏览栏例子"), -1, &rc, DT_SINGLELINE | DT_CENTER | DT_VCENTER);
EndPaint(m_hWnd, &ps);
return 0;
}

WM_SETFOCUS 和 WM_KILLFOCUS消息处理器通过调用本现场的IInputObjectSite::OnFocusChangeIS方法通知输入焦点现场改变:

LRESULT CExplorerBar::OnSetFocus(void)
{
FocusChange(TRUE);

return 0;
}

LRESULT CExplorerBar::OnKillFocus(void)
{
FocusChange(FALSE);
return 0;
}

void CExplorerBar::FocusChange(BOOL bFocus)
{
m_bFocus = bFocus;

//通知焦点已改变的输入对象现场
if(m_pSite)
   {
   m_pSite->OnFocusChangeIS((IDockingWindow*)this, bFocus);
   }
}

四、总结

区对象提供了灵活和强大的扩展方式,通过定制浏览栏使得IE的功能大为增强。桌面区的实现扩展了普通窗口的能力。尽管需要一些对COM的编程,但终究以子窗口的形式提供了一种用户界面。从而使今后的许多这种编程实现都能用类似的Windows编程技术。虽然本文所讨论的例子只提供了有限的功能,但它示范了区对象全部的特性,并且可以在此基础上进行扩充来创建独特和功能强大的的用户界面。

浏览器中显示PDF

2009/03/29
使用.NET 向浏览器写入二进制文件 – 相关参考资料 – pdf之家 pdf阅读器格式文件下载
http://www.haopdf.cn/viewthread-2263.html

使用.NET 向浏览器写入二进制文件

admin 发表于: 2008-9-07 10:22 来源: pdf之家

本例演示了如何从文件中检索二进制数据,然后如何使用 ASP.NET 和 Visual C# 将该数据写出到浏览器中。尽管此演示使用的是 Adobe Acrobat (.pdf) 文件(Web 应用程序开发人员常使用这种文件),但您也可以对其他二进制文件格式使用此过程。

1、确保要查看 .aspx 页的客户端计算机上已安装了 Adobe Reader,以便浏览器能够正确读取并呈现二进制数据。可以从 Adobe 网站下载 Adobe Acrobat Reader:

http://www.chinese-s.adobe.com/main.html (http://www.adobe.com)
2、确保将您的页面添加到项目中在上一节中添加的 .pdf 文件所在的级别。这一点非常重要,因为代码最初引用 .pdf 文件时采用相对路径。

在页面的Page_Load 事件中,加入以下代码:

private void Page_Load(object sender, System.EventArgs e)
…{
//Set the appropriate ContentType.
Response.ContentType = “Application/pdf”;
//Get the physical path to the file.
string FilePath = MapPath(“acrobat.pdf”);
//Write the file directly to the HTTP content output stream.
Response.WriteFile(FilePath);
Response.End();
}

Java技巧:怎样从servlet打开一个非html文件(如PDF) – 相关参考资料 – pdf之家 pdf阅读器格式文件下载
http://www.haopdf.cn/viewthread-2281.html

Java技巧:怎样从servlet打开一个非html文件(如PDF)

admin 发表于: 2008-9-12 08:26 来源: pdf之家

1、摘要

Java Servlet 程序提供了一个简单的方式发送HTML文件到客户端浏览器。但是,现在一些站点提供了一些非HTML的存取文件,例如Adobe PDF,Microsoft Word, 和 Microsoft Excel 文件等。这篇文章以使用PDF和WORD为例子讲解怎样从SERVLET发送一个非HTML文件到客户端浏览器。事实上,通过本文,你可以通过MIME类型发送任何的格式的文件。同时,也能了解到通过SERVLET怎样与防火墙进行交互。

通过一个SERVLET在浏览器中打开一个文件,你只需简单的把这个文件加入到SERVLET的输出流中。尽管这么做看起来很简单,但是当你打开一个非HTML文件时你必须需要一些事情,例如二进制文件和多个文件的问题。

你可以通过一下方式得到SERVLET输出流:

ServletOutputStream out = res.getOutputStream();

互联网通过MIME(多用途网络邮件扩展)协议发送多部分,多媒体和二进制文件。在SERVLET的RESPONSE对象中,设置你想打开的文件的MIME类型是非常重要的。

2、MIME类型

客户端浏览器通过MIME类型来识别非HTML文件和决定用什么包容器来呈现这个数据文件。插件能够通过MIME类型来决定用什么方式来打开这些文件,当我们下载这些文件以后,我们通过浏览器就可以浏览这些文件了。

MIME类型很有用,他们允许浏览器通过内置技术处理不同的文件类型。

MIME类型对于一个PDF文件是application/pdf。当在SERVLET打开一个PDF文件时,你必须在RESPONSE最前面设置application/pdf:

// MIME type for pdf doc
res.setContentType( application/pdf);

打开一个WORD文档,你应该设置成application/msword:

// MIME type for MSWord doc
res.setContentType( application/msword );

对于EXCEL文档,使用MIME类型是application/vnd.ms-excel。如果需要的插件系统没有没有安装,浏览器会出现一个提示框,询问用户是否在当前位置打开还是保存到磁盘上。

3、内容部署

部署内容的HTTP response 头允许servlet指定文件类型的信息。使用这个头说明,你可以制定特定的方式来打开这个文件(不仅仅在浏览器中),而且它不应该自动的显示。你可以设置要保存的文件名。这个文件应该是对话框中显示的文件名。如果你不想指定文件名,你得到的是你的servlet同名的文件名称。

在SERVLET中你可以象下面的代码一样来设置头

res.setHeader(Content-disposition,
attachment; filename= +
Example.pdf);
// attachment – since we dont want to open
// it in the browser, but
// with Adobe Acrobat, and set the
// default file name to use.

如果你想打开一个WORD文件,应该如下做:

res.setHeader(Content-disposition,
attachment; filename +
Example.doc );

4、封装

剩下的工作就很简单了。你需要基于你想打开的文件的名字创建一个java.net.URL对象。这个字符串应该完整的指出这个文件的位置。举例说明:

String fileURL =
http://www.adobe.com/aboutadobe/careeropp/pdfs/adobeapp.pdf;

你的url字符串应该象http://www.gr.com/pub/somefile.doc或者

http://www.gr.com/pub/somefile.xls这个样子。

但是你必须确认你所打开的文件与你MIME类型中指定的文件类型一定要一致。

URL url = new URL ( fileURL );

5、防火墙

如果你的浏览器需要通过防火墙,最后的一件事情就是你需要担心的就是你的URL连接了。你需要找出一些你的代理服务器的一些信息,例如主机名称和端口号去与防火墙建立连接。如果你使用JAVA 2,你应该创建一个URL连接对象,设置以下的系统属性:

URLConnection conn = url.openConnection();

// Use the username and password you use to
// connect to the outside world
// if your proxy server requires authentication.
String authentication = Basic + new
sun.misc.BASE64Encoder().encode(username:password.getBytes());

System.getProperties().put(proxySet, true);

System.getProperties().put(proxyHost, PROXY_HOST); // your proxy host
System.getProperties().put(proxyPort, PROXY_PORT); // your proxy port
conn.setRequestProperty(Proxy-Authorization, authentication);

如果你使用JDK 1.1你可以不用建立系统属性。你应该用你的代理服务器信息创建JAVA.NET.URL对象。

url = new URL(http, PROXY_HOST,
Integer.parseInt(PROXY_PORT),
fileURL );
// assumes authentication is not required

6、实现

在开始读取你得文件的时候,你需要从URLConnection (or URL)对象中包含InputStream。

在以下的例子中,你用一个BufferedInputStream来限制这个InputStream。

如果你正在使用这个URLConnection,以下就是这样的代码:

BufferedInputStream  bis = new BufferedInputStream(conn.getInputStream());

如果你正在使用URL,那就象以下的代码:

BufferedInputStream  bis = new BufferedInputStream(url.openStream());

一旦做完以上工作,你只需简单的从输入流写出每一个字节到servlet的输出流中

BufferedOutputStream bos = new
BufferedOutputStream(out);

byte[] buff = new byte[2048];
int bytesRead;

// Simple read/write loop.
while(-1 != (bytesRead = bis.read(buff, 0, buff.length))) {
bos.write(buff, 0, bytesRead);
}

最后,在最后的模块中关闭这个流。

这个例子通过使用一个servlet的doPost方法来实现这个功能。

public void doPost(HttpServletRequest req,
HttpServletResponse res)
throws ServletException, IOException
{
ServletOutputStream  out           =         res.getOutputStream ();

//—————————————————————
// Set the output datas mime type
//—————————————————————

res.setContentType( application/pdf);  // MIME type for pdf doc

//—————————————————————
// create an input stream from fileURL
//—————————————————————

String fileURL =
http://www.adobe.com/aboutadobe/careeropp/pdfs/adobeapp.pdf;

//————————————————————
// Content-disposition header – dont open in browser and
// set the Save As… filename.
// *There is reportedly a bug in IE4.0 which  ignores this…
//————————————————————
res.setHeader(Content-disposition,
attachment; filename= +=
Example.pdf);

//—————————————————————–
// PROXY_HOST and PROXY_PORT should be your proxy host and port
// that will let you go through the firewall without authentication.
// Otherwise set the system properties and use URLConnection.getInputStream().
//—————————————————————–
BufferedInputStream  bis = null;
BufferedOutputStream bos = null;

try {
URL url = new URL( http, PROXY_HOST,
Integer.parseInt(PROXY_PORT), fileURL  );

// Use Buffered Stream for reading/writing.
bis = new BufferedInputStream(url.openStream());
bos = new BufferedOutputStream(out);

byte[] buff = new byte[2048];
int bytesRead;

// Simple read/write loop.
while(-1 != (bytesRead = bis.read(buff, 0, buff.length))) {
bos.write(buff, 0, bytesRead);
}

} catch(final MalformedURLException e) {
System.out.println ( MalformedURLException. );
throw e;
} catch(final IOException e) {
System.out.println ( IOException. );
throw e;
} finally {
if (bis != null)
bis.close();
if (bos != null)
bos.close();
}
}

7、总结

正如你所看到的,在一个servlet中打开一个非HTML是很简单的,甚至在一个防火墙之外。你可以使用同样的代码打开图像文件或者其他类型的多媒体文件,只要正确设置MIME类型。今天,在互联网上更多的信息都变得有效了,并且其中大量的信息是以非HTML类型存储的。相比之下,开发一个servlet来呈现非HTML文档是一件容易,方便的方式来提供给你的用户更多的信息。

在.NET中三种方式生成WSDL的方法_wanghero_猫扑博客
http://i.mop.com/wanghero/blog/2006/05/11/4247826.html
WSDL是一个XML格式用以描述怎样调用具体的Web Service,Web Service提供什么样的Service等等。

在.NET中有三种方式生成WSDL:

1.在Web Service的URL后面加上WDSL需求,如下:

http://localhost/webExamples/simpleService.asmx?WSDL

2.使用disco.exe。在命令行中写下如下的命令:

disco http://localhost/webExamples/simpleService.asmx

3.使用System.Web.Services.Description命名空间下提供的类

每个 WSDL 文件的根元素都是 <definitions>,必须在其中提供服务的完整描述。首先,必须在 <definitions> 元素中提供各种名称空间的声明。

<definitions> 元素包含一个或多个 < portType > 元素,每个元素都是一系列 operation。可以将单个portType元素看作是将各种方法组成类的一个逻辑分组。应该将每个Types称为服务,因此整个 WSDL 文件将成为一个服务集合。

在每个服务内可以有几个方法或者 operation,WSDL 通过 <operation> 元素来引用它们。

下面是一个最简单的WSDL例子

<?xml version=”1.0″ encoding=”UTF-8″ ?>
<definitions name=”MobilePhoneService”
targetNamespace=”www.mobilephoneservice.com/MobilePhoneService-interface
xmlns=”http://schemas.xmlsoap.org/wsdl/
xmlns:soap=”http://schemas.xmlsoap.org/wsdl/soap/
xmlns:tns=”http://www.mobilephoneservice.com/MobilePhoneService
xmlns:xsd=”http://www.w3.org/1999/XMLSchema“>

<portType name=”MobilePhoneService_port”>
<operation name=”getListOfModels “>
…….
…….
</operation>

<operation name=”getPrice”>
…….
…….
</operation>
</portType>
</definitions>

Sample 1

定义好操作(或方法)以后,现在需要指定将向它们发送和从它们返回的参数。在 WSDL 术语中,所有参数称为“消息”。认为您是在递送消息而结果得到返回的消息是有用的。方法调用是这样一种操作:它准备返回“消息”来响应进入的消息。

<?xml version=”1.0″ encoding=”UTF-8″ ?>
<definitions name=”MobilePhoneService” targetNamespace=”http://www.mobilephoneservice.com/MobilePhoneService-interface
xmlns=”http://schemas.xmlsoap.org/wsdl/
xmlns:soap=”http://schemas.xmlsoap.org/wsdl/soap/
xmlns:tns=”http://www.mobilephoneservice.com/MobilePhoneService
xmlns:xsd=”http://www.w3.org/1999/XMLSchema“>
<types>
<xsd:schema targetNamespace=”http://www.mobilephoneservice.com/MobilePhoneService
xmlns=”http://www.w3.org/1999/XMLSchema/“>
<xsd:complexType name=”Vector”>
<xsd:element name=”elementData” type=”xsd:String” />
<xsd:element name=”elementCount” type=”xsd:int” />
</xsd:complexType>
</xsd:schema>
</types>
<message name=”ListOfPhoneModels”>
<part name=”models” type=”tns:Vector”>
</message>
<message name=”PhoneModel”>
<part name=”model” type=”xsd:String”>
</message>
<message name=”PhoneModelPrice”>
<part name=”price” type=”xsd:String”>
</message>
<portType name=”MobilePhoneService_port”>
<operation name=”getListOfModels “>
<output message=”ListOfPhoneModels”/>
</operation>
<operation name=”getPrice”>
<Input message=”PhoneModel”/>
<output message=”PhoneModelPrice”/>
</operation>
</portType>
</definitions>

Sample 2

WSDL 的任务是定义或描述 Web 服务,然后提供一个对外部框架的引用来定义 WSDL 用户将如何实现这些服务。可以将这个框架当作 WSDL 抽象定义和它们的实现之间的“绑定(binding)”。

WSDL 将指定能够访问 Web 服务实际实现的 SOAP 服务器,并且从那时起 SOAP 的整个任务就是将用户从 WSDL 文件带到它的实现。

WSDL binding 元素包含您将用于绑定用途的外部技术的声明。因为正在使用 SOAP,所以这里将使用 SOAP 的名称空间。WSDL 术语中,对外部名称空间的使用称为 extensibility 元素。

… …
<portType name=”MobilePhoneService_port”>

… …
</portType>
<binding name=”MobilePhoneService_Binding” type=”MobilePhoneService_port”>
<soap:binding style=”rpc” transport=”http://schemas.xmlsoap.org/soap/http” />
<operation name=”getListOfModels “>
<soap:operation soapAction=”urn:MobilePhoneService” />
<input>
<soap:body encodingStyle=”http://schemas.xmlsoap.org/soap/encoding/” namespace=”urn:MobilePhoneService” use=”encoded” />
</input>
<output>
<soap:body encodingStyle=”http://schemas.xmlsoap.org/soap/encoding/” namespace=”urn:MobilePhoneService” use=”encoded” />
</output>
</operation>
</binding>

… …

Sample 3

WSDL 需要一个附加步骤来创建该 WSDL 文件的概要。WSDL 将该文件称为 implementation 文件,当在 UDDI 注册中心发布 Web 服务时,会使用它。

<?xml version=”1.0″ encoding=”UTF-8″ ?>
<definitions name=”MobilePhoneService”
targetNamespace=”http://www.mobilephoneservice.com/MobilePhoneService
xmlns=”http://schemas.xmlsoap.org/wsdl/
xmlns:soap=”http://schemas.xmlsoap.org/wsdl/soap/
xmlns:tns=”http://www.mobilephoneservice.com/MobilePhoneService
xmlns:xsd=”http://www.w3.org/1999/XMLSchema“>
<import location=”http://localhost:8080/wsdl/MobilePhoneService-interface.wsdl” namespace=http://www.mobilephoneserviceservice.com/MobilePhoneService-interface/>
<service name=”MobilePhoneService”>
<documentation> Mobile Phone Information Service </documentation>
<port binding=”MobilePhoneService_Binding” name=”MobilePhoneService_ServicePort”>
<soap:address location=”http://localhost:8080/soap/servlet/rpcrouter” />
</port>
</service>
</definitions>

Introducing SOAP

Dave Wraight Planet PDF Contributing Editor

March 18, 2004

Advertisement
Advertisement

SOAP or Simple Object Access Protocol has become a standard mechanism in the world of Web Services. Now what exactly does this mean? And how can I make use of it inside Acrobat?

The definition given by the governing standards body W3C (World Wide Web Consortium) is:

“SOAP is a lightweight protocol for exchange of information in a decentralized, distributed environment. It is an XML based protocol that consists of three parts: an envelope that defines a framework for describing what is in a message and how to process it, a set of encoding rules for expressing instances of application-defined datatypes, and a convention for representing remote procedure calls and responses. SOAP can potentially be used in combination with a variety of other protocols; however, the only bindings defined in this document describe how to use SOAP in combination with HTTP and HTTP Extension Framework.”

This is quite a mouthful, but essentially means that SOAP is a way for two computer programs to talk to each other using XML as the language and HTTP as the transport layer.

These two programs can be on the same network or across the other side of the planet using different hardware, software and operating systems. They can communicate because they have a common language: SOAP.

Before SOAP came on the scene there were several large vendors each with their own proprietary protocols (CORBA, RMI, COM/DCOM) and toolsets this made it very difficult to implement systems when the two systems didn’t talk the same language. SOAP promises to make these complications a thing of the past.

Now that we have a little bit better understanding of what SOAP is, the next question is how is it useful to me?

Illustrating The Point

Let’s say for example that you have a customer who wants to purchase some rare coins from your website. Since these coins are hard to find you don’t keep them in stock, instead you have a supplier that you purchase them from.

Your potential customer browses your list of coins making selections and adding them to their online shopping basket. Once they have finished browsing they want to purchase their selections and proceed to the checkout.

At the checkout page they discover that the coins that they have selected aren’t in stock and since your web site isn’t setup to automatically find available stock you aren’t able to inform the customer of the expected delivery dates.

Rather than lose a potential sale, you decide to investigate a better way to do business.

This is where Web Services & SOAP steps in. Imagine instead that your web server was able to talk to the computer of your supplier(s) and find out what stock they have and the exact delivery details, also imagine that this could be done on demand and in real-time.

All that’s required to make this happen is for the supplier(s) to create a web service for their distributors (namely your web site) that let’s you find out what stock they have and also when it can be delivered.

In the above picture the supplier’s web service is asked about a product’s availability, delivery and exact price, it returns this information in the same structured way as it was asked for, in XML.

Your web site is now able to immediately inform the customer what is available, how much it will cost and when they can expect delivery.

Acrobat And SOAP

Right about now your probably wondering how is Acrobat involved in this loop? Well it turns out that Acrobat version 6 now supports SOAP through Javascript.

This means that you can build fully interactive and graphically designed PDF documents that have built in smarts for talking to Web Services. This is great since you can combine everything that’s great about PDF with the ability to do real business with Web Services.

To really illustrate how this works I’ve created a PDF form with two examples to demonstrate how to make use of web services/SOAP. The first free web service allows you to convert between different currencies for any currency in the world and the second web service gives you the postcode for any australian city name.

There are several things going on in the background after you press the ‘GO’ button. The first thing to happen is that Javascript is called upon to create a connection between the PDF form and the Web Service. This connection is made using SOAP.

This connection is referred to as an End Point connection and generally requires a URI (Uniform Resource Indentifier). At the other end of the connection is the Web Services Definition Language (WSDL) file for the web service. This file describes the parameters and responses the web service requires and allows other users to know how to operate the web service.

After a connection has been made between Acrobat and the Web Service a Javascript Object is created representing the web service, this object can then be used to pass information to the web service in the hope it will return the information we are chasing.

The Process

As the diagram below illustrates the following five steps are happening:

  1. Connection Object created between Acrobat and the Web Service
  2. Web Service Functions called
  3. User entered data taken from Form text fields and wrapped as XML fragments
  4. Web Service process request and returns response wrapped in XML
  5. Acrobat automatically formats the raw XML data in human readable format

The potential for Web Services & SOAP to really help businesses communicate is enormous. Couple this with the platform independance, stable and secure format that we have grown to known from PDF and it’s a perfect match.

Adobe Forums – WSDL-Data Binding Issue
http://www.adobeforums.com/webx/.3c057542/0
Topic

WSDL-Data Binding Issue

Saritha Mikkilineni – 01:54pm Nov 30, 2007 Pacific

I am very very new to Adobe LiveCycle ES (Beginner). I am having problems binding the Response to the Fields on the xdp form.

I was successfully able to invoke the Webservice. findAccount webservice takes ssn as input and returns xml string as response. I need to bind the xml string response to the xsd schema fields on the form (xdp). How can I do it?

Below are the detail steps I created

1.open new template in Designer 7.0
2.from the Data View, added new XML Schema file
3.Designed the form using the XML Schema elements
4.again from the Data View, invoked the WSDL ( findAccount WS. Ssn – as input. XML string as output)
5.When I hit the “Button” by giving the input, I see the response in the output box. But, I need the output displayed on the from fields which I binded it to the xml schema .


Reply To This Discussion | Back to Topic List | Bookmark | Subscribe
To start a NEW discussion click on the Back to Topic List link and select Add Topic.
If you are in an archive forum please go up to the main topic list (archives are read only).

Messages

3 messages. Displaying 1 through 3.
First    Previous      Next  Last  Show All Messages
WorkflowUser - 7:13pm Nov 30, 07 PST (#1 of 3)

I assume your web service is returning data of type “xml” and not string
Once you do this store the xml data in a hidden text field. then get the raw value of this hidden text field. This is your xml. you can xpath into the xml data.
for more information check the javascript for acrobat documentation @
http://www.verydoc.com/documents/acrojsguide/index.htm
go to Page 258,you will find some examples on how to do it

Post Reply | Bookmark back to top

WorkflowUser - 6:49pm Dec 1, 07 PST (#2 of 3)

my earlier post indicated that your web service return xml type instead of string, this is not requried it can be of string type as well

Post Reply | Bookmark back to top

Saritha Mikkilineni - 7:20am Dec 4, 07 PST (#3 of 3)

Thanks for the response. This issue is resolved.

Post Reply | Bookmark back to top

To post a message, compose your text in the box below, then click on Post (below) to send the message.

A blank line starts a new paragraph. A line starting with ‘b ‘, ‘i ‘, ‘c ‘, ‘* ‘, ‘] ‘, or ‘> ‘ provides simple formatting. You may use links to previous messages. See the quick-edit help for more information.

Message:
Subscribe to this discussion by email


You cannot rewrite history, but you will have 30 minutes to make any changes or fixes after you post a message. Just click on the Edit button which follows your message after you post it.

read subscriptions | message center | forum search | knowledgebase search | help | preferences | logout
 / User to User Forums  / Adobe Product Forums  / Adobe LiveCycle Forums  / LiveCycle ES  / LiveCycle Designer ES

VC++获得当前系统时间的几种方案 zz – VC – zz
http://blog.chinaunix.net/u2/63021/showart_1099314.html
VC++获得当前系统时间的几种方案 zz
//方案— 优点:仅使用C标准库;缺点:只能精确到秒级
#include <time.h>
#include <stdio.h>
int main( void )
{
time_t t = time( 0 );
char tmp[64];
strftime( tmp, sizeof(tmp), “%Y/%m/%d %X %A 本年第%j天 %z”,
localtime(&t) );
puts( tmp );
return 0;
}
//方案二 优点:能精确到毫秒级;缺点:使用了windows API
#include <windows.h>
#include <stdio.h>
int main( void )
{
SYSTEMTIME sys;
GetLocalTime( &sys );
printf( “%4d/%02d/%02d %02d:%02d:%02d.%03d 星期%1d\n”
,sys.wYear,sys.wMonth,sys.wDay
,sys.wHour,sys.wMinute,sys.wSecond,sys.wMilliseconds
,sys.wDayOfWeek);
return 0;
}
//方案三,优点:利用系统函数
#include<stdlib.h>
#include<iostream>
using namespace std;
void main(){
system(“time”);
}
可以改变电脑的时间设定
方案4:
#include<iostream>
#include<ctime>
using namespace std;
int main()
{
time_t now_time;
now_time = time(NULL);
cout<<now_time;
return 0;
}
另一:_strdate(tempstr);
另二:
CString CTestView::GetTime()
{
CTime CurrentTime=CTime::GetCurrentTime();
CString strTime;
strTime.Format(“%d:%d:%d”,CurrentTime.GetHour(),  CurrentTime.GetMinute(),CurrentTime.GetSecond());
return strTime;
} language=VBScript>call ReplaceSubjectHTML_emote(592915)

精确获得时间
Win32   API函数库中已经为用户提供了一组用于高精度计时的底层函数,如果用户使用得当,计时精度可到1ms。这个计时精度,对于一般的实时系统控制完全可以满足要求。现将由BCB提供的重新封装后的一组与时间相关的主要接口函数(函数名、参数、功能与Win32API基本相同)说明如下:
1.DWORD   timeGetTime(void)
返回从Windows启动开始经过的毫秒数。最大值为2的32次方,约49.71天。
2.MMRESULT timeSetEvent(UINT   uDelay,UINT   uResolution,
LPTIMECALLBACK lpTimeProc, DWORD dwUser, UINT fuEvent)
该函数设置一个定时回调事件,此事件可以是一个一次性事件或周期性事件。事件一旦被激活,便调用指定的回调函数,成功后返回事件的标识代码,否则返回NULL.参数说明如下:
uDelay:以毫秒制定事件的周期。
UResolution:以毫秒指定延时的精度,数值越小定时器事件分辩率越高。缺省值为1ms.
LpTimeProc:指向一个回调函数。
DwUser:存放用户提供的回调数据。
FuEvent:指定定时器事件类型:TIME_ONESHOT:uDelay毫秒后只产生一次事件。
TIME_PERIODIC: 每隔uDelay毫秒周期性地产生事件。

3.MMRESULT   timeKillEvent(UINT   uTimerID)
该函数取消一个指定的定时器回调事件。uTimerID标识要取消的事件(由timeSetEvent函数返回的标识符)。如果定时器时间不存在则返回MMSYSERR_INVALPARAM。
void CALLBACK TimeProc(UINT uID,UINT uMsg,DWORD dwUser,DWORD dw1,DWORD dw2);
该函数是一个应用程序定义的回调函数,出现定时器事件时该函数被调用。TimeProc是应用程序定义的函数名的占位符。

使用该函数时要注意的是,它只能调用以下有限的几组API函数:
PostMessage,timeGetSystemTime, timeGetTime, timeSetEvent, timeKillEvent,
midiOutShortMsg, midiOutLongMsg, OutputDebugString。同时也不要使用完成时间很长的API函数,程序尽可能简短。
使用以上一组函数就可以完成毫秒级精度的计时和控制(在BCB使用时要将头文件mmsystem.h加到程序中)。由于将定时控制精确到几毫秒,定时器事件将占用大量的CPU时间和系统资源,所以在满足控制要求的前提下,应尽量将参数uResolution的数值增大。而且定时器实时控制功能完成后要尽快释放。

COM组件的开发

VC OCX 打包CAB 网页发布全过程记录_Asp.net实例教程_Asp.net_网站开发
http://www.diybl.com/course/4_webprogram/asp.net/asp_netshl/2008111/96318.html

VC OCX 打包CAB 网页发布全过程记录

http://www.diybl.com/ 2008-1-11  网络 点击: [ 评论 ]
文章搜索:    【点击打包该文章】
【本站开通在线QQ讨论群】

第一次发布一个activeX控件,真的费了不少力气,主要是对发布的原理和过程不大清楚。在这里记下过程,以备后用和其它网友参考。

1,创建OCX:你要创建一个可用的OCX控件,并在本机测试通过。

2,打包CAB:WinCAB 是一种可视化的全新的CAB压缩包制作软件,它具有采用图形界面、支持分卷压缩、可制作具有自解包功能的CAB压缩包(*.EXE文件格式)等优秀功能, 这就从根本上解决了CAB压缩包的制作问题。需要注意的是在运行WinCAB.exe时,必须确保makecab.exe文件也在相同的目录下。


注:WinCAB在网上有很多下载,可以去百度搜,makecab.exe在系统盘windows\system32下。

一般来说,一个单独的OCX不需要INF也可以打成CAB发布,所以这里就不介绍INF文件了,想了解的话,去我文章下边的几个链接。


3,数字签名:做为测试,也可以不要数字签名。只需要在客户端PC的IE安全选项中,把级别调低或针对ACTIVEX设置调成enable。


关于证书和签名     -|zuiwanting 发表于 2006-5-24 9:58:00

[C#]利用反射调用COM组件 【中国IT软件知识库/大中华软件交易网】软件公司/软件开发人员社区论坛 软件项目交易网(软件外包、项目外包承接、软件供求交易、软件招标投标、投资融资合作)
http://www.ooreport.com/bbs/topicView.aspx?Id=90034

[C#]利用反射调用COM组件

最常见的调用com组件的方法就是添加对这个组件的引用,但是这会在最终可执行文件边生成一个伴随的dll如MyCom.Interop.dll等,这给程序的分发带来相当程度的不便,而且添加引用是我们只能引用某个特定版本的com组件,而我们往往无法确定程序所运行的机器上是否有相应版本的com组件,这可能造成我们的程序无法运行。我们要如何解决这个问题呢?
首先要解决com组件的版本问题,这就要求我们不直接使用clsid,而用ProgId来代替客户机上的com组件就可以解决相当程度的问题了,就是这么简单。
至于如何才能把那个可恶的dll文件给去掉,这就需要利用dotNet提供的反射功能了,我们一代码为例,这里我们假设有一个ProgId为”ReflectionCom.TestObj”的com组件,这个组件有唯一一个方法string SayHello(string AName):

using System;
using System.Reflection;

namespace TestConsole
{
class MainEntryPoint
{
static void Main(string[] args)
{
object[] oParams = new object[] { leafyoung };
object oComObj = Activator.CreateInstance(
Type.GetTypeFromProgID(
ReflectionCOM.TestObj));
object rez = oComObj.GetType().InvokeMember(SayHello,
BindingFlags.InvokeMethod,
null,
oComObj,
oParams);
Console.WriteLine(rez);
}

}

}

行了,我们就是怎么简简单单就可以调用com组件的任何功能了。
不过我们要看到这种方法的不足,由于这里用了迟绑定,因此我们无法让编译器做任何的类型检查工作,因此只有在运行时才能知道程序对错;而且,可以以这种方法调用的com组件必须实现IDispatch接口,也就是说必须是一个自动化组件,假如你要调用的组件是一个普通的com组件的话,那就一边哭去吧!呵呵

PRB: Creating STA Components in the Constructor in ASP.NET ASPCOMPAT Mode Negatively Affects
http://support.microsoft.com/default.aspx?scid=kb;en-us;308095

PRB: Creating STA Components in the Constructor in ASP.NET ASPCOMPAT Mode Negatively Affects Performance

This article was previously published under Q308095

SYMPTOMS

When you call apartment-threaded components from an ASP.NET page in ASPCOMPAT m…

When you call apartment-threaded components from an ASP.NET page in ASPCOMPAT mode, you may notice severe performance degradation.

CAUSE

If you use ASPCOMPAT mode (that is, if you use a page with the <%@ ASPCOMPAT=”t…

If you use ASPCOMPAT mode (that is, if you use a page with the <%@ ASPCOMPAT=”true” %> directive), ASP.NET runs those pages on an STA thread pool. However, Component Object Model (COM) components that are created at construction time are created before the request is scheduled to the single-threaded apartment (STA) thread pool and are therefore created from a multithreaded apartment (MTA) thread. In this scenario, you experience substantial performance degradation.

Most significantly, the same thread (host STA) executes all instances of apartment-threaded components that are created from MTA threads. This means that even though all users have a reference to their own instance of the COM component, all of the calls into these components are serialized to this one thread (only one call executes at a time).

Additionally, there is a smaller performance hit each time a call is made to the component from the Page events because of a thread switch. This is because the Page events are executed on a thread from the STA pool, but the COM component is still executed on the host STA (because the COM component was created from an MTA client). This thread switch also leads to other errors if you use impersonation. For more information, see the “References” section of this article.

RESOLUTION

If you are using ASPCOMPAT mode with STA components, only create COM components…

If you are using ASPCOMPAT mode with STA components, only create COM components from a method or one of the Page events (for example, Page_Load, Page_Init, and so on), and do not create these COM components at construction time.

For example, avoid a member declaration similar to the following, which creates the component at construction time:

Visual Basic .NET

<%@ Page Language="VB" ASPCOMPAT="TRUE" %>
<script runat="server">

Dim comObj As MyComObject = New MyComObject()

Public Sub Page_Load()
   comObj.DoSomething()
End Sub
</script>

Visual C# .NET

<%@ Page Language="C#" ASPCOMPAT="TRUE" %>
<script runat="server">

MyComObject comObj = new MyComObject();

public void Page_Load()
{
   comObj.DoSomething()
}
</script>

Visual J# .NET

<%@ Page Language="VJ#" ASPCOMPAT="TRUE" %>
<script runat="server">

MyComObject comObj = new MyComObject();

public void Page_Load()
{
  comObj.DoSomething();
}
</script>

Use the following code instead:

Visual Basic .NET

<%@ Page Language="VB" ASPCOMPAT="TRUE" %>
<script runat="server">

Dim comObj As MyComObject 

Public Sub Page_Load()
   comObj = New MyComObject()
   comObj.DoSomething()
End Sub

Visual C# .NET

<%@ Page Language="C#" ASPCOMPAT="TRUE" %>
<script runat="server">

MyComObject comObj;

public void Page_Load()
{
   comObj = new MyComObject();
   comObj.DoSomething();
}

Visual J# .NET

<%@ Page Language="VJ#" ASPCOMPAT="TRUE" %>
<script runat="server">

MyComObject comObj;

public void Page_Load()
{
  comObj = new MyComObject();
  comObj.DoSomething();
}
</script>

STATUS

This behavior is by design.

This behavior is by design.

REFERENCES

For additional information, click the article number below to view the article i…

For additional information, click the article number below to view the article in the Microsoft Knowledge Base:

325791 (http://support.microsoft.com/kb/325791/EN-US/ ) PRB: Access Denied Error Message Occurs When Impersonating in ASP.NET and Calling STA COM Components

APPLIES TO
  • Microsoft ASP.NET 1.1
  • Microsoft Visual Basic .NET 2003 Standard Edition
  • Microsoft Visual C# .NET 2003 Standard Edition
  • Microsoft ASP.NET 1.0
  • Microsoft Visual Basic .NET 2002 Standard Edition
  • Microsoft Visual C# .NET 2002 Standard Edition
  • Microsoft Visual J# .NET 2003 Standard Edition
  • Microsoft .NET Framework 1.1
Keywords:
kbhttpruntime kbinterop kbperformance kbprb kbreadme kbthread KB308095

CodeProject: Importing and Extending ActiveX Controls in .NET. Free source code and programming help
http://www.codeproject.com/KB/cs/importactivex.aspx

PRB: 灾难性故障错误使用 ActiveX 控件
http://support.microsoft.com/kb/189065/zh-cn

PRB: 灾难性故障错误使用 ActiveX 控件

本页

症状

从 Java, 试图使用 VisualC++ 生成 ActiveX 控件时试图运行 Java 应用程序时将看到以下错误: 错误: <0x8000ffff>…

从 Java, 试图使用 VisualC++ 生成 ActiveX 控件时试图运行 Java 应用程序时将看到以下错误:

错误: <0x8000ffff> 正在按任意键以继续 Catastrophic 失败

解决方案

当试图使用 ActiveX 控件作为自动化服务器, 您需要重写 IsInvokeAllowed 方法。 有关为什么此方法对被覆盖, 请参阅下面参考部分。 要…

当试图使用 ActiveX 控件作为自动化服务器, 您需要重写 IsInvokeAllowed 方法。 有关为什么此方法对被覆盖, 请参阅下面参考部分。

要解决该问题, 如下替代 IsInvokeAllowed 将 ActiveX 控件中:

BOOL CMyOleControl::IsInvokeAllowed (DISPID)
{

   // You can check to see if COleControl::m_bInitialized is FALSE
   // in your automation functions to limit access.
  return TRUE;
}

对于下面, 给定示例情况都将覆盖 Circ3ctl.cpp 文件中此方法并声明 Cir3ctl.h 文件中此虚函数。 还请确保重建并从 Java 使用之前注册控件。

状态

此行为是设计使然。

此行为是设计使然。

更多信息

复现行为的步骤 生成并注册 Circ3.ocx 示例随 VisualC++ 附带。 运行 JACTIVEX /javatlb circ3.ocx。 这将创建称…

复现行为的步骤

  1. 生成并注册 Circ3.ocx 示例随 VisualC++ 附带。
  2. 运行 JACTIVEX /javatlb circ3.ocx。 这将创建称为 circ3 % windir%\java\trustlib, 将包含相应 coclass, .java 文件对于 CIRC3.OCX 接口下一个文件夹。
  3. 如下创建 Java 应用程序并导入 ActiveX 控件:
    import com.ms.com.*;
    import circ3.*;
    
    public class circle
    {
       public static void main(String args[])
       {
          circle main = new circle();
          main.test();
       }
    }
    
    public void test()
    {
       _DCirc3 c  = null;
       c  = (_DCirc3) new Circ3();
       c.AboutBox();
    }
    
    }
  4. 生成 for Java 2.0 以使用附带 JVC x 或更高 Java 应用程序。 有关如何将此 JVC 与 VisualJ++, 请参阅下面参考部分。
  5. 将看到上面提到的错误消息。

参考

有关如何使用 ActiveX 控件用作自动化服务器, 请参阅下列 Microsoft 知识库文章: 146120  (http://support.micr...

有关如何使用 ActiveX 控件用作自动化服务器, 请参阅下列 Microsoft 知识库文章:

146120 (http://support.microsoft.com/kb/146120/EN-US/ ) HOWTO: 使用 OLE 控件作为自动化服务器

有关如何对 VisualJ++, 使用新 Jvc.exe 请参阅下列 Microsoft 知识库文章:

183712 (http://support.microsoft.com/kb/183712/EN-US/ ) 有关 SDK for Java HOWTO: 安装说明

这篇文章中的信息适用于:
  • Microsoft Java Virtual Machine
  • Microsoft Software Development Kit for Java 2.02
  • Microsoft Software Development Kit for Java 2.01
  • Microsoft Software Development Kit for Java 3.0
  • Microsoft Software Development Kit for Java 3.1
  • Microsoft Software Development Kit for Java 3.2
  • Microsoft Visual J++ 1.0 Standard Edition
  • Microsoft Visual J++ 1.1 Standard Edition
关键字:
kbprb KB189065 KbMtzh kbmt

CodeProject: OPC and .NET with COM Interoperability. Free source code and programming help
http://www.codeproject.com/KB/COM/opcdotnet.aspx

Sample Image

Abstract

In industrial automation, OPC (OLE for Process Control, see www.opcfoundation.org) is the primary COM component interface used to connect devices from different manufactures. The OPC standard is defined at ‘two different layers’ of COM/DCOM. First, as a collection of COM custom interfaces, and secondly as COM-automation compliant components/wrappers. For some reasons and applications, it is preferable to use the custom interface directly.

We will show you in this article how to access OPC servers with custom interfaces and how to write an OPC client in .NET.

The problem

The new Microsoft .NET Framework will provide some interoperability layers and tools to reuse a large part of the existing COM/ActiveX/OCX components, but with some strong limitations. While automation compliant COM objects will be nicely imported (as referenced COM objects inside Visual Studio .NET, or with the TLBIMP tool), pure custom COM interfaces will not work.

To understand the issues with COM custom interfaces and the .NET framework, we must first analyze, why automation components can be used immediately. Visual Studio .NET relies on the information found in a type-library for every imported COM component (e.g. the library generated by MIDL-compiler, named *.TLB). The problem is now, type libraries can only contain automation compliant information. So if we compile a custom-interface IDL file, the generated TLB misses very important type descriptions, especially the method call parameter size (e.g. of arrays). At the time of .NET Beta2, there’s unfortunately no tool (like ‘IDLIMP’) to import custom interface IDL files.

Example: Since the MIDL compiler does not propagate size_is information to the type library, the marshaler doesn’t know an array length and translates int[] to ref int.

One first solution would be to edit the assembly produced by TLBIMP (using ILDASM), and replace ref int with int[], and then compile IL back again with ILASM.

But if we look at some very custom IDL files, we also find methods with parameters like foo( int **arraybyref ) where arrays are passed by reference (caller allocates the memory)! Hand editing IL code won’t work here, there’s no marshaling signature for this. Currently, we must use one of two different workarounds:

  • Write a custom marshaler (e.g. in Managed C++)
  • Or the way we used, write a marshaling helper class (in C#)

The solution, first step

We have to rewrite our custom IDL file in a managed language code, here C#. Note this can be a very time consuming work! every method of all interfaces have to be coded in C#, and the critical (custom) parameters must be declared completely different. We found there’s often no way around the use of the special IntPtr type.

Let’s look at a sample method (from an OPC custom interface IDL):

Collapse
HRESULT AddItems(
  [in]                        DWORD            dwCount,
  [in,  size_is( dwCount)]    OPCITEMDEF     * pItemArray,
  [out, size_is(,dwCount)]    OPCITEMRESULT ** ppAddResults,
  [out, size_is(,dwCount)]    HRESULT       ** ppErrors    );

Redefined in C#, looks now like this:

Collapse
int AddItems(
  [In]       int       dwCount,
  [In]       IntPtr    pItemArray,
  [Out]  out IntPtr    ppAddResults,
  [Out]  out IntPtr    ppErrors );

As you can see, we loose many type information by declaring parameters as IntPtr!

Another point is the default exception mapping of .NET: as custom interface methods return HRESULT values, failed calls will be converted by the .NET marshaller to exceptions of type COMException. Further, some COM methods will also return other success codes besides S_OK, mainly S_FALSE. With the default mapping, this hint return value will be lost.

To bypass exception mapping, declare the interface with special signature attributes. See at the code below for the head of the final interface declaration:

Collapse
  [ComVisible(true), ComImport,
  Guid("39c13a54-011e-11d0-9675-0020afd8adb3"),
  InterfaceType( ComInterfaceType.InterfaceIsIUnknown )]
internal interface IOPCItemMgt
  {
    [PreserveSig]
  int AddItems(
    [In]       int       dwCount,
    [In]       IntPtr    pItemArray,
    [Out]  out IntPtr    ppAddResults,
    [Out]  out IntPtr    ppErrors );
  ...

The solution, second step

To use the interfaces we declared as above, it is recommended to write some wrapper classes. But more important, this wrapper now has to do all the custom marshaling, e.g. for all IntPtr parameters. So the wrapper must reconstruct the information we lost. Managed marshaling code makes use of the framework services provided in the System.Runtime.InteropServices namespace, especially the Marshal class. We found the following methods as useful for this:

AllocCoTaskMem() FreeCoTaskMem() SizeOf() manage COM native memory (as pointed to by IntPtr)
StructureToPtr() PtrToStructure() DestroyStructure() marshaling of simple structures
ReadInt32() WriteInt32() Copy() read/write to native memory (also -Byte/Int16/Int64)
PtrToStringUni() StringToCoTaskMemUni() string marshaling
GetObjectForNativeVariant() GetNativeVariantForObject() conversions between VARIANT and Object
ThrowExceptionForHR() map HRESULT to exception and throw
ReleaseComObject() finally, release COM object

To get the idea, see a simplified excerpt for the sample method AddItems() we declared above – here we allocate native memory and marshal an array of structures into it:

Collapse
...
IntPtr ptrdef = Marshal.AllocCoTaskMem( count * sizedefinition );
int rundef = (int) ptrdef;
for( int i = 0; i < count; i++ )
  {
  Marshal.StructureToPtr( definitions[i], (IntPtr) rundef, false );
  rundef += sizedefinition;
  }

int hresult = itemsinterface.AddItems( count, ptrdef, ... );
  ...
int rundef = (int) ptrdef;
for( int i = 0; i < count; i++ )
  {
  Marshal.DestroyStructure( (IntPtr) rundef, typedefinition );
  rundef += sizedefinition;
  }
Marshal.FreeCoTaskMem( ptrdef );
...

Download

In the download package, you will find the complete interface declarations and a sample client application showing how to use them.

Please note:

  • First read the included whitepaper
  • To run this OPC client, you must have any OPC-DA 2.0 servers installed!

Useful links

OPC, the OPC logo, and OPC Foundation are trademarks of the OPC Foundation. .NET, the .NET logo, and Microsoft .NET are trademarks of the Microsoft Corporation.

Disclaimer

The information in this article & source code are published in accordance with the Beta2 bits of the .NET Framework SDK).

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

VISCOM .NET Team
Location: United States United States

Other popular COM / COM+ articles:

请问,在ASP下如何调用OCX控件,高分求救 Web 开发 / ASP – CSDN社区 community.csdn.net
http://topic.csdn.net/t/20020918/21/1035190.html

请问,在ASP下如何调用OCX控件,高分求救

kaney00(kaney00)2002-09-18 21:52:45 在 Web 开发 / ASP 提问

请问,在ASP下如何调用OCX控件??

顺便问一下,OCX控件的用法之类的,

请大家知道一点就说一点,多谢,

急,

!!!

问题点数:100、回复次数:10Top

Iamfish(呆鱼)回复于 2002-09-18 22:18:37 得分 10

ASP应该不能调用OCX控件。因为OCX控件是可视的!

就是ASP里面不能用MSGBOX一样。

可以调用ActivX   DllTop

kaney00(kaney00)回复于 2002-09-18 22:26:52 得分 0

啊,这样呀,

我对OCX都不熟悉,是一个朋友问我,他上网不方便,

所以,能不能请lamfish讲多一些关于OCX的东西,因为我刚才在网上找了一下,发现讲OCX的不多,

多谢!!!!!!Top

kaney00(kaney00)回复于 2002-09-19 10:36:20 得分 0

天啦,没有人来看看吗,

!!

继续在线等!!Top

xjbx()回复于 2002-09-19 11:06:14 得分 30

可以调用呀!如果你用frontpage来做asp文件!在“插入”菜单里面有添加“active   x”的选项,把你要添加的“ocx”控件添加进去就可以了,不过该控件要注册!Top

BrightEye(男儿当自强,靠别人不如靠自己)回复于 2002-09-19 11:12:51 得分 40

只要符合控件规范就可以调用,如果是COM控件也可以调用.调用控件一般有良种方法:
1.set   myocx=server.createobject(控件名.类名)
2.利用<object></object>标记Top

zqrhero(zqrhero)回复于 2002-09-19 11:36:08 得分 10

<object   classid=”clsid:20DD1B9E-87C4-11D1-8BE3-0000F8754DA1″   height=20   id=dtBegin   style=”left:   100px;   top:   15px”
width=120   class=”selector”   codebase=”../resource/MSCOMCT2.OCX”>
</object>Top

kaney00(kaney00)回复于 2002-09-19 11:41:44 得分 0

继续问,OCX主要是实现什么东西,??

多谢!!Top

BainStudio(胖胖狗)回复于 2002-09-19 17:45:41 得分 0

关注
Top

xjbx()回复于 2002-09-19 18:04:16 得分 0

OCX主要是实现什么东西?

呵呵,这个问题有点让我无从解释了!:)Top

pyyxs(大力神)回复于 2002-09-19 18:55:27 得分 10

同意   :   BrightEye(问个不休)Top

相关问题

谁有asp调用activex函数的代码 Web 开发 / ASP – CSDN社区 community.csdn.net
http://topic.csdn.net/t/20020729/12/907605.html

谁有asp调用activex函数的代码

lsys(游戏人生)2002-07-29 12:06:04 在 Web 开发 / ASP 提问

delhpi的函数代码
function   TFingerPrint.ReLoading(const   PoliceManCode,   PassWd:   WideString;
FingerCharOne,   FingerCharTwo:   PChar):   Smallint;
begin
。。。
end;
delhpi的函数代码
过程
procedure   TFingerPrint.ImportFingerChar(out   FingerChar:   PChar;
out   FingerID:   Smallint);
begin

end;
求教asp如何的调用

问题点数:100、回复次数:8Top

lanying(蓝鹰)(问个不休)回复于 2002-07-29 13:38:29 得分 10

可以写一个com,用asp调用呀
Top

lsys(游戏人生)回复于 2002-07-29 14:23:44 得分 0

我要源码Top

lsys(游戏人生)回复于 2002-08-01 09:17:54 得分 0

上面的问题没有人回答
改问
asp中如何接收activex中onclick的事件Top

yonghengdizhen(等季节一过,繁花就凋落)回复于 2002-08-01 09:28:56 得分 0

asp中不能接收任何COM对象的事件.

ASP可以处理的只有Application和Session对象的四个事件..

而且ASP中不支持GUI界面的COM对象..

如果说有的话,那不是ASP服务端脚本处理的了,那是客户端浏览器的编程了.Top

yonghengdizhen(等季节一过,繁花就凋落)回复于 2002-08-01 09:31:06 得分 40

asp中不能接收任何COM对象的事件.
这个应该纠正一下,   任何COM对象是指外部COM对象.Top

ramboo2002(天涯游子)回复于 2002-08-01 10:44:13 得分 10

我也想问一下:ASP中如何接收activex中onclick的事件?请高手指教……!
Top

Czh_cz(风清云淡)回复于 2002-08-01 11:06:49 得分 40

应该不可以。
要asp接受activex的返回值,可以给你的activex添加属性,然后给它赋值。
asp   中的脚本可以调用该属性。Top

lsys(游戏人生)回复于 2002-08-03 20:54:37 得分 0

还有没有人有高见,我要结贴了Top

相关问题

ASP中是不是不能调用带有界面的ocx控件? Web 开发 / ASP – CSDN社区 community.csdn.net
http://topic.csdn.net/t/20060303/15/4591007.html

ASP中是不是不能调用带有界面的ocx控件?

devonzzb(水瓶座阿斌)2006-03-03 15:41:20 在 Web 开发 / ASP 提问

现有一个带界面的用于处理图片的ocx控件,嵌入客户端的html页面中使用没有问题。现在我想在服务器端处理用户上传的图片,代码如下:
<OBJECT   name=”oImg”   ID=”oImg”   CLASSID=”CLSID:4475B53F-71F8-403E-9BB8-030685E74834″   runat=”server”></OBJECT>
<%
response.write   TypeName(oImg)   &   “<br>”
const   MAX_SMALL_WIDTH   =   180
const   MAX_MID_WIDTH   =   400

‘########   1   ########
oImg.SetImageTemp(   “C:\”   )                     ”   设置处理图片时所需的临时文件夹
‘########   2   ########
oImg.DrawImage(   “c:\pic\test.jpg”   )   ”   读取图片并显示
%>
现在的问题是,每次执行到1的时候就报错:

错误   ’8000ffff’
灾难性故障

注释掉1,就在2处提示“灾难性故障”

所以,现在怀疑是不是由于这个ocx带界面的缘故?

谢谢关注。 问题点数:100、回复次数:2Top

eglic(圪圪) (理由永远是谎言,信仰永远是自慰)回复于 2006-03-03 15:55:23 得分 100

是的Top

lishery(→SuperBigHero)回复于 2006-03-17 15:57:34 得分 0

这就是传说中的刷分?Top

相关问题

regsvr32.exe使用详解_regsvr32反注册使用方法_CodeLife的WebZine
http://hi.baidu.com/appleluree/blog/item/8bdabbdef505635795ee377d.html
查看文章
regsvr32.exe使用详解_regsvr32反注册使用方法
2007-12-13 13:28
From:烮钬の心

regsvr32.exe是32位系统下使用的DLL注册和反注册工具,使用它必须通过命令行的方式使用,格式是:regsvr32 [/u] [/s] [/n] [/i[:cmdline]] DLL文件名

命令可以在“开始→运行”的文本框中,也可以事先在bat批处理文档中编写好命令。未带任何参数是注册DLL文件功能,其它参数对应功能如下:

/u:反注册DLL文件;

/s:安静模式(Silent)执行命令,即在成功注册/反注册DLL文件前提下不显示结果提示框。

/c:控制端口;

/i:在使用/u反注册时调用DllInstall;

/n:不调用DllRegisterServer,必须与/i连用。

单独运行regsvr32.exe程序,可以看到弹出一“No DLL name specified”的错误提示框,并且可以看到参数原英文提示信息

输入DLL文件名时,如果待处理的是非系统文件,必须在文件名前添加文件绝对路径,必须注意的是文件路径不包含中文,否则很可能导致处理失败。如果碰到regsvr32不能正常执行时,很可能系统文件遭到破坏,因为使用regsvr32.exe 时会调用到Kernel32.dll、User32.dll和Ole32.dll三个文件,在DOS模式或其它系统替换正常文件即可解决。

C#调用第三方ocx控件_DSSH117–毛毛的家园
http://hi.baidu.com/dssh/blog/item/25a56d6070d27944eaf8f800.html
C#调用第三方ocx控件
2007-03-11 12:27

1、.net环境在工具箱上点右键,选择自定义工具箱,然后选择你需要的COM或者OCX控件就可以了。

2、在自定义工具箱中加入相应的控件,设置id,在客户端脚本中直接引用它的ID应可以了,ocx不能作为服务器端使用。

3、不能在asp.net服务端调用ocx, 只能是调用标准的com组件;给你的ocx做一个证书, 捆绑成.cab文件,

然后网页中做 object codebase=”./a.cba” ..

4、http://www.oia.com.cn/Web/CSDN/asppost6/web28039.htm

5、我要开发一个ASP.NET的应用程序,开发工具VS.NET 2003。
在应用程序中要使用公司以前开发的COM组件(可视组件),经试验后发现,COM组件能够被加载到工具箱中,从工具箱能够拖到页面上,不幸的是被作为一个对象插入到.aspx中,没有引入到.aspx.cs中。
例如:
<%@    Page    language=”C#”    Codebehind=”WebForm1.aspx.cs”    AutoEventWireup=”false”    Inherits=”WebTestTTF16.WebForm1″    %>
<!DOCTYPE    HTML    PUBLIC    “-//W3C//DTD    HTML    4.0    Transitional//EN”    >
<HTML>
<HEAD>
<title>WebForm1</title>
<meta    content=”Microsoft    Visual    Studio    .NET    7.1″    name=”GENERATOR”>
<meta    content=”C#”    name=”CODE_LANGUAGE”>
<meta    content=”JavaScript”    name=”vs_defaultClientScript”>
<meta    content=”http://schemas.microsoft.com/intellisense/ie5“    name=”vs_targetSchema”>
</HEAD>
<body    MS_POSITIONING=”GridLayout”>
<form    id=”Form1″    method=”post”    runat=”server”>
<FONT    face=”宋体”>
<OBJECT    style=”Z-INDEX:    104;    LEFT:    24px;    POSITION:    absolute;    TOP:    24px”    classid=”clsid:8E27C92B-1264-101C-8A2F-040224009C02″    VIEWASTEXT>
<PARAM    NAME=”_Version”    VALUE=”524288″>
<PARAM    NAME=”_ExtentX”    VALUE=”7620″>
<PARAM    NAME=”_ExtentY”    VALUE=”5080″>
<PARAM    NAME=”_StockProps”    VALUE=”1″>
<PARAM    NAME=”BackColor”    VALUE=”-2147483633″>
<PARAM    NAME=”Year”    VALUE=”2003″>
<PARAM    NAME=”Month”    VALUE=”10″>
<PARAM    NAME=”Day”    VALUE=”29″>
<PARAM    NAME=”DayLength”    VALUE=”1″>
<PARAM    NAME=”MonthLength”    VALUE=”1″>
<PARAM    NAME=”DayFontColor”    VALUE=”0″>
<PARAM    NAME=”FirstDay”    VALUE=”7″>
<PARAM    NAME=”GridCellEffect”    VALUE=”1″>
<PARAM    NAME=”GridFontColor”    VALUE=”10485760″>
<PARAM    NAME=”GridLinesColor”    VALUE=”-2147483632″>
<PARAM    NAME=”ShowDateSelectors”    VALUE=”-1″>
<PARAM    NAME=”ShowDays”    VALUE=”-1″>
<PARAM    NAME=”ShowHorizontalGrid”    VALUE=”-1″>
<PARAM    NAME=”ShowTitle”    VALUE=”-1″>
<PARAM    NAME=”ShowVerticalGrid”    VALUE=”-1″>
<PARAM    NAME=”TitleFontColor”    VALUE=”10485760″>
<PARAM    NAME=”ValueIsNull”    VALUE=”0″>
</OBJECT>
</FONT>
</form>
</body>
</HTML>
用这种方式,在页面上能够看到这个控件,但使用这种方法我不和道如何在aspx.cs文件中去得到它,控制它?
如果我不用从工具箱拖动控件到页面,我在aspx.cs文件中去创建这个控件,在程序中也能正确使用这个控件,但在页面上是看不到控件的。

我想请大虾指点一下,我如何在页面中能看到这个控件,在asp.cs中又能得到它,就象使用其他控件一样使用它?

回答:

1、把这个控件用命令转换成dll文件,然后引入就OK了。
1、使用TlbImp.exe生成控件.dll的.net引用控件TOBJECTLib.dll。
2、将控件TOBJECTLib.dll引用到你的工程中。
然后就可以象在ASP中一样使用。

2、在ASP。NET中使用OCX一般分以下几个步骤:
一.对生成的DLL文件进行注册
在CMD 中 regsvr32 存放DLL文件地址
二.然后在项目中对DLL进行引用,具体的不详细说明。
三.引用之后在控件栏中你将会看到你刚才添加的控件,直接拖到页面上。
四.就是调用控件中的属性、方法、函数。

C#开发Delphi调用的OCX组件

在类型中添加两个方法,就可以避免注册成为component。

[ComRegisterFunction()]

public static void RegisterClass(string key)

{

StringBuilder sb = new StringBuilder(key);

sb.Replace(@”HKEY_CLASSES_ROOT\”, “”);

RegistryKey k = Registry.ClassesRoot.OpenSubKey(sb.ToString(), true);

RegistryKey ctrl = k.CreateSubKey(“Control”);

ctrl.Close();

RegistryKey inprocServer32 = k.OpenSubKey(“InprocServer32″, true);

inprocServer32.SetValue(“CodeBase”, Assembly.GetExecutingAssembly().CodeBase);

inprocServer32.Close();

k.Close();

}

[ComUnregisterFunction()]

public static void UnregisterClass(string key)

{

StringBuilder sb = new StringBuilder(key);

sb.Replace(@”HKEY_CLASSES_ROOT\”, “”);

RegistryKey k = Registry.ClassesRoot.OpenSubKey(sb.ToString(), true);

k.DeleteSubKey(“Control”, false);

RegistryKey inprocServer32 = k.OpenSubKey(“InprocServer32″, true);

k.DeleteSubKey(“CodeBase”, false);

k.Close();

}

在.Net 中枚举COM对象的方法和属性名称 – .NET专区 – 新云网络
http://www.newasp.net/tech/net/12490.html

在.Net 中枚举COM对象的方法和属性名称

减小字体 增大字体 作者:佚名  来源:本站整理  发布时间:2005-6-15 8:32:30
在.Net 中枚举COM对象的方法和属性名称

Author:Zee

恩,以前满世界问过这个问题,没有人理偶的说,还是自己动手搞定比较好。

一般来说,一个COM对象在提供的时候,通常还会提供一个类型库,在其中定义了COM对象的所有方法名称、参数名称、属性名称等等信息。我们要做的就是从类型库中取出这些信息。
当然,某些只供C++程序员使用的COM对象没有类型库,而代之以C++的头文件和/或idl文件,对这种情况,一般没有办法在程序中枚举出对象的方法属性:毕竟去找C++头文件不太现实,何况在非开发环境下,根本就没有头文件的说。
因此,我们将讨论当COM对象存在TypeLib的情况下,枚举方法/属性名称的问题。
从COM对象定位到TypeLib
在一般情况下,COM对象的TypeLib信息存储在注册表中:在HK_CLASSROOT\CLSID\{ClassID}\的注册表项下,有一个名为TypeLib的子项,其中定义了这个COM对象类型库的ID;而在HK_CLASSROOT\TypeLib 注册表项下,列举了系统中所有TypeLib。
看看我们首先要做什么:从ProgID 取得 ClassID,这个工作可以通过调用COM 基础库的 CLSIDFromProgID 函数来完成,在Platform SDK中,该函数的定义如下:

HRESULT CLSIDFromProgID(
  LPCOLESTR lpszProgID,
  LPCLSID pclsid

);

为了在.Net中使用这个函数,我们用DllImport Attribute 把这个函数引入.Net 中:

class UnsafeNativeMethods{
[DllImport("ole32.dll",CharSet=CharSet.Unicode,PreserveSig=false)]
public static extern void CLSIDFromProgID([In,MarshalAs(UnmanagedType.BStr)] string lpszProgID,[Out]out Guid pclsid);
………

然后,我们可以在.Net 中调用这个函数取得ClassID了:

Guid clsid;
UnsafeNativeMethods.CLSIDFromProgID(progID,out clsid);

OK, 升级宝物Class ID 入手,Level Up!Strength + 3, Life + 5,必杀技 dll import 习得。 :) 下一个任务:取得TypeLib。
l         取得TypeLib。
为访问TypeLib,COM 提供了二个接口:ITypeLib 和 ITypeInfo,其中ITypeLib 提供对 TypeLib 的访问,而ITypeInfo 则表示TypeLib中定义的某一项ITypeInfo。
要获得ITypeInfo,COM有二个函数可以做这件事情:LoadTypeLib 和 LoadRegTypeLib。其中 LoadTypeLib 需要 TypeLib 文件的路径作为参数,而LoadRegTypeLib 则根据TypeLib的TypeLib ID和TypeLib的版本号取得 ITypeLib。在这里,我们用LoadRegTypeLib来取得ITypeLib 接口。
先来准备需要的参数:TypeLibID和TypeLib的版本号,这些信息需要从注册表里得到:

RegistryKey regKey = Registry.ClassesRoot;
regKey = regKey.OpenSubKey(“CLSID\\{” + clsid.ToString() + “}\\TypeLib”);
Guid typeLibID = new Guid(regKey.GetValue(“”).ToString());
//Get TypeLib Versions
short iMajorVer,iMinusVer;
regKey = Microsoft.Win32.Registry.ClassesRoot;
regKey = regKey.OpenSubKey(“TypeLib\\{” + typeLibID.ToString() + “}”);
string[] aryTemp = regKey.GetSubKeyNames();
string sVersion = aryTemp[0];
aryTemp = sVersion.Split(‘.’);
iMajorVer = short.Parse(aryTemp[0],System.Globalization.NumberStyles.AllowHexSpecifier);
iMinusVer = short.Parse(aryTemp[1] ,System.Globalization.NumberStyles.AllowHexSpecifier);

这里要注意一点:在注册表里记录的TypeLib版本号是以十六进制格式表示的,运气好的话,你会发现类似”1.a”之类的版本号,所以我们最好把它们看成16进制来转换。
现在可以调用LoadRegTypeLib 了,和CLSIDFromProgID一样,先import进来:
这是LoadRegTypeLib 的函数原型:

HRESULT LoadRegTypeLib( 
  REFGUID  rguid,             
  unsigned short  wVerMajor,  
  unsigned short  wVerMinor,  
  LCID  lcid,                 
  ITypeLib FAR* FAR*  pptlib  

);

恩,在.Net里,这个函数是这个样子的:

[DllImport("oleaut32.dll",CharSet=CharSet.Unicode,PreserveSig=false)]
[LCIDConversion(3)]
public static extern UCOMITypeLib LoadRegTypeLib(ref Guid rguid, [In,MarshalAs(UnmanagedType.U2)]short wVerMajor, [In,MarshalAs(UnmanagedType.U2)]short wVerMinor);

哈,一个小小的技巧:在.Net 的定义里,偶没有定义原型里 lcid 这个参数,这是因为偶应用了LCIDConversionAttribute,这个Attribute 意思就是说这个方法需要一个LCID做参数,参数的位置嘛:[LCIDConversion(3)]——第三个参数。这样在调用这个方法的时候,.Net 的封送拆收器将自动提供 LCID 参数。不错把:)
另外,在.Net Framwork的System.Runtime.InteropServices 命名空间里,定义了一些常用COM Interface的.Net托管定义,虽然不多,但幸运的是我们要用到的ITypeLib和 ITypeInfo这二个接口都有,也就是System.Runtime.InteropService.UCOMITypeLibSystem.Runtime.InteropService.UCOMITypeInfo。这就省下了我们自己定义接口的工作。
好了,接下来的事情狠简单了:

UCOMITypeLib typeLib;
typeLib = UnsafeNativeMethods.LoadRegTypeLib(ref typeLibID,iMajorVer,iMinusVer);

Bingo!Mission Complete!只剩下最后一个任务:定位到对应我们的COM对象的ITypeInfo,并从中取出我们需要的信息。
ITypeInfo接口的GetITypeInfo方法和GetITypeInfoCount方法一起提供了遍历TypeLib中所有ITypeInfo的能力,不过既然我们手上有COM对象的ClassID,利用GetITypeInfoOfGuid 方法就可以获得COM对象的ITypeInfo了。

UCOMITypeInfo ITypeInfo;
typeLib.GetITypeInfoOfGuid(ref clsid,out ITypeInfo);

拿到ITypeInfo之后,首先我们需要看看这个ITypeInfo里有多少方法/属性,这需要我们调用它的GetTypeAttr 方法获得TYPEATTR结构。

TYPEATTR typeattr;
IntPtr p_typeattr = IntPtr.Zero;
ITypeInfo.GetTypeAttr(out p_typeattr);
typeattr = (TYPEATTR)Marshal.PtrToStructure(p_typeattr,typeof(TYPEATTR));

获得TYPEATTR结构有那么一点点麻烦,因为 .Net的不支持非托管签名的 TYPEATTR** 参数,所以只有使用引用 IntPtr 参数定义 GetTypeAttr。然后我们需要用Marshal.PtrToStructure将数据从非托管内存块封送到托管对象。在TYPEATTR结构中,cFuns字段表示当前TrpeInfo描述的函数数目,而每个函数的描述则是通过ITypeInfo的GetFuncDesc方法取得的FUNCDESC结构描述的。

if(typeattr.cFuncs > 0)
{
for(int i=0;i<typeattr.cFuncs;++i)
{
//Get FUNCDESC struct
FUNCDESC funcdesc;
IntPtr p_funcDesc;
ITypeInfo.GetFuncDesc(i,out p_funcDesc);
funcdesc = (FUNCDESC)Marshal.PtrToStructure(p_funcDesc,typeof(FUNCDESC));
……

和TYPEATTR一样,FUNCDESC结构也需要Marshal.PtrToStructure处理一下,偶就不多说了。
讨厌的是:FUNCDESC结构里并没有函数的名称,我们只能通过它的memid字段和invkind字段知道这个函数的成员ID和函数的类型。函数的类型是我们需要的:它告诉我们这个函数是一个方法或者是一个属性的Get/Set方法,而名称这个东西,我们还得求助于ITypeInfo:GetNames 方法获取具有指定成员ID的成员名称,它的返回一个string数组,对方法而言,这个数组第一个元素是方法名称,后面的元素则是方法的参数名,而对属性而言,属性名称也出现在数组的第一个元素。
好了,现在除了一件事情,该说的偶已经都说了,我们已经知道了如何从ITypeInfo获得方法/属性的名称,至于如何如何先建立二个空的Collection分别用于存放方法和属性名称;如何如何遍历ITypeInfo的所有FuncDesc,根据每个不同的函数类型向对应的Collection中插入元素,这些简单操作偶就不想多说了。我们剩下的唯一的问题是:对所有VB生成的COM对象,按照上面的步骤走下来,我们什么方法属性也看不到。
原因嘛,用OleView看一下这些dll的TypeLib就明白了:VB会生成一个名为”_”+类名的类接口,这个接口继承自IDispatch,所有的方法属性都在这个接口上定义,而实现类只是简单的实现这个接口,在它的TypeLib里,真正对应ClassID的实现类里没有任何成员。
既然知道了原因,办法也就有了:我们在枚举一个COM对象的所有方法/属性时,不应该只枚举仅对应它自己ClassID的TypeInfo,这个TypeInfo继承的所有其他接口中定义的方法/属性也要照样拿出来,而要定位到它继承的其他接口,我们要做的事情其实和遍历这个ITypeInfo的所有FUNCDESC差不多:

if(typeattr.cImplTypes > 0)
{
for(int i=0;i<typeattr.cImplTypes;++i)
{
int href;
UCOMITypeInfo imptypeinfo;
typeinfo.GetRefTypeOfImplType(i,out href);
typeinfo.GetRefTypeInfo(href,out imptypeinfo);
//Now we can do the same thing to the imptypeinfo like typeinfo
……
}
}

TYPEATTR的cImplTypes字段表示这个ItypeInfo实现的接口数目,ITypeInfo的GetRefTypeOfImplType 方法获取对某个已实现接口的句柄的引用,而GetRefTypeInfo 方法从这个句柄的引用获取该接口的ITypeInfo。很明显,我们可以写一个递归函数来走遍所有COM对象实现的接口,而且我们可以确信这个递归是有出口的:因为COM里所有的接口归根到底都派生自“我不知道”接口 。^-^
最后,我想在大多数情况下,你不会希望在COM对象的方法列表里看到QueryInterface或者AddRef这类IUnknown接口的方法,而IDispatch接口那些类似Invoke之类的方法想来有兴趣的人也不多,不过反正这种底层方法就那么几个,在你遍历的时候尽可以判断一下过滤掉这些方法名称。

免责声明:
在本文中,为了清晰起见,所有给出的代码中都没有错误处理。如果你在你的代码中使用本文中的部分代码,由此造成的诸如程序出错、系统宕机、走路撞树、手机爆炸、洪水毁堤、地球毁灭等等一切后果,本人概不负责

CodeProject: Calling Managed .NET C# COM Objects from Unmanaged C++ Code. Free source code and
http://www.codeproject.com/KB/cs/ManagedCOM.aspx

Preface

COM Interoperability is the feature of Microsoft .NET that allows managed .NET code to interact with unmanaged code using Microsoft’s Component Object Model semantics.

This article is geared towards C# programmers who are familiar with developing COM components and familiar with the concept of an interface. I’ll review some background on COM, explain how C# interacts with COM, and then show how to design .NET components to smoothly interact with COM.

For those die-hard COM experts, there will be some things in this article that are oversimplified, but the concepts, as presented, are the important points to know for those developers supplementing their COM code with .NET components.

Introduction

.NET Interfaces and Classes

The basis for accessing .NET objects either from other .NET code or from unmanaged code is the Class. A .NET class represents the encapsulation of the functionality (methods and properties) that the programmer wants to expose to other code. A .NET interface is the abstract declaration of the methods and properties that classes which implement the interface are expected to provide in their implementations. Declaring a .NET interface doesn’t generate any code, and a .NET interface is not callable directly. But any class which implements (“inherits”) the interface must provide the code that implements each of the methods and properties declared in the interface definition.

Microsoft realized that the very first version of .NET needed a way to work with the existing Windows technology used to develop applications over the past 8+ years: COM. With that in mind, Microsoft added support in the .NET runtime for interoperating with COM – simply called “COM Interop”. The support goes both ways: .NET code can call COM components, and COM code can call .NET components.

Using the code

Steps to create a Managed .NET C# COM Object:

  1. Open VS.NET2003->New Project->Visual C# Projects->Class Library.
  2. Project name: MyInterop.
  3. Create MyDoNetClass.cs file, and add the following lines of code:
    Collapse
    using System.Runtime.InteropServices;
    using System.Windows.Forms;
  4. Create an Interface IMyDotNetInterface.
  5. Create a class MyDoNetClass.
  6. Add the following line for MyDotNetClass:
    Collapse
    [ClassInterface(ClassInterfaceType.None)]

Although a .NET class is not directly invokable from unmanaged code, Microsoft has provided the capability of wrapping a .NET interface in an unmanaged layer of code that exposes the methods and properties of the .NET class as if the class were a COM object. There are two requirements for making a .NET class visible to unmanaged code as a COM object:

Requirement 1:

You have to add GUIDs – Globally Unique Identifiers – into your code for the interface and the class separately, through a GUID tool.

  1. Now, create a GUID for the Interface, and add the following line for the interface:
    Collapse
    [Guid("03AD5D2D-2AFD-439f-8713-A4EC0705B4D9")]
  2. Now, create a GUID for the class, and add the following line for the class:
    Collapse
    [Guid("0490E147-F2D2-4909-A4B8-3533D2F264D0")]
  3. Your code will look like:
    Collapse
    using System;
    using System.Runtime.InteropServices;
    using System.Windows.Forms;
    
    namespace MyInterop
    {
        [Guid("03AD5D2D-2AFD-439f-8713-A4EC0705B4D9")]
        interface IMyDotNetInterface
        {
            void ShowCOMDialog();
        }
    
        [ClassInterface(ClassInterfaceType.None)]
        [Guid("0490E147-F2D2-4909-A4B8-3533D2F264D0")]
        class MyDotNetClass : IMyDotNetInterface
        {
            // Need a public default constructor for COM Interop.
    
            public MyDotNetClass()
            {}
            public void ShowCOMDialog()
            {
                System.Windows.Forms.MessageBox.Show(“I am a" +
                      "  Managed DotNET C# COM Object Dialog”);
            }
        }
    }
  4. Compile the solution.
  5. You will see inside the project directory->obj->debug directory, the file “MyInterop.dll” generated after compilation.

Requirement 2:

Registration of the COM Class and Interfaces

For a COM class to be accessible by the client at runtime, the COM infrastructure must know how to locate the code that implements the COM class. COM doesn’t know about .NET classes, but .NET provides a general “surrogate” DLL – mscoree.dll — which acts as the wrapper and intermediary between the COM client and the .NET class.

  1. Hard-code a specific version number in your AssemblyVersion attribute in the AssemblyInfo.cs file which is in your project.Example:
    Collapse
    [assembly: AssemblyVersion("1.0.0.0")]
  2. Create a strong-name key pair for your assembly and point to it via the AssemblyKeyFile attribute in the AssemblyInfo.cs file which is in your project. Example:
    Collapse
    sn -k TestKeyPair.snk
    Collapse
    [assembly: AssemblyKeyFile("TestKeyPair.snk")]
  3. Add your assembly to the GAC using the following command:
    Collapse
    gacutil /i MyInterop.dll
  4. Register your assembly for COM by using the REGASM command along with the “/tlb” option to generate a COM type library.
    Collapse
    REGASM MyInterop.dll /tlb:com.MyInterop.tlb
  5. Close the C# project.

Steps to create an Unmanaged C++ application to call a .NET Managed C# COM

  1. Open VS.NET2003->New Project->Visual C++ Projects->Win32->Win32 Console Project.
  2. Name: DotNet_COM_Call.
  3. Include the following line in your DoNet_COM_Call.cpp file:
    Collapse
    #import<Full Path>\com.MyInterop.tlb" named_guids raw_interfaces_only
  4. Compile the solution.
  5. It will generate a “com.myinterop.tlh” file into your project->debug directory.
  6. You can open this file and see the contents. This is basically the proxy code of the C# COM code.
  7. Now, you can write the code to call the .NET Managed COM.
  8. Please add the following lines of code before calling the COM exported functions:
    Collapse
    CoInitialize(NULL);   //Initialize all COM Components
    
    
    // <namespace>::<InterfaceName>
    
    MyInterop::IMyDotNetInterfacePtr pDotNetCOMPtr;
    
    // CreateInstance parameters
    
    // e.g. CreateInstance (<namespace::CLSID_<ClassName>)
    
    HRESULT hRes =
      pDotNetCOMPtr.CreateInstance(MyInterop::CLSID_MyDotNetClass);
    if (hRes == S_OK)
    {
        BSTR str;
        pDotNetCOMPtr->ShowCOMDialog ();
        //call .NET COM exported function ShowDialog ()
    
    }
    
    CoUninitialize ();   //DeInitialize all COM Components
  9. Run this console application.
  10. Expected result: a managed code (C# ) dialog should appear with the string “I am a Managed DotNET C# COM Object Dialog”.

Points of Interest

While creating an Interface for COM exported functions, creating GUIDs for the Interface and the class and registering the class are required steps, and doing all this is always interesting and fun. Calling parameterized exported functions also is very interesting.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

Atul Mani Atul Mani Tripathi has been a professional developer since 1997 and working as a senior consultant with Cognizant Technology Solutions (CTS) .

Most of the time, his focus is on creating a clean and simple solution to development problems. He is a C++, Visual C++ and C# (Dot NET) guy who loves Visual Studio, Google, Code Project and developing personal projects.

Occupation: Web Developer
Location: United States United States

Other popular C# articles:

请问.odl和.idl有什么区别啊? VC/MFC / ATL/ActiveX/COM – CSDN社区 community.csdn.net
http://topic.csdn.net/t/20040427/17/3014084.html

请问.odl和.idl有什么区别啊?

cctime()2004-04-27 17:25:36 在 VC/MFC / ATL/ActiveX/COM 提问

as   title 问题点数:0、回复次数:5Top

Onega(www.fruitfruit.com回复于 2004-04-27 17:48:32 得分 0

When   using   IDL,   you   must   declare   the   interfaces   that   will   generate   the   C++   source   files   outside   of   the   library   declaration.   For   the   ODL,   this   step   is   not   necessary.   Other   than   a   few   minor   language   differences,   the   IDL   and   ODL   are   identical   in   terms   of   syntax   and   organization.
Top

yaoyuhang(tom)回复于 2004-04-27 21:14:33 得分 0

.odl和.idl在com中的功能相同.
前者是ActiveX中的   后者是ATL中.

可在前者的文件中   用   #import   “XXXXX.idl”   的方式包含后者.   反过来没有试过.

Top

ygang76(小草)回复于 2004-04-27 22:15:27 得分 0

ODL是Microsoft对IDL的扩展Top

ddkc_c(ddkc_c)回复于 2004-04-28 08:47:19 得分 0

odl已经过时了,现在是idl的天下
现在使用idl就行了Top

wangweixing2000(星(inspiration(灵感)))回复于 2004-04-28 09:18:08 得分 0

odl   —对象描述语言
idl   —接口描述语言
其实是一个功能就是在写法上有些不同,eg:
odl:
[
uuid(3C591B20-1F13-101B-B826-00DD01103DE1),             //   LIBID_Lines.
helpstring("Lines   1.0   Type   Library"),
lcid(0x09),
version(1.0)
]
library   Lines
{
importlib(“stdole.tlb”);

[
uuid(3C591B25-1F13-101B-B826-00DD01103DE1),     //   IID_Ipoint.
helpstring("Point   object."),
oleautomation,
dual
]
interface   IPoint   :   IDispatch
{
[propget,   helpstring("Returns   and   sets   x   coordinate.")]
HRESULT   x([out,   retval]   int*   retval);
[propput,   helpstring("Returns   and   sets   x   coordinate.")]
HRESULT   x([in]   int   Value);

[propget,   helpstring("Returns   and   sets   y   coordinate.")]
HRESULT   y([out,   retval]   int*   retval);
[propput,   helpstring("Returns   and   sets   y   coordinate.")]
HRESULT   y([in]   int   Value);
}
[
uuid(3C591B21-1F13-101B-B826-00DD01103DE1),       //   CLSID_Lines.
helpstring("Lines   Class"),
appobject
]
coclass   Lines
{
[default]   interface   IPoint;
interface   IDispatch;
}
}
idl:

[
uuid(3C591B25-1F13-101B-B826-00DD01103DE1),     //   IID_Ipoint.
helpstring("Point   object."),
oleautomation,
dual
]
interface   IPoint   :   IDispatch
{
[propget,   helpstring("Returns   and   sets   x   coordinate.")]
HRESULT   x([out,   retval]   int*   retval);
[propput,   helpstring("Returns   and   sets   x   coordinate.")]
HRESULT   x([in]   int   Value);

[propget,   helpstring("Returns   and   sets   y   coordinate.")]
HRESULT   y([out,   retval]   int*   retval);
[propput,   helpstring("Returns   and   sets   y   coordinate.")]
HRESULT   y([in]   int   Value);
}
[
uuid(3C591B20-1F13-101B-B826-00DD01103DE1),             //   LIBID_Lines.
helpstring("Lines   1.0   Type   Library"),
lcid(0x09),
version(1.0)
]
library   Lines
{
importlib(“stdole32.tlb”);
importlib(“stdole2.tlb”);

[
uuid(3C591B21-1F13-101B-B826-00DD01103DE1),       //   CLSID_Lines.
helpstring("Lines   Class"),
appobject
]
coclass   Lines
{
[default]   interface   IPoint;
interface   IDispatch;
}
}Top

相关问题

WideCharToMultiByte
2008年08月19日 星期二 11:42
仔细做好串处理
这一部分将花点时间来讨论如何在COM代码中处理串。如果你熟悉Unicode 和ANSI,并知道如何对它们进行转换的话,你就可以跳过这一部分,否则还是读一下这一部分的内容。
不管什么时候,只要COM方法返回一个串,这个串都是Unicode串(这里指的是写入COM规范的所有方法)。Unicode是一种字符编码集,类似ASCII,但用两个字节表示一个字符。如果你想更好地控制或操作串的话,应该将它转换成TCHAR类型串。
TCHAR和以_t开头的函数(如_tcscpy())被设计用来让你用相同的源代码处理Unicode和ANSI串。在大多数情况下编写的代码都是用来处理ANSI串和ANSI WindowsAPIs,所以在下文中,除非另外说明,我所说的字符/串都是指TCHAR类型。你应该熟练掌握TCHAR类型,尤其是当你阅读其他人写的有关代码时,要特别注意TCHAR类型。
当你从某个COM方法返回得到一个Unicode串时,可以用下列几种方法之一将它转换成char类型串:

1、调用 WideCharToMultiByte() API。
2、调用CRT 函数wcstombs()。
3、使用CString 构造器或赋值操作(仅用于MFC )。
4、使用ATL 串转换宏。

WideCharToMultiByte()
你可以用WideCharToMultiByte()将一个Unicode串转换成一个ANSI串。此函数的原型如下:
int WideCharToMultiByte (
    UINT    CodePage,
    DWORD   dwFlags,
    LPCWSTR lpWideCharStr,
    int     cchWideChar,
    LPSTR   lpMultiByteStr,
    int     cbMultiByte,
    LPCSTR  lpDefaultChar,
    LPBOOL  lpUsedDefaultChar );
以下是参数解释:
CodePage
Unicode字符转换成的代码页。你可以传递CP_ACP来使用当前的ANSI代码页。代码页是256个字符集。字符0——127与ANSI编码一样。字符128——255与ANSI字符不同,它可以包含图形字符或者读音符号。每一种语言或地区都有其自己的代码页,所以使用正确的代码页对于正确地显示重音字符很重要。
dwFlags
dwFlags 确定Windows如何处理“复合” Unicode字符,它是一种后面带读音符号的字符。如è就是一个复合字符。如果这些字符在CodePage参数指定的代码页中,不会出什么事。否则,Windows必须对之进行转换。
传递WC_COMPOSITECHECK使得这个API检查非映射复合字符。
传递WC_SEPCHARS使得Windows将字符分为两段,即字符加读音,如e`。
传递WC_DISCARDNS使得Windows丢弃读音符号。
传递WC_DEFAULTCHAR使得Windows用lpDefaultChar参数中说明的缺省字符替代复合字符。
缺省行为是WC_SEPCHARS。
lpWideCharStr
要转换的Unicode串。
cchWideChar
lpWideCharStr在Unicode 字符中的长度。通常传递-1,表示这个串是以0×00结尾。
lpMultiByteStr
接受转换的串的字符缓冲
cbMultiByte
lpMultiByteStr的字节大小。
lpDefaultChar
可选——当dwFlags包含WC_COMPOSITECHECK | WC_DEFAULTCHAR并且某个Unicode字符不能被映射到同等的ANSI串时所传递的一个单字符ANSI串,包含被插入的“缺省”字符。可以传递NULL,让API使用系统缺省字符(一种写法是一个问号)。
lpUsedDefaultChar
可选——指向BOOL类型的一个指针,设置它来表示是否缺省字符曾被插入ANSI串。可以传递NULL来忽略这个参数。
我自己都有点晕菜了……!,万事开头难啊……,不搞清楚这些东西就很难搞清楚COM的串处理。何况文档中列出的比实际应用的要复杂得多。下面就给出了如何使用这个API的例子:
// 假设已经有了一个Unicode 串 wszSomeString...
char szANSIString [MAX_PATH];

    WideCharToMultiByte ( CP_ACP,                // ANSI 代码页
                          WC_COMPOSITECHECK, // 检查重音字符
                          wszSomeString,         // 原Unicode 串
                          -1,                    // -1 意思是串以0x00结尾
                          szANSIString,          // 目的char字符串
                          sizeof(szANSIString),  // 缓冲大小
                          NULL,                  // 肥缺省字符串
                          NULL );                // 忽略这个参数
调用这个函数后,szANSIString将包含Unicode串的ANSI版本。
wcstombs()
这个CRT函数wcstombs()是个简化版,但它终结了WideCharToMultiByte()的调用,所以最终结果是一样的。其原型如下:
size_t wcstombs (
    char*          mbstr,
    const wchar_t* wcstr,
    size_t         count );
以下是参数解释:
mbstr
接受结果ANSI串的字符(char)缓冲。
wcstr
要转换的Unicode串。
count
mbstr参数所指的缓冲大小。

wcstombs()在它对WideCharToMultiByte()的调用中使用WC_COMPOSITECHECK | WC_SEPCHARS标志。用wcstombs()转换前面例子中的Unicode串,结果一样:

wcstombs ( szANSIString, wszSomeString, sizeof(szANSIString) );

CString
MFC中的CString包含有构造函数和接受Unicode串的赋值操作,所以你可以用CString来实现转换。例如:

// 假设有一个Unicode串wszSomeString…

CString str1 ( wszSomeString ); // 用构造器转换
CString str2;

str2 = wszSomeString; // 用赋值操作转换

ATL宏
ATL有一组很方便的宏用于串的转换。W2A()用于将Unicode串转换为ANSI串(记忆方法是“wide to ANSI”——宽字符到ANSI)。实际上使用OLE2A()更精确,“OLE”表示的意思是COM串或者OLE串。下面是使用这些宏的例子:

#include <atlconv.h>

// 还是假设有一个Unicode串wszSomeString…

{
char szANSIString [MAX_PATH];
USES_CONVERSION; // 声明这个宏要使用的局部变量

lstrcpy ( szANSIString, OLE2A(wszSomeString) );
}

OLE2A()宏“返回”转换的串的指针,但转换的串被存储在某个临时栈变量中,所以要用lstrcpy()来获得自己的拷贝。其它的几个宏是W2T()(Unicode 到 TCHAR)以及W2CT()(Unicode到常量TCHAR串)。
有个宏是OLE2CA()(Unicode到常量char串),可以被用到上面的例子中,OLE2CA()实际上是个更正宏,因为lstrcpy()的第二个参数是一个常量char*,关于这个问题本文将在以后作详细讨论。
另一方面,如果你不想做以上复杂的串处理,尽管让它还保持为Unicode串,如果编写的是控制台应用程序,输出/显示Unicode串时应该用全程变量std::wcout,如:

wcout << wszSomeString;

但是要记住,std::wcout只认Unicode,所以你要是“正常”串的话,还得用std::cout输出/显示。对于Unicode串文字量,要使用前缀L标示,如:

wcout << L”The Oracle says…” << endl << wszOracleResponse;

如果保持串为Unicode,编程时有两个限制:

—— 必须使用wcsXXX() Unicode串处理函数,如wcslen()。
—— 在Windows 9x环境中不能在Windows API中传递Unicode串。要想编写能在9x和NT上都能运行的应用,必须使用TCHAR类型,详情请参考MSDN。

用例子代码总结上述内容
下面用两个例子演示本文所讲的COM概念。代码中还包含了本文的例子工程。
使用单接口COM对象
第一个例子展示的是单接口COM对象。这可能是你碰到得最简单的例子。它使用外壳中的活动桌面组件对象类(CLSID_ActiveDesktop)来获得当前桌面墙纸的文件名。请确认系统中安装了活动桌面(Active Desktop)。

以下是编程步骤:

初始化COM库。 (Initialize)
创建一个与活动桌面交互的COM对象,并取得IActiveDesktop接口。
调用COM对象的GetWallpaper()方法。
如果GetWallpaper()成功,则输出/显示墙纸文件名。
释放接口(Release())。
收回COM库(Uninitialize)。

WCHAR   wszWallpaper [MAX_PATH];
CString strPath;
HRESULT hr;
IActiveDesktop* pIAD;

    // 1. 初始化COM库(让Windows加载DLLs)。通常是在程序的InitInstance()中调用
    // CoInitialize ( NULL )或其它启动代码。MFC程序使用AfxOleInit()。

    CoInitialize ( NULL );

    // 2. 使用外壳提供的活动桌面组件对象类创建COM对象。
    // 第四个参数通知COM需要什么接口(这里是IActiveDesktop).

    hr = CoCreateInstance ( CLSID_ActiveDesktop,
                            NULL,
                            CLSCTX_INPROC_SERVER,
                            IID_IActiveDesktop,
                            (void**) &pIAD );

    if ( SUCCEEDED(hr) )
        {
        // 3. 如果COM对象被创建成功,则调用这个对象的GetWallpaper() 方法。
        hr = pIAD->GetWallpaper ( wszWallpaper, MAX_PATH, 0 );

        if ( SUCCEEDED(hr) )
            {
            // 4. 如果 GetWallpaper() 成功,则输出它返回的文件名字。
            // 注意这里使用wcout 来显示Unicode 串wszWallpaper.  wcout 是
            // Unicode 专用,功能与cout.相同。
            wcout << L"Wallpaper path is:\n    " << wszWallpaper << endl << endl;
            }
        else
            {
            cout << _T("GetWallpaper() failed.") << endl << endl;
            }

        // 5. 释放接口。
        pIAD->Release();
        }
    else
        {
        cout << _T("CoCreateInstance() failed.") << endl << endl;
        }

    // 6. 收回COM库。MFC 程序不用这一步,它自动完成。
CoUninitialize();
在这个例子中,输出/显示Unicode 串 wszWallpaper用的是std::wcout。
标签: COM字符串

VC知识库文章 – COM 组件设计与应用(四)——简单调用组件
http://www.vckbase.com/document/viewdoc/?id=1493
::首页 >> 文档中心 >> 在线杂志 >> COM技术(COM/DCOM/COM+) [ 在线杂志 第43期 ]
[ 原创文档 本文适合中级读者 已阅读47452次 ] 文档 代码 工具

COM组件设计与应用(四)
简单调用组件

作者:杨老师

一、前言

同志们、朋友们、各位领导,大家好。

VCKBASE 不得了,
网友众多文章好。
组件设计怎么学?
知识库里闷头找!
摘自—杨老师打油集录

VCKBASE 的顶力支持下,在各位网友回帖的鼓励下,我才能顺利完成系列论文的前三回。书到本回,我们终于开始写代码啦。写点啥那?恩,有了!咱们先从如何调用现成的简单的组件开始吧,同时也顺便介绍一些相关的知识。


二、组件的启动和释放

第三回中,大家用“小本本”记录了一个原则:COM 组件是运行在分布式环境中的 。于是,如何启动组件立刻就遇到了严重的问题,大家看这段代码:

      p = new 对象;
      p->对象函数();
      delete p;

这样的代码再熟悉不过了,在本地进程中运行是不会有问题的。但是你想想,如果这个对象是在“地球另一边”的计算机上,结果会如何?嘿嘿,C++ 在设计 new 的时候,可没有考虑远程的实现呀(计算机语言当然不会,也没必要去设计)。因此启动组件、调用接口的功能,当然就由 COM 系统来实现了。


图一 组件调用机制

由上图可以看出,当调用组件的时候,其实是依靠代理(运行在本地)和存根(运行在远端)之间的通讯完成的。具体来说,当客户程序通过 CoCreateInstance() 函数启动组件,则代理接管该调用,它和存根通讯,存根则它所在的本地(相对于客户程序来说就是远程了)执行 new 操作加载对象。对于初学者,你可以不用理它,代理和存根对我们来说是透明的。只要大约知道是怎么一回事就一切OK了。
问题又来了,这个远程的对象什么时候消灭呢?在第二回介绍接口概念的时候,当时我们特意忽略了两个函数,就是IUnknown::AddRef()和IUnknown::Release(),从函数名就能猜到了,一个是对内部引用记数器(Ref)加1,一个是释放(减1),当记数器减为0的时候,就是释放的机会啦。看起来很复杂,没办法,因为这是在介绍原理。其实在我们写程序的时候到比较简单,请大家遵守几个原则:
1、启动组件得到一个接口指针(Interface)后,不要调用AddRef()。因为系统知道你得到了一个指针,所以它已经帮你调用了AddRef()函数;
2、通过QueryInterface()得到另一个接口指针后,不要调用AddRef()。因为……和上面的道理一样;
3、当你把接口指针赋值给(保存到)另一个变量中的时候,请调用AddRef();
4、当不需要再使用接口指针的时候,务必执行Release()释放;
5、当使用智能指针的时候,可以省略指针的维护工作;(注1)


三、内存分配和释放

自从学习了C语言,老师就教导我们说:对于动态内存的申请和释放,一定要遵守“谁申请,谁释放”的原则。在此原则的指导下,不仅是我、不仅是你,就连特级大师都设计了这样怪怪的函数:

函数 说明 评论
GetWindowText(HWND,LPTSTR,int) 取得窗口标题。需要在参数中给出保存标题所使用的内存指针,和这块内存的尺寸。 晕!我又不知道窗口标题的长度,居然还要我提供尺寸?!没办法,只能估摸着给一个大一些的尺寸吧。
sprintf(char *,const char *,…) 格式化一个字符串。这个函数不用给出缓冲区的长度啦。 恩,虽然不用给出长度了,但你敢给个小尺寸吗?哼!
int CListBox::GetTextLen(int)
CListBox::GetText(int,LPTSTR)
取得列表窗中子项目的标题。需要调用两个函数,先取得长度,然后分配内存,再实际取得标题内容。 真烦!

说实在的,不但函数调用者感觉别扭,就连函数设计者心情也不会爽的,而这一切都是为了满足所谓“谁申请,谁释放”的原则。 解决这个问题最好的方式就是:函数内部根据实际需要动态申请内存,而调用者负责释放。这虽然违背了上述原则,但 COM 从方便性和效率出发,确实是这么设计的。

C语言 C++语言 Windows 平台 COM IMalloc 接口 BSTR
申请 malloc() new GlobalAlloc() CoTaskMemAlloc() Alloc() SysAllocString()
重新申请 realloc() GlobalReAlloc() CoTaskRealloc() Realloc() SysReAllocString()
释放 free() delete GlobalFree() CoTaskMemFree() Free() SysFreeString()

以上这些函数必须要按类型配合使用(比如:new 申请的内存,则必须用 delete 释放)。在 COM 内部,当然你可以随便使用任何类型的内存分配释放函数,但组件如果需要与客户进行内存的交互,则必须使用上表中的后三类函数族。
1、BSTR 内存在上回书中,已经有比较丰富的介绍了,不再重复;
2、CoTaskXXX()函数族,其本质上就是调用C语言的函数(malloc…);
3、IMalloc 接口又是对 CoTaskXXX() 函数族的一个包装。包装后,同时增强了一些功能,比如:IMalloc::GetSize()可以取得尺寸,使用 IMallocSpy 可以监视内存的使用;

四、参数传递方向

在C语言的函数声明中,尤其当参数为指针的时候,你是看不出它传递方向的。比如:
void fun(char * p1, int * p2); 请问,p1、p2 哪个是入参?哪个是出参?甚或都是入参或都是出参?由于牵扯到内存分配和释放等问题,COM 需要明确标注参数方向。以后我们写程序,就类似下面的样子:

      HRESULT Add([in] long n1, [in] long n2, [out] long *pnSum);  // IDL文件(注2)
      STDMETHOD(Add)(/*[in]*/ long n1, /*[in]*/ long n2, /*[out]*/ long *pnSum);  // .h文件

如果参数是动态分配的内存指针,那么遵守如下的规定:

方向 申请人 释放人 提示
[in] 调用者 调用者 组件接收指针后,不能重新分配内存
[out] 组件 调用者 组件返回指针后,调用者“爱咋咋地”(注3)
[in,out] 调用者 调用者 组件可以重新分配内存

五、示例程序

示例一、由 CLSID 得到 ProgID。(程序以 word 为例子。如果运行不正确,嘿嘿,你没有安装 word 吧?)

	::CoInitialize( NULL );

	HRESULT hr;
	// {000209FF-0000-0000-C000-000000000046} = word.application.9
	CLSID clsid = {0x209ff,0,0,{0xc0,0,0,0,0,0,0,0x46}};
	LPOLESTR lpwProgID = NULL;

	hr = ::ProgIDFromCLSID( clsid, &lpwProgID );
	if ( SUCCEEDED(hr) )
	{
		::MessageBoxW( NULL, lpwProgID, L"ProgID", MB_OK );

		IMalloc * pMalloc = NULL;
		hr = ::CoGetMalloc( 1, &pMalloc );  // 取得 IMalloc
		if ( SUCCEEDED(hr) )
		{
			pMalloc->Free( lpwProgID );  // 释放ProgID内存
			pMalloc->Release();          // 释放IMalloc
		}
	}

	::CoUninitialize();

示例二、如何使用“浏览文件夹”选择对话窗。

CString BrowseFolder(HWND hWnd, LPCTSTR lpTitle)
{
    // 调用 SHBrowseForFolder 取得目录(文件夹)名称
    // 参数 hWnd: 父窗口句柄
    // 参数 lpTitle: 窗口标题

    char szPath[MAX_PATH]={0};
    BROWSEINFO m_bi;

    m_bi.ulFlags = BIF_RETURNONLYFSDIRS  | BIF_STATUSTEXT;
    m_bi.hwndOwner = hWnd;
    m_bi.pidlRoot = NULL;
    m_bi.lpszTitle = lpTitle;
    m_bi.lpfn = NULL;
    m_bi.lParam = NULL;
    m_bi.pszDisplayName = szPath;

    LPITEMIDLIST pidl = ::SHBrowseForFolder( &m_bi );
    if ( pidl )
    {
        if( !::SHGetPathFromIDList ( pidl, szPath ) )  szPath[0]=0;

        IMalloc * pMalloc = NULL;
        if ( SUCCEEDED ( ::SHGetMalloc( &pMalloc ) ) )  // 取得IMalloc分配器接口
        {
            pMalloc->Free( pidl );    // 释放内存
            pMalloc->Release();       // 释放接口
        }
    }
    return szPath;
}

示例三、在窗口中显示一幅 JPG 图象。

void CxxxView::OnDraw(CDC* pDC)
{
	::CoInitialize(NULL);  // COM 初始化
	HRESULT hr;
	CFile file;

	file.Open( "c:\\aa.jpg", CFile::modeRead | CFile::shareDenyNone );  // 读入文件内容
	DWORD dwSize = file.GetLength();
	HGLOBAL hMem = ::GlobalAlloc( GMEM_MOVEABLE, dwSize );
	LPVOID lpBuf = ::GlobalLock( hMem );
	file.ReadHuge( lpBuf, dwSize );
	file.Close();
	::GlobalUnlock( hMem );

	IStream * pStream = NULL;
	IPicture * pPicture = NULL;

	// 由 HGLOBAL 得到 IStream,参数 TRUE 表示释放 IStream 的同时,释放内存
	hr = ::CreateStreamOnHGlobal( hMem, TRUE, &pStream );
	ASSERT ( SUCCEEDED(hr) );

	hr = ::OleLoadPicture( pStream, dwSize, TRUE, IID_IPicture, ( LPVOID * )&pPicture );
	ASSERT(hr==S_OK);

	long nWidth,nHeight;  // 宽高,MM_HIMETRIC 模式,单位是0.01毫米
	pPicture->get_Width( &nWidth );    // 宽
	pPicture->get_Height( &nHeight );  // 高

	////////原大显示//////
	CSize sz( nWidth, nHeight );
	pDC->HIMETRICtoDP( &sz );  // 转换 MM_HIMETRIC 模式单位为 MM_TEXT 像素单位
	pPicture->Render(pDC->m_hDC,0,0,sz.cx,sz.cy,
		0,nHeight,nWidth,-nHeight,NULL);

	////////按窗口尺寸显示////////
//	CRect rect;	GetClientRect(&rect);
//	pPicture->Render(pDC->m_hDC,0,0,rect.Width(),rect.Height(),
//		0,nHeight,nWidth,-nHeight,NULL);

	if ( pPicture ) pPicture->Release();// 释放 IPicture 指针
	if ( pStream ) pStream->Release();  // 释放 IStream 指针,同时释放了 hMem

	::CoUninitialize();
}

示例四、在桌面建立快捷方式
在阅读代码之前,先看一下关于“快捷方式”组件的结构示意图。


图二、快捷方式组件的接口结构示意图

从结构图中可以看出,“快捷方式”组件(CLSID_ShellLink),有3个(其实不止)接口,每个接口完成一组相关功能的函数。IShellLink 接口(IID_IShellLink)提供快捷方式的参数读写功能(见图三),IPersistFile 接口(IID_IPersistFile)提供快捷方式持续性文件的读写功能。对象的持续性(注5),是一个非常常用,并且功能强大的接口家族。但今天,我们只要了解其中两函数,就可以了:IPersistFile::Save()和IPersistFile:Load()。(注6)


图三、快捷方式中的各种属性

#include < atlconv.h >
void CreateShortcut(LPCTSTR lpszExe, LPCTSTR lpszLnk)
{
	// 建立块捷方式
	// 参数 lpszExe: EXE 文件全路径名
	// 参数 lpszLnk: 快捷方式文件全路径名

	::CoInitialize( NULL );

	IShellLink * psl = NULL;
	IPersistFile * ppf = NULL;

	HRESULT hr = ::CoCreateInstance(  // 启动组件
		CLSID_ShellLink,      // 快捷方式 CLSID
		NULL,                 // 聚合用(注4)
		CLSCTX_INPROC_SERVER, // 进程内(Shell32.dll)服务
		IID_IShellLink,       // IShellLink 的 IID
		(LPVOID *)&psl );     // 得到接口指针

	if ( SUCCEEDED(hr) )
	{
		psl->SetPath( lpszExe );  // 全路径程序名
//		psl->SetArguments();      // 命令行参数
//		psl->SetDescription();    // 备注
//		psl->SetHotkey();         // 快捷键
//		psl->SetIconLocation();   // 图标
//		psl->SetShowCmd();        // 窗口尺寸

		// 根据 EXE 的文件名,得到目录名
		TCHAR szWorkPath[ MAX_PATH ];
		::lstrcpy( szWorkPath, lpszExe );
		LPTSTR lp = szWorkPath;
		while( *lp )    lp++;
		while( ''\\'' != *lp )    lp--;
		*lp=0;

		// 设置 EXE 程序的默认工作目录
		psl->SetWorkingDirectory( szWorkPath );

		hr = psl->QueryInterface(  // 查找持续性文件接口指针
			IID_IPersistFile,      // 持续性接口 IID
			(LPVOID *)&ppf );      // 得到接口指针

		if ( SUCCEEDED(hr) )
		{
			USES_CONVERSION;       // 转换为 UNICODE 字符串
			ppf->Save( T2COLE( lpszLnk ), TRUE );  // 保存
		}
	}
	if ( ppf )	ppf->Release();
	if ( psl )	psl->Release();

	::CoUninitialize();
}

void OnXXX()
{
	CreateShortcut(
		_T("c:\\winnt\\notepad.exe"),  // 记事本程序。注意,你的系统是否也是这个目录?
		_T("c:\\Documents and Settings\\Administrator\\桌面\\我的记事本.lnk")
	);
	// 桌面上建立快捷方式(lnk)文件的全路径名。注意,你的系统是否也是这个目录?
	// 如果用程序实现寻找桌面的路径,则可以查注册表
	// HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders
}


七、小结

本回介绍的内容比较实用。大家不要只抄袭代码,而一定要理解它。结合 MSDN 的说明去思索代码、理解其含义。好了,想方设法把代码忘掉!三天后(如过你还没有忘记,那就再过三天),你在不参考示例代码,但可以随便翻阅 MSDN 的情况下,自己能独立地再次完成这四个例程,那么恭喜你,你已经入门了:0) 从下回开始,我们要用 ATL 做 COM 的开发工作啦,您老人家准备好了吗?

作业,留作业啦……
1、你已经学会如何建立快捷方式了,那么你知道怎么读取它的属性吗?(如果写不出这个程序,那么你就不用继续学习了。因为……动点脑筋呀!我还没有见过象你这么笨的学生呢!)
2、示例程序三中使用了 IPicture 接口显示一个 JPG 图象。那么你现在去完成一个功能,把 JPG 文件转换为 BMP 文件。


注1:智能指针的概念和用法,后续介绍。
注2:IDL 文件,下回就要介绍啦。
注3:东北话,想干什么都可以,反正我不管啦。
注4:聚合,也许在第30回中介绍吧:-)
注5:持续性,IPersistXXXXXX是一个非常强大的接口家族,后续介绍。
注6:想知道 IShellLink、IPersistFile接口的所有函数吗?别愣着,快去看MSDN呀……
最新评论 [发表评论] [文章投稿] 查看所有评论 推荐给好友 打印
好奇怪,我做了老师的作业,先用IPicture通过IStream对象去读一个jpg文件.然后再把这个IPicture通过一个IStream对象写到一个Buffer里,再通过这个Buffer写到一个bmp文件中。可是为什么原来那个JPG文件1.7M,可是写到bmp文件里还是1.7M?我感觉图像格式并没有变。 ( smallkey 发表于 2008-5-8 1:07:00)

相见恨晚呀! ( wl0008 发表于 2007-7-3 16:39:00)

写得到太好,深入淺出。 ( ytfrdfiw 发表于 2007-4-22 14:10:00)

我想问杨老师一个问题:
工程引用在vc6++怎么实现 ( lycnet 发表于 2007-3-4 14:40:00)

顶……..非常的好!teacher Yang 的文章和《COM技术内幕》结合起来看我觉得不错!
郑重申明:个人愚见 ( Johnason 发表于 2006-9-12 16:28:00)

杨老师,谢谢您!!! ( goxigo 发表于 2006-8-23 17:43:00)

写的好啊,太谢谢了。。
只恨见到太晚。。。
不过老师就是老师,总爱留作业 ;) ( maxxfire 发表于 2006-6-10 11:00:00)

如何在一个方法中实现两个出参数:[out]BSTR* BSTR1,BSTR* BSTR2??
很急!谢谢大虾.
还有,一个接口方法中能不能使用两个以上的CoTaskMemAlloc??我使用一个可以正常交互;使用两个的时候在客户端得到的数据为空.
毕业设计赶着要用.谢谢大虾的帮忙
我的油箱:liziwen1982@163.com
QQ:87052309
谢谢交流 ( liziwen1982 发表于 2006-5-1 17:29:00)

疑问: 为什么void CUse1Dlg::OnButton1() 中需要
if( s3 ) ::SysFreeString( s3 );  //IFun::Cat()最后一个参数是 [out] 方向属性,因此调用者要释放内存, 不理解是什么意思啊!!!

每一个new() 对应 一个delete(), 那么跟SysFreeString()对应的操作是什么呢, 在哪里分配的内存?? ( ding_net 发表于 2006-4-29 21:45:00)

void CTestCom1Dlg::GetAttribute( LPCSTR szLnk )
{
IShellLink * psl = NULL;
IPersistFile * ppf = NULL;
HRESULT hr = ::CoCreateInstance(  // 启动组件
CLSID_ShellLink,      // 快捷方式 CLSID
NULL,                 // 聚合用(注4)
CLSCTX_INPROC_SERVER, // 进程内(Shell32.dll)服务
IID_IShellLink,       // IShellLink 的 IID
(LPVOID *)&psl );     // 得到接口指针

if ( SUCCEEDED(hr) )
{
hr = psl->QueryInterface(  // 查找持续性文件接口指针
IID_IPersistFile,      // 持续性接口 IID
(LPVOID *)&ppf );      // 得到接口指针

if ( SUCCEEDED(hr) )
{
USES_CONVERSION;
ppf->Load( T2COLE( szLnk ) , 0 ) ;

TCHAR pszAtt[ 1024 ] ;
WIN32_FIND_DATA pfd ;
psl->GetPath( pszAtt , sizeof( pszAtt ) , &pfd , SLGP_SHORTPATH ) ;
MessageBox( pszAtt ) ;
}
}

if ( ppf ) ppf->Release();
if ( psl ) psl->Release();
} ( xiongxiongVC 发表于 2006-1-26 17:54:00)

……………………………………………….
More…

有关COM接口的参数属性(in、out、retval)的解释

对于in类型的参数,由调用者创建,COM组件使用,但是不会改变参数的值。
【in,out】类型的参数是由调用者创建,并由调用者分配内存,COM组件修改 内存中的数据供调用者使用。
【out,retval】类型的参数是由COM组件创建并分配内存,返回给调用者使用,调用者不能修改内存数据。

在html页面中如果比较数据JavaScript可以使用【in,out】的参数,一般使用【out,retval】参数。
在C#代码中可以使用【in,out】参数

CodeProject: .NET – COM Interoperability. Free source code and programming help
http://www.codeproject.com/KB/COM/COM_DOTNET_INTEROP.aspx

Summary

This paper provides the technical overview of .NET and COM interoperability. It describes how .NET components can communicate with existing COM components without migrating those COM components into .NET components, thus helping the migration cost and business systems. This paper also provides an overview of marshalling. The intended audience is a development team that wishes to interact with COM and .NET applications. (This paper assumes that the reader has the fundamental knowledge of COM and .NET)

Introduction

From the time Microsoft engineers started working on the ideas behind COM in 1998, COM went through quite an evolution. Once .NET was released everything was about the CLR. Those business systems made lot of investments on those COM developments and they may not be willing to invest more money to build their components into .NET. Also this will make a severe impact in productivity.

Fortunately, switching from COM to .NET involves no such radical loss of productivity. The concept of providing bridge between .NET and COM components is .NET-COM interoperability. Microsoft .NET Framework provides system, tools, and strategies that enable strong integration with past technologies and allow legacy code to be integrated with new .NET components. It provides a bridge between the .NET and COM and vice versa.

There are two key concepts that make it much easier to move from COM development to .NET development, without any loss of code base or productivity.

  • Interaction with COM components from .NET
  • Interaction with .NET components from COM

Before going further, this paper will describe about the basic communication fundamentals of COM and .NET components.

Communication between Object and Client

COM is a binary reusable object which exposes its functionality to other components. When a client object asks for instances of server object, the server instantiates those objects and handout references to the client. So, a COM component can act as a binary contract between caller and callee. This binary contract is defined in a document known as Type library. The Type library describes to a potential client the services available from a particular server. Each COM components will expose a set of interfaces through which the communication between COM components will occurs.

The following diagram shows the communication between a client and a COM object.

Fig.1 Communication between client and a COM object

In the above figure the IUnknown and IDispatch are the interfaces and QueryInterface, AddRef, Release, etc., are the methods exposed by those interfaces.

The communication between the .NET objects occurs through Objects, there are no such interfaces for communication. So, in .NET component, there is no type libraries, instead they deal with assemblies. Assembly is a collection of types and resources that are built to work together and form a logical unit of functionality. All the information related to the assembly will be held in assembly metadata. Unlike the communication between COM components, the communication between .NET components is Object based.

Calling COM components from .NET Client

Generally COM components will expose interfaces to communicate with other objects. A .NET client cannot directly communicate with a COM component because the interfaces exposed by a COM component may not be read by the .NET application. So, to communicate with a COM component, the COM component should be wrapped in such a way that the.NET client application can understand the COM component. This wrapper is known as Runtime Callable Wrapper (RCW).

The .NET SDK provides Runtime Callable Wrapper (RCW) which wraps the COM components and exposes it into to the .NET client application.

Fig.2 calling a COM component from .NET client

To communicate with a COM component, there should be Runtime Callable Wrapper (RCW). RCW can be generated by using VS.NET or by the use of TlbImp.exe utility. Both the ways will read the type library and uses System.Runtime.InteropServices.TypeLibConverter class to generate the RCW. This class reads the type library and converts those descriptions into a wrapper (RCW). After generating the RCW, the .NET client should import its namespace. Now the client application can call the RCW object as native calls.

When a client calls a function, the call is transferred to the RCW. The RCW internally calls the native COM function coCreateInstance there by creating the COM object that it wraps. The RCW converts each call to the COM calling convention. Once the object has been created successfully, the .NET client application can access the COM objects as like native object calls.

Calling .NET components from COM Client

When a COM client requests a server, first it searches in the registry entry and then the communication starts. Calling a .NET component from a COM component is not a trivial exercise. The .NET objects communicate through Objects. But the Object based communication may not be recognized by the COM clients. So, to communicate with the .NET component from the COM component, the .NET component should be wrapped in such a way that the COM client can identify this .NET component. This wrapper is known as COM Callable Wrapper (CCW). The COM Callable Wrapper (CCW) will be used to wrap the .NET components and used to interact with the COM clients.

CCW will be created by the .NET utility RegAsm.exe. This reads metadata of the .NET component and generates the CCW. This tool will make a registry entry for the .NET components.

Fig.3 calling a .NET component from COM client

Generally COM client instantiates objects through its native method coCreateInstance. While interacting with .NET objects, the COM client creates .NET objects by coCreateInstance through CCW.

Internally, when coCreateInstance is called, the call will redirect to the registry entry and the registry will redirect the call to the registered server, mscoree.dll. This mscoree.dll will inspect the requested CLSID and reads the registry to find the .NET class and the assembly that contains the class and rolls a CCW on that .NET class.

When a client makes a call to the .NET object, first the call will go to CCW. The CCW converts all the native COM types to their .NET equivalents and also converts the results back from the .NET to COM.

Programming model comparison of .NET-COM interoperability

The following table compares the .NET and COM based component programming models.

.NET COM
Object based communication Interface based communication
Garbage Collector to manage memory Reference count will be used to manage memory
Type Standard objects Binary Standard objects
Objects are created by normal new operator Objects are created by coCreateInstance
Exceptions will be returned HRESULT will be returned
Object info resides in assembly files Object info resides in Type library

Before the application starts to communicate, there are some technical constraints associated with this. When an object is transmitted to a receiver which is in a separate machine/process (managed/unmanaged) space, the object may need to undergo a transformation according to the native type to make it suitable for use by the recipient. That is the object will be converted into a recipient readable form. This process of converting an object between types when sending it across contexts is known as marshaling. The next section of the paper will gives an overview of marshalling in .NET.

.NET Marshalling

Thus .NET runtime automatically generates code to translate calls between managed code and unmanaged code. While transferring calls between these two codes, .NET handles the data type conversion also. This technique of automatically binding with the server data type to the client data type is known as marshalling. Marshaling occurs between managed heap and unmanaged heap. For example, Fig.4 shows a call from the .NET client to a COM component. This sample call passes a .NET string from the client. The RCW converts this .NET data type into the COM compatible data type. In this case COM compatible data type is BSTR. Thus the RCW converts the .NET string into COM compatible BSTR. This BSTR will be passed to the object and the required calls will be made. The results will be returned to back to the RCW. The RCW converts this COM compatible result to .NET native data type.

Fig.4 Sample diagram for marshalling

Logically the marshalling can be classified into 2 types.

  1. Interop marshalling
  2. COM marshalling

If a call occurs between managed code and unmanaged code with in the same apartment, Interop marshaler will play the role. It marshals data between managed code and unmanaged code.

In some scenarios COM component may be running in different apartment threads. In those cases i.e., calling between managed code and unmanaged code in different apartments or process, both Interop marshaler and COM marshaler are involved.

Interop marshaler

When the server object is created in the same apartment of client, all data marshaling is handled by Interop marshaling.

Fig.5 Sample diagram for same apartment marshalling

COM marshaler

COM marshaling involved whenever the calls between managed code and unmanaged code are in different apartments. For eg., when a .NET client (with the default apartment settings) communicates with a COM component (whichever developed in VB6.0), the communication occurs through proxy and stub because both the objects will be running in different apartment threads. (The default apartment settings of .NET objects are STA and the components which are developed by VB6.0 are STA). Between these two different apartments COM marshaling will occurs and with in the apartment Interop marshaling will occurs. Fig.6 shows this kind of marshaling.

This kind of different apartment communication will impact the performance. The apartment settings of the managed client can be changed by changing the STAThreadAttribute / MTAThreadAttribute / Thread.ApartmentState property. Both the codes can run in a same apartment, by making the managed code’s thread to STA. (If the COM component is set as MTA, then cross marshaling will occurs.)

Fig.6 Sample diagram for cross apartment marshalling

In the above scenario, the call with in different apartments will occur by COM marshaling and the call between managed and unmanaged code will occur by Interop marshaling.

Conclusion

Thus the communication between .NET applications and COM applications occurs through RCW and CCW.

As you have seen, COM applications can implement .NET types to achieve type compatibility or a .NET type can implement COM interfaces to achieve binary compatibility with related coclasses.

Although the managed clients can interact with the unmanaged objects, the managed client expects that the unmanaged object should act exactly the same as managed object.

When developing against the unmanaged component through COM interoperability, managed code developers will not be able to use some features of .NET like parameterized constructors, static methods, inheritance, etc., migrating an existing component or writing a managed wrapper will make the component easier to use for managed code developers. In some cases, the developer wants to migrate parts of the application to .NET so that application can take advantage of the new features that the .NET Framework offers. For example, ASP .NET provides advanced data binding, browser-dependent user interface generation, and improved configuration and deployment. The designer should evaluate when the value of bringing these new features in to the application outweigh the cost of code migration.

References

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

KRISHNA PRASAD.N
Occupation: Web Developer
Location: United States United States

Other popular COM / COM+ articles:


加关注

Get every new post delivered to your Inbox.