2007-06-29 08:14:10
文章正文
一.Win32全局钩子的运行机制
hook将使程序效率降低,因为它们增加了系统必须处理的消息总数。你应该在需要时才使用,并及时删除它。我将以下面的主题描述hook。
系统支持很多不同类型的hooks;不同的hook提供不同的消息处理机制。比如,应用程序可以使用WH_MOUSE_hook来监视鼠标消息的传递。
系统为不同类型的hook提供单独的hook链。hook链是一个指针列表,这个列表的指针指向指定的,应用程序定义的,被hook过程调用的回调函数。当与指定的hook类型关联的消息发生时,系统就把这个消息传递到hook过程。一些hook过程可以只监视消息,或者修改消息,或者停止消息的前进,避免这些消息传递到下一个hook过程或者目的窗口。
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。
下面的例子使用了不同的特定线程hook过程去监视系统事件。它示范了怎样使用下面的hook过程去处理事件:
WH_CALLWNDPROC
WH_CBT
WH_DEBUG
WH_GETMESSAGE
WH_KEYBOARD
WH_MOUSE
WH_MSGFILTER
用户可以通过使用菜单安装或者移走hook过程。当hook过程已经安装并且过程监视的时间发生时,hook过程将在应用程序主窗口客户区写出事件信息。原代码如下:
// 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:
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.
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;
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;
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);
}
今天早上去公司上班,老板去的很早,在我吃带过去的饼的时候,老板和我聊了一下下一步该怎么做。我原以为老板会让我把昨天写的代码集成到现有的系统里去的。哪知道老板说,现在的系统已经很稳定了,希望我能把我写的代码作为一个独立的进程来运行,以免影响到原有的系统的稳定。这下可难着我了,我怎么知道用户什么时候修改了文件夹的权限呢?本来我的如意算盘是:当用户把文件上传到服务器前,我检测一下文件夹的权限,然后做相应的修改。这个时机是可以把握住的。现在却要让我捕获用户修改权限的动作。幸好以前看的书多,多少知道些概念,似乎可以用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. }
用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的_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创建一个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窗口,给出了你所期望的结果。
Posted by dengwei under doc
内容
实现和IE浏览器交互的几种方法的介绍
—- 1.引言
—- 如何实现对IE浏览器中对象的操作是一个很有实际意义问题,通过和IE绑定的DLL我们可以记录IE浏览过的网页的顺序,分析用户的使用行为和模式。我们可以对网页的内容进行过滤和翻译,可以自动填写网页中经常需要用户填写的Form内容等等,我们所有的例子代码都是通过VC来表示的,采用的原理是通过和IE对象的接口的交互来实现对IE的访问。实际上是采用COM的技术,我们知道COM是和语言无关的一种二进制对象交互的模式,所以实际上我们下面所描述的内容都可以用其他的语言来实现,比如VB,DELPHI,C++ Builder等等。
—- 2.IE实例遍历实现
—- 首先我们来看系统是如何知道当前有多少个IE的实例在运行。
—- 我们知道在Windows体系结构下,一个应用程序可以通过操作系统的运行对象表来和这些应用的实例进行交互。但是IE当前的实现机制是不在运行对象表中进行注册,所以需要采用其他的方法。我们知道可以通过ShellWindows集合来代表属于shell的当前打开的窗口的集合,而IE就是属于shell的一个应用程序。
—- 下面我们描述一下用VC实现对当前 IE实例的进行遍历的方法。IShellWindows是关于系统shell的一个接口,我们可以定义一个如下的接口变量:
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);
然后我们可以判断实例对象是不是
属于IE浏览器对象,通过下面的语句实现:
SHDocVw::IWebBrowser2Ptr spBrowser(spDisp);
assert(spBrowser != NULL)
—-在得到了IE浏览器对象以后,我们可以调用IWebBrowser2Ptr接口的方法来得到当前的文档对象的指针: MSHTML::IHTMLDocument2Ptr spDoc(spBrowser->GetDocument());
—- 然后我们就可以通过这个接口对这个文档对象进行操作,比如通过Gettitle得到文档的标题。
—- 我们在浏览网络的时候,一般总会同时开很多IE的实例,如果这些页面都是很好的话,我们可能想保存在硬盘上,这样,我们需要对每一个实例进行保存,而如果我们采用上面的原理,我们可以得到每一个IE的实例及其网页对象的接口,这样就可以通过一个简单的程序来批量的保存当前的所有打开的网页。采用上面介绍的方法实现了对当前IE实例的遍历,但是我们希望得到每一个IE实例所产生的事件,这就需要通过DLL的机制来实现。
—- 3.和IE相绑定的DLL的实现
—- 我们介绍一下如何建立和IE进行绑定的DLL的实现的过程。为了和IE的运行实例进行绑定,我们需要建立一个能够和每一个IE实例进行绑定的DLL。IE的启动过程是这样的,当每一个IE的实例启动的时候,它都会在注册表中去寻找这个的一个CLSID,具体的注册表的键位置为:
HKEY_LOCALL_MACHINESOFTWAREMicrosoftWindows
CurrentVersionExplorerBrowser Helper Objects
—- 当在这个键位置下存在CLSIDs的时候,IE会通过使用CoCreateInstance()方法来创建列在该键位置下的每一个对象的实例。注意对象的CLSIDs必须用子键而非启动过程是这样的,当每一个IE的实例启动的时候名字值的形式表现,比如{DD41D66E-CE4F-11D2-8DA9-00A0249EABF4} 就是一个有效的子键。我们使用DLL的形式而非EXE的形式的原因是因为DLL和IE实例运行在同一个进程空间里面。每一个这种形式的DLL必须实现接口IObjectWithSite,其中方法SetSite必须被实现。通过这个方法,我们自己的DLL就可以得到一个指向IE COM对象的IUnknown的指针,实际上通过这个指针我们就可以通过COM对象中的方法QueryInterface来遍历所有可以得到的接口,这是COM的基本的机制。当然我们需要的只是IWebBrowser2这个接口。
—- 实际上我们建立的是一个COM对象,DLL只不过是COM对象的一种表现形式。我们建立的COM对象需要建立和实现的方法有:
—-1. IOleObjectWithSite接口的方法SetSite必须实现。实际上IE实例通过这个方法向我们的COM对象传递一个接口的指针。假设我们有一个接口指针的变量,不妨设为:
—-CComQIPtr< IWebBrowser2, &IID_IWebBrowser2 > m_myWebBrowser2;
—- 我们就可以在方法SetSite中把这个传进来的接口指针赋给m_myWebBrowser2。 2. 在我们得到了指向IE COM对象的接口后,我们需要把自己的DLL和IE实例所发生的事件相关连,为了实现这个目的,需要介绍两个接口:
—-(1) IConnectionPointContainer。:
—-CComQIPtr< IWebBrowser2, &这里使用这个接口的目的是用来根据它得到的IID来建立和DLL的一个特定的连接。比如我们可以进行如下的定义:
CComQIPtr< IConnectionPointContainer,
&IID_IConnectionPointContainer >
spCPContainer(m_myWebBrowser2);
—-然后,我们需要把所有IE中发生的事件和我们的DLL进行通讯,可以使用 IConnectPoint。
—-(2) IConnectPoint。通过这个接口,客户可以对连接的对象开始或者是终止一个advisory循环。IConnectPoint有两个主要的方法,一个为Advice,另一个为Unadvise。对于我们的应用来说,Advise是用来在每一个IE发生的事件和DLL之间建立一个通道。而Unadvise就是用来终止以前用Advise建立的通知关系。比如我们可以定义IConnectPoint接口如下: CComPtr< IConnectionPoint > spConnectionPoint;
—- 然后,我们要使所有在IE实例中发生的事件和我们的DLL相关,可以使用 如下的方法:
hr = spCPContainer->FindConnectionPoint(
DIID_DWebBrowserEvents2, &spConnectionPoint);
—-然后我们通过IConnectPoint接口的方法Advice使每当IE有一个新的事件发生的时候,都能够让我们的DLL知道。可以用如下的语句实现:
hr = spConnectionPoint- >Advise(
(IDispatch*)this, &m_dwIDCode);
—-在把IE实例中的事件和我们的DLL之间建立联系以后,我们可以通过IDispatch接口的Invoke()方法来处理所有的IE的事件。
—-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实际上是可以认为是和IE实例所发生的每一个事件相关的方法,比如: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,所以在IE和我们DLL之间可以传递的参数类型的数目是有限的。只有那些能够被放到VARIANTARG结构中的类型才可以通过调度接口进行传递。 比如对于事件DISPID_NAVIGATECOMPLETE2来说:第一个参数表示IE在访问的URL的值,类型是VT_BYREF|VT_VARIANT。注意DISPID_NAVIGATECOMPLETE2等DISPID已经在VC中被定义,我们可以直接进行使用。 如上说述,我们在方法Invoke中可以得到所有IE实例所发生的事件,我们可以把这些数据放到文件中进行事后的分析,也可以放到一个列表框中实时的显示。
—- 4.微软的HTML文档对象模型和应用分析
—- 下面我们来看如何得到网页文档的接口:网页文档的接口为IHTMLDocument2,可以通过调用IE 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)标准,当然这个标准不仅仅是针对HTML,同时还是针对XML制定的。W3C组织只是定义了网页对象的接口,不同的公司可以采用不同的语言和方法进行具体的实现。按照W3C组织定义的网页对象被认为是动态的,即用户可以动态的对网页对象里面所包含的每一个对象进行操作。这里的对象可以是指一个输入框,也可以是图象和声音等对象。同时按照W3C的正式文档的说明,网页对象是可以动态增加和删除的。事实上,很少有厂商实现了DOM定义的所有功能。微软对网页对象的定义也基本上是按照这个标准实现的。但是当前的接口还不支持动态的增加和删除元素,但是可以对网页中的基本元素进行属性的修改。比如IHTMLElementCollection表示网页中一些基本的元素的集合,IHTMLElement表示网页中的一个基本的元素。而象IHTMLOptionElement接口就表示一个特定的元素Option。基本的元素都有setAttribute和geAttribute方法来动态的设置和得到元素的名称和值。
—- 较为常见的一个应用是我们能够分析网页中是否有需要填写的Forms,如果这个网址的Forms以前已经填写过而且数据我们已经保存下来的话,我们就可以把数据自动放到和该URL下的Forms的相关的位置中去。另外,我们可以总结网页上需要填写的Form的数据项,先对这些数据项进行赋值,以后碰到有相同的数据项的时候就自动把我们赋值的内容填写进去。实际上Form是对象,Form中包含的元素,比如INPUT,OPTION,SELECT等类型的输入元素都是对象。
—- 另外一个可以想到的应用是自动对网页中的文本进行翻译,因为我们可以修改网页中任何对象的属性,所以我们可以把里面不属于本国语言的部分自动翻译成本国语言,当然真正的实现还要靠自然语言理解方面技术的突破,但是IE浏览器的接口和对象的形式使我们能够灵活的控制整个IE,无论是从事件对象还是到网页对象。
—- 5.小结
—- 上面我们分析了如何得到所有IE的实例,同时介绍了和IE实例相捆绑的DLL的详细的实现机制,同时对网页的对象化进行了分析。并且介绍了几个相关的应用和实现的方法及存在的技术问题。IE是一个组件化的以COM为基础的浏览器,它具有强大的功能,同时为应用开发者留下了广阔的空间,当然它也存在体积比较大,速度相对比较慢的缺点。但是它的体系结构代表了微软先进的创新的技术,因此具有强大的生命力。
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 的命令有:
| 值 | 描述 |
|---|---|
| pUnk | band 对象的 IUnknown 指针,其它的桌面 bands 将被隐藏 |
| 0 | 隐藏所有的桌面 bands |
| 1 | 显示所有的桌面 bands |
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 就会加载活动的桌面对象。
五、结束语
好了,到这里,就到这里了。祝大家学习快乐^_^
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 总结 一、 简介 浏览栏区对象 |
| 图一 在浏览栏中可以创建很多子菜单或选项,用户能以不同方式选择这些子菜单或选项提供的功能,打开IE或者资源管理器,从“查看”菜单中选择“浏览栏”,可以看到Windows提供了几种标准的浏览栏菜单,如“搜索(Search)”,“收藏夹(Favorites)”, 和“历史记录(History)”,以及“文件夹(All Folders)”。(如图二) |
| 图二 为了创建定制的浏览栏,必须编程实现,然后注册它们。Windows在外壳(Shell)4.71中引入了区对象。它提供与普通窗口一样的功能。但因为它是以IE或外壳为容器的COM对象,所以实现起来就与普通窗口有所不同。图一中显示的就是一个简单的浏览栏例子。图中有一个垂直的浏览栏和一个水平的浏览栏。 工具栏区对象 用户可以从“查看”菜单中的“工具栏”子菜单中选择显示单选工具栏,也可以在工具栏区域单击鼠标右键从它的上下文菜单中选择显示单选工具栏。 桌面区对象 桌面区的初始浮动位置在任务栏:(如图五 用户可以将桌面区拖到桌面上,这时它就成了一个普通窗口:(如图六) |
| 二、实现区对象 尽管可以像使用普通窗口一样使用区对象,但它们毕竟是COM对象,存在于某个容器之中。如浏览栏和工具栏位于IE之中,桌面区位于外壳之中。虽然它们的功能不同,但其基本实现非常相似。一个主要的差别是它们的注册方式不同,而注册方式的不同又决定了对象的类型及其容器。这一部分我们先讨论所有区对象实现的共性。其它的实现细节可参考垂直浏览栏例子程序。 区对象除了要实现 IUnknown 和 IClassFactory 两个接口之外,所有的区对象还必须实现以下这几个接口:
另外,在注册时除了注册它们的CLSID之外,浏览栏和桌面区对象还必须进行组件类别(category)的注册。它决定了对象的类型及其容器。工具栏不需要进行种类注册。归纳起来,需要进行CATID注册的三种区对象是:
对于如何注册区对象的进一步讨论请参见注册部分。
DBID_PUSHCHEVRON——目前没有实现。 注册 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 }
除此之外,还有几个可选的注册值可以加到注册表中,本文的例子中未使用这些值。
如果要用浏览栏显示HTML,则前两个注册项是必须的。最后一个注册项则根据垂直的或者水平的浏览栏定义相应的缺省宽度和高度。 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。
实际上,只要通过恰当的组件种类注册,浏览栏例子代码便既可用于浏览栏的实现,也能用于桌面band实现。更加复杂的实现将需要定制每种对象类型的显示区域和容器。但大多数的定制工作都能通过范例代码以及Windows子窗口的编程技术来完成。例如,你可以添加用户交互控制或者进行色彩丰富的图形显示处理。 DLL函数
这些函数可以在BandObjs.cpp中找到,它们服务于所有三种区对象。前三个函数乃标准的实现,我们不再本文中讨论。类工厂也是标准实现,代码可以在ClsFact.cpp中找到 有了COM对象后,必须对浏览栏的CLSID进行注册。另外如果要与IE或资源管理器 ...
//注册浏览栏对象
if(!RegisterServer(CLSID_SampleExplorerBar, TEXT("垂直浏览栏例子")))
return SELFREG_E_CLASS;
//注册浏览栏的对象组件种类
if(!RegisterComCat(CLSID_SampleExplorerBar, CATID_InfoBand))
return SELFREG_E_CLASS;
...
区对象的注册使用通常的COM过程,它由私有函数RegisterServer处理。 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实现应该完成下列步骤:
以下是浏览栏实现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接口 IDeskBand接口 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接口 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;
}
窗口过程 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编程技术。虽然本文所讨论的例子只提供了有限的功能,但它示范了区对象全部的特性,并且可以在此基础上进行扩充来创建独特和功能强大的的用户界面。 |
admin 发表于: 2008-9-07 10:22 来源: pdf之家
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();
}
admin 发表于: 2008-9-12 08:26 来源: pdf之家
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文档是一件容易,方便的方式来提供给你的用户更多的信息。
2006-05-11 16:01:56
在.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>
March 18, 2004
|
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?
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.
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.
As the diagram below illustrates the following five steps are happening:

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.
Topic
Messages
I assume your web service is returning data of type “xml” and not string
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
Thanks for the response. This issue is resolved.
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: 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.
|
|||||||||||||||||||||||||||||||||||||||||||||||||||
| VC++获得当前系统时间的几种方案 zz | |
|

第一次发布一个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
最常见的调用com组件的方法就是添加对这个组件的引用,但是这会在最终可执行文件边生成一个伴随的dll如MyCom.Interop.dll等,这给程序的分发带来相当程度的不便,而且添加引用是我们只能引用某个特定版本的com组件,而我们往往无法确定程序所运行的机器上是否有相应版本的com组件,这可能造成我们的程序无法运行。我们要如何解决这个问题呢?
首先要解决com组件的版本问题,这就要求我们不直接使用clsid,而用ProgId来代替客户机上的com组件就可以解决相当程度的问题了,就是这么简单。
至于如何才能把那个可恶的dll文件给去掉,这就需要利用dotNet提供的反射功能了,我们一代码为例,这里我们假设有一个ProgId为”ReflectionCom.TestObj”的com组件,这个组件有唯一一个方法string SayHello(string AName):
行了,我们就是怎么简简单单就可以调用com组件的任何功能了。
不过我们要看到这种方法的不足,由于这里用了迟绑定,因此我们无法让编译器做任何的类型检查工作,因此只有在运行时才能知道程序对错;而且,可以以这种方法调用的com组件必须实现IDispatch接口,也就是说必须是一个自动化组件,假如你要调用的组件是一个普通的com组件的话,那就一边哭去吧!呵呵
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.
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>
Keywords: |
kbhttpruntime kbinterop kbperformance kbprb kbreadme kbthread KB308095 |
要解决该问题, 如下替代 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 使用之前注册控件。
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();
}
}
有关如何对 VisualJ++, 使用新 Jvc.exe 请参阅下列 Microsoft 知识库文章:
关键字: |
kbprb KB189065 KbMtzh kbmt |

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 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:
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):
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:
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:
[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 ); ...
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:
... 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 ); ...
In the download package, you will find the complete interface declarations and a sample client application showing how to use them.
Please note:
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.
The information in this article & source code are published in accordance with the Beta2 bits of the .NET Framework SDK).
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
| VISCOM .NET Team |
|
Other popular COM / COM+ articles:
|
请问,在ASP下如何调用OCX控件??
顺便问一下,OCX控件的用法之类的,
请大家知道一点就说一点,多谢,
急,
!!!
问题点数:100、回复次数:10Top
ASP应该不能调用OCX控件。因为OCX控件是可视的!
就是ASP里面不能用MSGBOX一样。
可以调用ActivX DllTop
啊,这样呀,
我对OCX都不熟悉,是一个朋友问我,他上网不方便,
所以,能不能请lamfish讲多一些关于OCX的东西,因为我刚才在网上找了一下,发现讲OCX的不多,
多谢!!!!!!Top
天啦,没有人来看看吗,
!!
继续在线等!!Top
可以调用呀!如果你用frontpage来做asp文件!在“插入”菜单里面有添加“active x”的选项,把你要添加的“ocx”控件添加进去就可以了,不过该控件要注册!Top
只要符合控件规范就可以调用,如果是COM控件也可以调用.调用控件一般有良种方法:
1.set myocx=server.createobject(控件名.类名)
2.利用<object></object>标记Top
<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
继续问,OCX主要是实现什么东西,??
多谢!!Top
关注
Top
OCX主要是实现什么东西?
呵呵,这个问题有点让我无从解释了!:)Top
同意 : BrightEye(问个不休)Top
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
可以写一个com,用asp调用呀
Top
我要源码Top
上面的问题没有人回答
改问
asp中如何接收activex中onclick的事件Top
asp中不能接收任何COM对象的事件.
ASP可以处理的只有Application和Session对象的四个事件..
而且ASP中不支持GUI界面的COM对象..
如果说有的话,那不是ASP服务端脚本处理的了,那是客户端浏览器的编程了.Top
asp中不能接收任何COM对象的事件.
这个应该纠正一下, 任何COM对象是指外部COM对象.Top
我也想问一下:ASP中如何接收activex中onclick的事件?请高手指教……!
Top
应该不可以。
要asp接受activex的返回值,可以给你的activex添加属性,然后给它赋值。
asp 中的脚本可以调用该属性。Top
还有没有人有高见,我要结贴了Top
现有一个带界面的用于处理图片的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
是的Top
这就是传说中的刷分?Top
|
查看文章
|
|
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模式或其它系统替换正常文件即可解决。 |
|
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。 我想请大虾指点一下,我如何在页面中能看到这个控件,在asp.cs中又能得到它,就象使用其他控件一样使用它? 回答: 1、把这个控件用命令转换成dll文件,然后引入就OK了。 2、在ASP。NET中使用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();
}
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.UCOMITypeLib 和 System.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之类的方法想来有兴趣的人也不多,不过反正这种底层方法就那么几个,在你遍历的时候尽可以判断一下过滤掉这些方法名称。
免责声明:
在本文中,为了清晰起见,所有给出的代码中都没有错误处理。如果你在你的代码中使用本文中的部分代码,由此造成的诸如程序出错、系统宕机、走路撞树、手机爆炸、洪水毁堤、地球毁灭等等一切后果,本人概不负责
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.
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 System.Runtime.InteropServices; using System.Windows.Forms;
IMyDotNetInterface.MyDoNetClass.MyDotNetClass:
[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:

[Guid("03AD5D2D-2AFD-439f-8713-A4EC0705B4D9")]
[Guid("0490E147-F2D2-4909-A4B8-3533D2F264D0")]
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”); } } }
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.
AssemblyVersion attribute in the AssemblyInfo.cs file which is in your project.Example:
[assembly: AssemblyVersion("1.0.0.0")]
AssemblyKeyFile attribute in the AssemblyInfo.cs file which is in your project. Example:
sn -k TestKeyPair.snk
[assembly: AssemblyKeyFile("TestKeyPair.snk")]
gacutil /i MyInterop.dll
REGASM MyInterop.dll /tlb:com.MyInterop.tlb
#import “<Full Path>\com.MyInterop.tlb" named_guids raw_interfaces_only
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
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.
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
| 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.
|
Other popular C# articles:
|
as title 问题点数:0、回复次数:5Top
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
.odl和.idl在com中的功能相同.
前者是ActiveX中的 后者是ATL中.
可在前者的文件中 用 #import “XXXXX.idl” 的方式包含后者. 反过来没有试过.
ODL是Microsoft对IDL的扩展Top
odl已经过时了,现在是idl的天下
现在使用idl就行了Top
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
|
| ::首页 >> 文档中心 >> 在线杂志 >> COM技术(COM/DCOM/COM+) | [ 在线杂志 第43期 ] | |
|
每一个new() 对应 一个delete(), 那么跟SysFreeString()对应的操作是什么呢, 在哪里分配的内存?? ( ding_net 发表于 2006-4-29 21:45:00)
if ( SUCCEEDED(hr) ) if ( SUCCEEDED(hr) ) TCHAR pszAtt[ 1024 ] ; if ( ppf ) ppf->Release(); ………………………………………………. |
对于in类型的参数,由调用者创建,COM组件使用,但是不会改变参数的值。
【in,out】类型的参数是由调用者创建,并由调用者分配内存,COM组件修改 内存中的数据供调用者使用。
【out,retval】类型的参数是由COM组件创建并分配内存,返回给调用者使用,调用者不能修改内存数据。
在html页面中如果比较数据JavaScript可以使用【in,out】的参数,一般使用【out,retval】参数。
在C#代码中可以使用【in,out】参数
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)
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.
Before going further, this paper will describe about the basic communication fundamentals of COM and .NET components.
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.
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.
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.
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.
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.
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.
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 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.
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.
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
| KRISHNA PRASAD.N |
|
Other popular COM / COM+ articles:
|