C++ Coder

HCP高性能计算架构,实现,编译器指令优化,算法优化, LLVM CLANG OpenCL CUDA OpenACC C++AMP OpenMP MPI

C++博客 首页 新随笔 联系 聚合 管理
  98 Posts :: 0 Stories :: 0 Comments :: 0 Trackbacks

#

     摘要: 目录(?)[-] 一、前言二、组件的启动和释放三、内存分配和释放四、参数传递方向五、示例程序六、小结 本文摘自:http://www.vckbase.net/index.php/wv/1211 一、前言      同志们、朋友们、各位领导,大家好。      VCKBASE 不得了,   &nb...  阅读全文
posted @ 2012-10-17 23:13 jackdong 阅读(429) | 评论 (0)编辑 收藏

本文摘自:http://www.vckbase.com/index.php/wv/1206

 

一、前言

上回书介绍了GUID、CLSID、IID和接口的概念。本回的重点是介绍 COM 中的数据类型。咋还不介绍组件程序的设计步骤呀?咳......别着急,别着急!孔子曰:“饭要一口一口地吃”;老子语:“心急吃不了热豆腐”,孙子云:“走一步看一步吧” ...... 先掌握必要的知识,将来写起程序来才会得心应手也:-)

走入正题之前,请大家牢牢记住一条原则:COM 组件是运行在分布式环境中的。比如,你写了一个组件程序(DLL或EXE),那么使用者可能是在本机的某个进程内加载组件(INPROC_SERVER);也可能是从另一个进程中调用组件的进程(LOCAL_SERVER);也可能是在这台计算机上调用地球那边计算机上的组件(REMOTE_SERVER)。所以在理解和设计的时候,要时时刻刻想起这句话。快!拿出小本本,记下来!

二、HRESULT 函数返回值

每个人在做程序设计的时候,都有他们各自的哲学思想。拿函数返回值来说,就有好多种形式。

函数 返回值 返回值信息
double sin(double)

浮点数值

计算正玄值
BOOL DeleteFile(LPCTSTR)

布尔值

文件删除是否成功。如失败,需要GetLastError()才能取得失败原因
void * malloc(size_t)

内存指针

内存申请,如果失败,返回空指针 NULL
LONG RegDeleteKey(HKEY,LPCTSTR)

整数

删除注册表项。0表示成功,非0失败,同时这个值就反映了失败的原因
UINT DragQueryFile(HDROP,UINT,LPTSTR,UINT)

整数

取得拖放文件信息。以不同的参数调用,则返回不同的含义:
一会儿表示文件个数,一会儿表示文件名长度,一会儿表示字符长度
...... ......

...

...... ......

 

 

如此纷繁复杂的返回值,如此含义多变的返回值,使得大家在学习和使用的过程中,增加了额外的困难。好了,COM 的设计规范终于对他们进行了统一。组件API及接口指针中,除了IUnknown::AddRef()和IUnknown::Release()两个函数外,其它所有的函数,都以 HRESULT 作为返回值。大家想象一个组件的接口函数比如叫Add(),完成2个整数的加法运算,在C语言中,我们可以如下定义:

  1. long Add( long n1, long n2 )  
  2. {  
  3. return n1 + n2;  
  4. }  

还记得刚才我们说的原则吗?COM 组件是运行在分布式环境中的。也就是说,这个函数可能运行在“地球另一边”的计算机上,既然运行在那么遥远的地方,就有可能出现服务器关机、网络掉线、运行超时、对方不在服务区......等异常。于是,这个加法函数,除了需要返回运算结果以外,还应该返回一个值------函数是否被正常执行了。

  1. HRESULT Add( long n1, long n2, long *pSum )  
  2. {  
  3. 3*pSum = n1 + n2;  
  4.  return S_OK;  
  5. }  

如果函数正常执行,则返回 S_OK,同时真正的函数运行结果则通过参数指针返回。如果遇到了异常情况,则COM系统经过判断,会返回相应的错误值。常见的返回值有:

HRESULT 含义
S_OK 0x00000000 成功
S_FALSE 0x00000001 函数成功执行完成,但返回时出现错误
E_INVALIDARG 0x80070057 参数有错误
E_OUTOFMEMORY 0x8007000E 内存申请错误
E_UNEXPECTED 0x8000FFFF 未知的异常
E_NOTIMPL 0x80004001 未实现功能
E_FAIL 0x80004005 没有详细说明的错误。一般需要取得 Rich Error 错误信息(注1)
E_POINTER 0x80004003 无效的指针
E_HANDLE 0x80070006 无效的句柄
E_ABORT 0x80004004 终止操作
E_ACCESSDENIED 0x80070005 访问被拒绝
E_NOINTERFACE 0x80004002 不支持接口

图一、HRESULT 的结构

HRESULT 其实是一个双字节的值,其最高位(bit)如果是0表示成功,1表示错误。具体参见 MSDN 之"Structure of COM Error Codes"说明。我们在程序中如果需要判断返回值,则可以使用比较运算符号;switch开关语句;也可以使用VC提供的宏:

  1. HRESULT hr = 调用组件函数;  
  2. if( SUCCEEDED( hr ) ){...} // 如果成功  
  3. ......  
  4. if( FAILED( hr ) ){...} // 如果失败  
  5. ......  

三、UNICODE

计算机发明后,为了在计算机中表示字符,人们制定了一种编码,叫ASCII码。ASCII码由一个字节中的7位(bit)表示,范围是0x00 - 0x7F 共128个字符。他们以为这128个数字就足够表示abcd....ABCD....1234 这些字符了。

咳......说英语的人就是“笨”!后来他们突然发现,如果需要按照表格方式打印这些字符的时候,缺少了“制表符”。于是又扩展了ASCII的定义,使用一个字节的全部8位(bit)来表示字符了,这就叫扩展ASCII码。范围是0x00 - 0xFF 共256个字符。

咳......说中文的人就是聪明!中国人利用连续2个扩展ASCII码的扩展区域(0xA0以后)来表示一个汉字,该方法的标准叫GB-2312。后来,日文、韩文、阿拉伯文、台湾繁体(BIG-5)......都使用类似的方法扩展了本地字符集的定义,现在统一称为 MBCS 字符集(多字节字符集)。这个方法是有缺陷的,因为各个国家地区定义的字符集有交集,因此使用GB-2312的软件,就不能在BIG-5的环境下运行(显示乱码),反之亦然。

咳......说英语的人终于变“聪明”一些了。为了把全世界人民所有的所有的文字符号都统一进行编码,于是制定了UNICODE标准字符集。UNICODE 使用2个字节表示一个字符(unsigned shor int、WCHAR、_wchar_t、OLECHAR)。这下终于好啦,全世界任何一个地区的软件,可以不用修改地就能在另一个地区运行了。虽然我用 IE 浏览日本网站,显示出我不认识的日文文字,但至少不会是乱码了。UNICODE 的范围是 0x0000 - 0xFFFF 共6万多个字符,其中光汉字就占用了4万多个。嘿嘿,中国人赚大发了:0)

在程序中使用各种字符集的方法:

  1. const char * p = "Hello"// 使用 ASCII 字符集  
  2. const char * p = "你好"// 使用 MBCS 字符集,由于 MBCS 完全兼容 ASCII,多数情况下,我们并不严格区分他们  
  3. LPCSTR p = "Hello,你好"// 意义同上  
  4.   
  5. const WCHAR * p = L"Hello,你好"// 使用 UNICODE 字符集  
  6. LPCOLESTR p = L"Hello,你好"// 意义同上  
  7.   
  8. // 如果预定义了_UNICODE,则表示使用UNICODE字符集;如果定义了_MBCS,则表示使用 MBCS  
  9. const TCHAR * p = _T("Hello,你好");   
  10. LPCTSTR p = _T("Hello,你好"); // 意义同上  

在上面的例子中,T是非常有意思的一个符号(TCHAR、LPCTSTR、LPTSTR、_T()、_TEXT()...),它表示使用一种中间类型,既不明确表示使用 MBCS,也不明确表示使用 UNICODE。那到底使用哪种字符集那?嘿嘿......编译的时候决定吧。设置条件编译的方式是:VC6中,"Project\Settings...\C/C++卡片 Preprocessor definitions" 中添加或修改 _MBCS、_UNICODE;VC.NET中,"项目\属性\配置属性\常规\字符集"然后用组合窗进行选择。使用 T 类型,是非常好的习惯,严重推荐!

四、BSTR

COM 中除了使用一些简单标准的数据类型外(注2),字符串类型需要特别重点地说明一下。还记得原则吗?COM 组件是运行在分布式环境中的。通俗地说,你不能直接把一个内存指针直接作为参数传递给COM函数。你想想,系统需要把这块内存的内容传递到“地球另一 边”的计算机上,因此,我至少需要知道你这块内存的尺寸吧?不然让我如何传递呀?传递多少字节呀?!而字符串又是非常常用的一种类型,因此 COM 设计者引入了 BASIC 中字符串类型的表示方式---BSTR。BSTR 其实是一个指针类型,它的内存结构是:(输入程序片段 BSTR p = ::SysAllocString(L"Hello,你好");断点执行,然后观察p的内存)

图二、BSTR 内存结构

BSTR 是一个指向 UNICODE 字符串的指针,且 BSTR 向前的4个字节中,使用DWORD保存着这个字符串的字节长度( 没有含字符串的结束符)。因此系统就能够正确处理并传送这个字符串到“地球另一 边”了。特别需要注意的是,由于BSTR的指针就是指向 UNICODE 串,因此 BSTR 和 LPOLESTR 可以在一定程度上混用,但一定要注意:

有函数 fun(LPCOLESTR lp),则你调用 BSTR p=...; fun(p); 正确

有函数 fun(const BSTR bstr),则你调用 LPCOLESTR p=...; fun(p); 错误!!!

有关 BSTR 的处理函数:

API 函数 说明
SysAllocString() 申请一个 BSTR 指针,并初始化为一个字符串
SysFreeString() 释放 BSTR 内存
SysAllocStringLen() 申请一个指定字符长度的 BSTR 指针,并初始化为一个字符串
SysAllocStringByteLen() 申请一个指定字节长度的 BSTR 指针,并初始化为一个字符串
SysReAllocStringLen() 重新申请 BSTR 指针

CString 函数

说明

AllocSysString() 从 CString 得到 BSTR
SetSysString() 重新申请 BSTR 指针,并复制到 CString 中

CComBSTR 函数

ATL 的 BSTR 包装类。在 atlbase.h 中定义

Append()、AppendBSTR()、AppendBytes()、ArrayToBSTR()、BSTRToArray()、AssignBSTR()、Attach()、Detach()、Copy()、CopyTo()、Empty()、Length()、ByteLength()、ReadFromStream()、WriteToStream()、LoadString()、ToLower()、ToUpper()
运算符重载:!,!=,==,<,>,&,+=,+,=,BSTR
太多了,但从函数名称不能看出其基本功能。详细资料,查看MSDN 吧。另外,左侧函数,有很多是 ATL 7.0 提供的,VC6.0 下所带的 ATL 3.0 不支持。
由于我们将来主要用 ATL 开发组件程序,因此使用 ATL 的 CComBSTR 为主。VC也提供了其它的包装类 _bstr_t。

五、各种字符串类型之间的转换

1、函数 WideCharToMultiByte(),转换 UNICODE 到 MBCS。使用范例:

  1.     LPCOLESTR lpw = L"Hello,你好";  
  2.     size_t wLen = wcslen( lpw ) + 1;  // 宽字符字符长度,+1表示包含字符串结束符  
  3.       
  4.     int aLen=WideCharToMultiByte(  // 第一次调用,计算所需 MBCS 字符串字节长度  
  5. CP_ACP,  
  6. 0,  
  7. lpw,  // 宽字符串指针  
  8. wLen, // 字符长度  
  9. NULL,  
  10. 0,  // 参数0表示计算转换后的字符空间  
  11. NULL,  
  12. NULL);  
  13.   
  14.     LPSTR lpa = new char [aLen];  
  15.   
  16.     WideCharToMultiByte(  
  17. CP_ACP,  
  18. 0,  
  19. lpw,  
  20. wLen,  
  21. lpa,  // 转换后的字符串指针  
  22. aLen, // 给出空间大小  
  23. NULL,  
  24. NULL);  
  25.   
  26.     // 此时,lpa 中保存着转换后的 MBCS 字符串  
  27.     ... ... ... ...  
  28.     delete [] lpa;  

2、函数 MultiByteToWideChar(),转换 MBCS 到 UNICODE。使用范例:

  1.     LPCSTR lpa = "Hello,你好";  
  2.     size_t aLen = strlen( lpa ) + 1;  
  3.       
  4.     int wLen = MultiByteToWideChar(  
  5. CP_ACP,  
  6. 0,  
  7. lpa,  
  8. aLen,  
  9. NULL,  
  10. 0);  
  11.       
  12.     LPOLESTR lpw = new WCHAR [wLen];  
  13.     MultiByteToWideChar(  
  14. CP_ACP,  
  15. 0,  
  16. lpa,  
  17. aLen,  
  18. lpw,  
  19. wLen);  
  20.     ... ... ... ...  
  21.     delete [] lpw;  

3、使用 ATL 提供的转换宏。

A2BSTR OLE2A T2A W2A
A2COLE OLE2BSTR T2BSTR W2BSTR
A2CT OLE2CA T2CA W2CA
A2CW OLE2CT T2COLE W2COLE
A2OLE OLE2CW T2CW W2CT
A2T OLE2T T2OLE W2OLE
A2W OLE2W T2W W2T

上表中的宏函数,其实非常容易记忆:

2 好搞笑的缩写,to 的发音和 2 一样,所以借用来表示“转换为、转换到”的含义。
A ANSI 字符串,也就是 MBCS。
W、OLE 宽字符串,也就是 UNICODE。
T 中间类型T。如果定义了 _UNICODE,则T表示W;如果定义了 _MBCS,则T表示A
C const 的缩写

使用范例:

  1. #include < atlconv.h >  
  2.   
  3. void fun()  
  4. {  
  5.     USES_CONVERSION;  // 只需要调用一次,就可以在函数中进行多次转换  
  6.       
  7.     LPCTSTR lp = OLE2CT( L"Hello,你好") );  
  8.     ... ... ... ...  
  9.     // 不用显式释放 lp 的内存,因为  
  10.     // 由于 ATL 转换宏使用栈作为临时空间,函数结束后会自动释放栈空间。  
  11. }  

使用 ATL 转换宏,由于不用释放临时空间,所以使用起来非常方便。但是考虑到栈空间的尺寸(VC 默认2M),使用时要注意几点:

1、只适合于进行短字符串的转换;

2、不要试图在一个次数比较多的循环体内进行转换;

3、不要试图对字符型文件内容进行转换,因为文件尺寸一般情况下是比较大的;

4、对情况 2 和 3,要使用 MultiByteToWideChar() 和 WideCharToMultiByte();

六、VARIANT

C++、BASIC、Java、Pascal、Script......计算机语言多种多样,而它们各自又都有自己的数据类型,COM 产生目的,其中之一就是要跨语言(注3)。而 VARIANT 数据类型就具有跨语言的特性,同时它可以表示(存储)任意类型的数据。从C语言的角度来讲,VARIANT 其实是一个结构,结构中用一个域(vt)表示------该变量到底表示的是什么类型数据,同时真正的数据则存贮在 union 空间中。结构的定义太长了(虽然长,但其实很简单)大家去看 MSDN 的描述吧,这里给出如何使用的简单示例:

学生:我想用 VARIANT 表示一个4字节长的整数,如何做?

老师:VARIANT v; v.vt=VT_I4; v.lVal=100;

学生:我想用 VARIANT 表示布尔值“真”,如何做?

老师:VARIANT v; v.vt=VT_BOOL; v.boolVal=VARIANT_TRUE;

学生:这么麻烦?我能不能 v.boolVal=true; 这样写?

老师:不可以!因为 

类型 字节长度 假值 真值
bool 1(char) 0(false) 1(true)
BOOL 4(int) 0(FALSE) 1(TRUE)
VT_BOOL 2(short int) 0(VARIANT_FALSE) -1(VARIANT_TRUE)

所以如果你 v.boolVal=true 这样赋值,那么将来 if(VARIANT_TRUE==v.boolVal) 的时候会出问题(-1 != 1)。但是你注意观察,任何布尔类型的“假”都是0,因此作为一个好习惯,在做布尔判断的时候,不要和“真值”相比较,而要与“假值”做比较。

学生:谢谢老师,你太牛了。我对老师的敬仰如滔滔江水,连绵不绝......

学生:我想用 VARIANT 保存字符串,如何做?

老师:VARIANT v; v.vt=VT_BSTR; v.bstrVal=SysAllocString(L"Hello,你好");

学生:哦......我明白了。可是这么操作真够麻烦的,有没有简单一些的方法?

老师:有呀,你可以使用现成的包装类 CComVariant、COleVariant、_variant_t。比如上面三个问题就可以这样书写:CComVariant v1(100),v2(true),v3("Hello,你好"); 简单了吧?!(注4)

学生:老师,我再问最后一个问题,我如何用 VARIANT 保存一个数组?

老师:这个问题很复杂,我现在不能告诉你,我现在告诉你怕你印象不深......(注5)

学生:~!@#$%^&*()......晕!

七、小结

以上所介绍的内容,是基本功,必须熟练掌握。先到这里吧,休息一会儿......更多精彩内容,敬请关注《COM 组件设计与应用(四)》


注1:在后续的 ISupportErrorInfo 接口中介绍。

注2:常见的数据类型,请参考 IDL 文件的说明。(别着急,还没写那......嘿嘿)

注3:跨语言就是各种语言中都能使用COM组件。但啥时候能跨平台呢?

注4:CComVariant/COlevariant/_variant_t 请参看 MSDN。

注5:关于安全数组 SafeArray 的使用,在后续的文章中讨论。

posted @ 2012-10-17 22:52 jackdong 阅读(402) | 评论 (0)编辑 收藏

本文摘自:http://blog.vckbase.com/teacheryang/archive/2005/06/27/8884.html

一、前言
  书接上回,话说在 doc(Word) 复合文件中,已经解决了保存 xls(Excel) 数据的问题了。那么,接下来又要解决另一个问题:当 WORD 程序读取复合文件,遇到了 xls 数据的时候,它该如何启动 Excel 呢?启动后,又如何让 Excel 自己去读入、解析、显示 xls 数据呢?

二、CLSID 概念
  有一个非常简单的解决方案,那就是在对象数据的前面,保存有处理这个数据的程序名。(见下图左上)


图一、CLSID 的概念
  这的确是一个简单的方法,但同时问题也很严重。在“张三”的计算机上,Excel 的路径是:"c:\office\Excel.exe",如果把这个 doc 文件复制到“李四”的计算机上使用,而“李四”的 Excel 的路径是:
"d:\Program files\Microsoft Office\Office\Excel.exe",完蛋了:-(
  于是,微软想出了一个解决方案,那就是不使用直接的路径表示方法,而使用一个叫 CLSID(注1)的方式间接描述这些对象数据的处理程序路径。CLSID 其实就是一个号码,或者说是一个16字节的数。观察注册表(上图),在HKCR\CLSID\{......}主键下,LocalServer32(DLL组件使用InprocServer32) 中保存着程序路径名称。CLSID 的结构定义如下:
typedef struct _GUID {
  
DWORD Data1; // 随机数
  WORD Data2; // 和时间相关
  WORD Data3; // 和时间相关
BYTE Data4[8]; // 和网卡MAC相关
} GUID;

typedef GUID CLSID; // 组件ID
typedef GUID IID; // 接口ID
#define REFCLSID const CLSID &

// 常见的声明和赋值方法
CLSID CLSID_Excel = {0x00024500,0x0000,0x0000,{0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x46}};
struct __declspec(uuid("00024500-0000-0000-C000-000000000046")) CLSID_Excel;
class DECLSPEC_UUID("00024500-0000-0000-C000-000000000046") CLSID_Excel;

// 注册表中的表示方法
{00024500-0000-0000-C000-000000000046}

  用一个号码间接表示程序名,的确是个 Good idea,实现了组件位置的透明性,并方便地扩展出 DCOM(远程组件)。但,但,但,但.....CLSID 有16个字节共128位二进制数,干吗用这么长的数字呀?遥想当年......我还在上幼儿园的时候,人们设计了 socket,用 TCP/IP 协议进行网络通讯。每个参与通讯的计算机都有一个4字节的 IP 表示编号地址,范围是 0,0,0,0 ~ 255,255,255,255 共42亿个地址。可是没想到啊,没想到,自从 Internet 选择了TCP/IP 协议后,42亿个地址就不够全世界的劳动人民分配啦。除了劳动人民,还有冰箱、彩电、电饭锅、手机、手提电脑......这些都需要连网呀。在办公室通过网络开启电饭锅给我焖饭,下班回家后就能吃现成的啦,多幸福呀?!(注:在我们家老婆是领导,所以是我做饭。咳......)
  由于前车之鉴,微软这次设计 CLSID/IID 就使用了GUID概念的16个字节,这下好啦,全世界60亿人口,每个人每秒钟分配10亿个号码,那么需要分配1800亿年。反正等到地球没有了都不会使用完的:-)

三、产生 CLSID
  
1、
如果使用开发环境编写组件程序,则IDE会自动帮你产生 CLSID;
  2、
你可以手工写 CLSID,但千万不要和人家已经生成的 CLSID 重复呀,所以严重地不推荐;(可是微软的CLSID都是手工写的,这叫“只许州官放火,不许百姓点灯”) ;
  3、
程序中,可以用函数 CoCreateGuid() 产生 CLSID;
  4、
使用工具产生 GUID(注2);

  vc6.0版本运行:"vc目录\Common\Tools\GuidGen.exe"程序(你可以参照上回文章中介绍的方法,把这个工具程序加到开发环境中,方便调用)。vc.net版本,在菜单“工具\创建GUID”中,就可以执行了。

四、ProgID 概念
  每一个COM组件都需要指定一个 CLSID,并且不能重名。它之所以使用16个字节,就是要从概率上保证重复是“不可能”的。但是,(世界上就怕“但是”二字)微软为了使用方便,也支持另一个字符串名称方式,叫 ProgID(注3)。见上图注册表的ProgID 子键内容(注4)。由于 CLSID 和 ProgID 其实是一个概念的两个不同的表示形式,所以我们在程序中可以随便使用任何一种。(有些人就是讨厌,说话不算数。明明 GUID 的目的就是禁止重复,但居然又允许使用 ProgID?!ProgID 是一个字符串的名字,重复的可能性就太大了呀。赶明儿我也写个程序,我打算这个程序的 ProgID 叫“Excel.Application”,嘿嘿)下面介绍一下 CLSID 和 ProgID 之间的转换方法和相关的函数:

函数 功能说明
CLSIDFromProgID()、CLSIDFromProgIDEx() 由 ProgID 得到 CLSID。没什么好说的,你自己都可以写,查注册表贝
ProgIDFromCLSID() 由 CLSID 得到 ProgID,调用者使用完成后要释放 ProgID 的内存(注5)
CoCreateGuid() 随机生成一个 GUID
IsEqualGUID()、IsEqualCLSID()、IsEqualIID() 比较2个ID是否相等
StringFromCLSID()、StringFromGUID2()、StringFromIID() 由 CLSID,IID 得到注册表中CLSID样式的字符串,注意释放内存

 

五、接口(Interface)的来历
  到此,我们已经知道了 CLSID 或 ProgID 唯一地表示一个组件服务程序,那么根据这些ID,就可以加载运行组件,并为客户端程序提供服务了。(启动组件程序的方法,会陆续介绍)。接下来先讨论如何调用组件提供的函数?-----接口。
  作为客户端程序员,它希望或者说他要求:我的程序只写一次,然后不做任何修改就可以调用任意一个组件。举例来说:

  1. 你可以在 Word 中嵌入 Excel,也可以嵌入 Picture,也可以嵌入任何第三方发表的 ActiveX 文档......也就是说,连 Word 自己都不知道使用它的人将会在 doc 里面插入什么东东;

  2. 你可以在 HTML 文件中插入一个 ActiveX,也可以插入一个程序脚本Script,......你自己写的插件也可以插入到 IE 环境中。为了完成你的功能, 你绝对也不会去让微软修改IE吧?!

 

  这个要求实在有点难度,Office 开发停滞了。说来话巧,一天老O(Office 项目的总工程师)和小B(VB 项目的总工程师)一起喝酒,老O向小B倾诉了他的烦恼:
老O:怎么能让我写的程序C,可以调用其它人写的程序S中的函数?(C表示客户程序,S表示提供服务的程序)
小B:你是不是喝糊涂了?让S作成 DLL,你去 LoadLibrary()、GetProcAddress()、...FreeLibrary()?!
老O:废话!要是这么简单就好了。问题是,连我都不知道这个S程序是干什么的?能干什么?我怎么调用呀?
小B:哦......这个比较高级,但我现在不能告诉你,因为我怕你印象不深。
老O:~!·#¥%……—*......
小B:是这样的,在VB中,我们制定了一个标准,这个标准允许任何一个VB开发者,把他自己写的某个功能的小程序放在VB的工具栏上,这样就好象他扩展了 VB 的功能一样。
老O:哦?就是那个叫什么 VBX 的滥玩意儿?
小B:我呸......别看 VBX 这个东西不起眼儿,的确我也没看上它。但你猜怎么着?现在有成千上万的 VB 程序爱好者把他们写的各式各样功能的 VBX 小程序,放到网上,让大家共享那。
老O:哦~~~,那你们的这个 VBX 标准是什么?
小B:嘿嘿......其实特简单,就是在 VBX 中必须实现7个函数,这7个函数名称和功能必须是:初始化、释放、显示、消息处理......,而至于它内部想干什么,我也管不着。我只是在需要的时候调用我需要的这7个函数。
老O:哦~~~,这样呀......对了,我现有个急事,我先走了。88,你付帐吧......
小B:喂!喂喂...... 走这么急干什么,钱包都掉了:-)
  老O虽然丢了钱包,仍然兴奋地冲回办公室,他开始了思考......

1、我的程序C,要能调用任何人写的程序B。那么B必须要按照我事先的要求,提供我需要的函数F1(),F2(),F3(),K1(),K2()。
2、BASIC 是解释执行,因此它的函数不用考虑书写顺序,只要给出函数名,解释器就能找到。但我使用的是 C++呀......
3、C++编译后的代码中没有函数名,只有函数地址,因此我必须改进为用VTAB(虚函数表)表示函数入口:


图二、VTAB 的结构

4、还不够好,需要改进一下,因为所有的函数地址都放在一个表中会不灵活、不好修改、不易扩展。恩,有了!按照函数功能的类型进行分类:


图三、多个 VTAB 的结构

5、问题又来了,现在有2个 VTAB 虚函数表,那么怎么能够从一个表找到另一个表那?恩又有办法了,我要求你必须要实现一个函数,并且这个函数地址必须放在所有表的开头(表中的第一个函数指针),这个函数就叫 QueryInterface()吧,完成从一个表查找到另一个表的功能:(除了QueryInterface()函数,顺便也完成另外两个函数,叫 AddRef() 和 Release()。这两个函数的功能以后再说)


图四、COM 接口结构

6、为了以后描述方便,不再使用上图(图四)的方法了,而使用图五这样简洁的样式:


图五、COM 接口结构的简洁图示


六、接口(Interface)概念
1、函数是通过 VTAB 虚函数表提供其地址, 从另一个角度来看,不管用什么语言开发,编译器产生的代码都能生成这个表。这样就实现了组件的“二进制特性”轻松实现了组件的跨语言要求。

2、假设有一个指针型变量保存着 VTAB 的首地址,则这个变量就叫“接口指针”(注6), 变量命名的时候,习惯上加上"I"开头。另外为了区分不同的接口,每个接口 也都要有一个名字,该名字就和 CLSID 一样,使用 GUID 方式,叫 IID。
3、接口一经发表,就不能再修改了。不然就会出现向前兼容的问题。这个性质叫“接口不变性”。
4、组件中必须有3个函数,QueryInterface、AddRef、Release,它们3个函数也组成一个接口,叫"IUnknown"。(注7)
5、任何接口,其实都包含了 IUnknown 接口。随着你接触到更多的接口就会了更体会解到接口的另一个性质“继承性”。
6、在任何接口上,调用表中的第一个函数,其实就是调用 QueryInterface()函数,就得到你想要的另外一个接口指针。这个性质叫“接口的传递性”
7、C/C++语言中需要事先对函数声明,那么就 会要求组件也必须提供C语言的头文件。不行!为了能使COM具有跨语言的能力,决定不再为任何语言提供对应的函数接口声明,而是独立地提供一个叫类型库(TLB)的声明。每个语言的IDE环境自己去根据TLB生成自己语言需要的包装。这个性质叫“接口声明的独立性”(注8)

七、客户程序与组件之间的协商调用
  
回到我们的上一个话题,Word中嵌入一个组件,那么Word是如何协商使用这个组件的那?下面是容器和组件之间的一个模拟对话过程:

  容器 协商部分 组件 应答部分
1 根据CLSID启动组件 。
CoCreateInstance()
生成对象,执行构造函数,执行初始化动作。
2 你有IUnknown接口吗? 有,给你!
3 恩,太好了,那么你有IPersistStorage接口吗?(注9)
IUnknown::QueryInterface(IID_IPersistStorage...)
没有!
4 真差劲,连这个都没有。那你有IPersistStreamInit接口吗?(注10)
IUnknown::QueryInterface(IID_IPersistStreamInit...)
哈,这个有,给!
5 好,好,这还差不多。你现在给我初始化吧。
IPersistStreamInit::InitNew()
OK,初始化完成了。
6 完成了?好!现在你读数据去吧。
IPersistStreamInit::Load()
读完啦。我根据数据,已经在窗口中显示出来了。
7 好,现在咱们各自处理用户的鼠标、键盘消息吧...... ......
8 哎呀!用户要保存退出程序了。你的数据被用户修改了吗?
IPersistStreamInit::IsDirty()
改了,用户已经修改啦。
9 那好,那么用户修改后,你的数据需要多大的存储空间呀?
IPersistStreamInit::GetSizeMax()
恩,我算算呀......好了,总共需要500KB。
10 晕,你这么个小玩意居然占用这么大空间?!......好了,你可以存了。
IPersistStreamInit::Save()
谢谢,我已经存好了。
11 恩。拜拜了您那。(注11)
IPersistStreamInit::Release();IUnknown::Release()
执行析构函数,删除对象。
12 我自己也该退出了......
PostQuitMessage()
 

  容器(或者说客户端)就是这样和组件进行对话,协商调用的。如果组件甲实现了 IA 接口,那么容器就会使用它,如果组件乙没有提供 IA 接口,但是它提供了 IB 接口,那么容器就会调用 IB 接口的函数......如此,容器程序根本就不需要知道组件到底是干什么的,组件到底是用什么语言开发的,组件的磁盘位置到底在哪里,它都可以正常运行。太奇妙了!太精彩了!怎一个“爽”字了得!

八、小结
  第二回中,介绍了两个非常重要的概念:CLSID 和 Interface。由于全篇都是概念描述而没有示例程序相配合,可能读者的理解还不太深入、不彻底。别着急,我们马上就要进入到组件程序设计阶段了,到那个时候,你根据具体的程序代码,再回过头来再次阅读本回文章,没读懂?哦......再读!慢慢地您老人家就懂了:-)

留作业啦......
1、IDispatch 接口的 IID 是多少?(哎~~~ 笨笨,在源程序中,用鼠标右键执行Go to definition 呀)
2、IPicture 接口有几个函数?功能是什么?(别玩了!你多大了?想不想在程序中显示 JPG 图像呀,看 MSDN 去)
  想知道为什么COM函数总是返回 HRESULT 吗?想知道如何使用 BSTR、VARIANT 吗?想知道 COM 中应该如何使用内存吗?想知道如何使用 UNICODE 吗?......恩~~~,我现在不能告诉你,我现在告诉你,怕你印象不深!且听下回分解......

 


 

注1:CLSID = Class ID 上回书已经介绍了把CLSID写入复合文件的函数:WriteClassStg()、IStorage::SetClass()。
注2:GUID 全局唯一标示符,CLSID/IID 其实是借用了GUID的概念。
注3:ProgID = Program ID,等价于 CLSID, 是用字符串表示的。
注4:注册表子键 ProgID 和 VersionIndependentProgID 分别表示真正的 ProgID 和版本无关的 ProgID。比如在我计算机上安装的 Excel,它的 ProgID = "Excel.Application.9",而 VersionIndependentProgID = "Excel.Application"。
注5:COM 组件的内存管理,见后续的文章。
注6:Interface = 接口,以前微软不叫它接口,而叫协议Protocol。其实我 到认为这个词更贴切一些。
注7:IUnknown 这个名字起的好,居然叫“我不知道”:-),它的 IID 叫 IID_IUnknown,如果用注册表样式表示,那么它的值是{00000000-0000-0000-C000-000000000046}。
注8:TLB是由一个描述接口的文件 IDL 经过编译产生的。IDL 的说明,见后续的文章吧。
注9:IPersistStorage 是用复合文件的存储(Storage)功能来保存/读取数据用的一个接口。
注10:IPersistStreamInit 是用复合文件的流(Stream)功能来保存/读取数据用的一个接口。
注11:拜拜了您那 = 英语北京话,再见。

posted @ 2012-10-17 22:33 jackdong 阅读(443) | 评论 (0)编辑 收藏

本文摘自:http://blog.vckbase.com/teacheryang/archive/2005/06/27/8883.html

一、前言
  公元一九九五年某个夜黑风高的晚上,我的一位老师跟我说:“小杨呀,以后写程序就和搭积木一样啦。你赶快学习一些OLE的技术吧......”,当时我心里就寻思 :“开什么玩笑?搭积木方式写程序?再过100年吧......”,但作为一名听话的好学生,我开始在书店里“踅摸”(注1)有关OLE的书籍(注2)。功夫不负有心人,终于买到了我的第一本COM书《OLE2 高级编程技术》,这本800多页的大布头花费了我1/5的月工资呀......于是开始日夜耕读.....
功夫不负有心人,我坚持读完了全部著作,感想是:这本书,在说什么呐?
功夫不负有心人,我又读完了一遍大布头,感想是:咳~~~,没懂!
功夫不负有心人,我再,我再,我再读 ... 感想是:哦~~~,读懂了一点点啦,哈哈哈。
...... ......
功夫不负有心人,我终于,我终于懂了。
800页的书对现在的我来说,其实也就10几页有用。到这时候才体会出什么叫“书越读越薄”的道理了。到后来,能买到的书也多了,上网也更方便更便宜了......
  为了让VCKBASE上的朋友,不再经历我曾经的痛苦、不再重蹈我“无头苍蝇”般探索的艰辛、为了VCKBASE的蓬勃发展、为了中国软件事业的腾飞(糟糕,吹的太也高了)......我打算节约一些在 BBS 上赚分的时间,写个系列论文,就叫“COM组件设计与应用”吧。今天是第一部分——起源。

二、文件的存储
  传说350年前,牛顿被苹果砸到了头,于是发现了万有引力。但到了二十一世纪的现在,任何一个技术的发明和发展,已经不再依靠圣人灵光的一闪。技术的进步转而是被社会的需求、商业的利益、竞争的压力、行业的渗透等推动的。微软在Windows平台上的组件技术也不例外,它的发明,有其必然因素。什么是这个因素那?答案是——文件的存储。
  打开记事本程序,输入了一篇文章后,保存。——这样的文件叫“非结构化文件”;
  打开电子表格程序,输入一个班的学生姓名和考试成绩,保存。——这样的文件叫“标准结构化文件”;
  在我们写的程序中,需要把特定的数据按照一定的结构和顺序写到文件中保存。——这样的文件叫“自定义结构化文件”;(比如 *.bmp 文件)
  以上三种类型的文件,大家都见的多了。那么文件存储就依靠上述的方式能满足所有的应用需求吗?恩~~~,至少从计算机发明后的50多年来,一直是够用的了。嘿嘿,下面看看商业利益的推动作用,对文件 的存储形式产生了什么变化吧。30岁以上的朋友,我估计以前都使用过以下几个著名的软件:WordStar(独霸DOS下的英文编辑软件),WPS(裘伯君写的中文编辑软件,据说当年的市场占有率高达90%,各种计算机培训班的必修课程),LOTUS-123(莲花公司出品的电子表格软件)......
微软在成功地推出 Windows 3.1 后,开始垂涎桌面办公自动化软件领域。微软的 OFFICE 开发部门,各小组分别独立地开发了 WORD 和 EXCEL 等软件,并采用“自定义结构”方式,对文件进行存储。在激烈的市场竞争下,为了打败竞争对手,微软自然地产生了一个念头------如果我能在 WORD 程序中嵌入 EXCEL,那么用户在购买了我 WORD 软件的情况下,不就没有必要再买 LOTUS-123 了吗?!“恶毒”(中国微软的同志们看到了这个词,不要激动,我是加了引号的呀)的计划产生后,他们开始了实施工作,这就是 COM 的前身 OLE 的起源(注3)。但立刻就遇到了一个严重的技术问题:需要把 WORD 产生的 DOC 文件和 EXCEL 产生的 XLS 文件保存在一起。

方案

优点

缺点

建立一个子目录,把 DOC、XLS 存储在这同一个子目录中。 数据隔离性好,WORD 不用了解 EXCEL 的存储结构;容易扩展。 结构太松散,容易造成数据的损坏或丢失。
不易携带。
修改文件存储结构,在DOC结构基础上扩展出包容 XLS 的结构。 结构紧密,容易携带和统一管理。 WORD 的开发人员需要通晓 EXCEL 的存储格式;缺少扩展性,总不能新加一个类型就扩展一下结构吧?!

  以上两个方案,都有严重的缺陷,怎么解决那?如果能有一个新方案,能够合并前两个方案的优点,消灭缺点,该多好呀......微软是作磁盘操作系统起家的,于是很自然地他们提出了一个非常完美的设计方案,那就是把磁盘文件的管理方式移植到文件中了------复合文件,俗称“文件中的文件系统”。连微软当年都没有想到,就这么一个简单的想法,居然最后就演变出了 COM 组件程序设计的方法。可以说,复合文件是 COM 的基石。下图是磁盘文件组织方式与复合文件组织方式的类比图:


图一、左侧表示一个磁盘下的文件组织方式,右侧表示一个复合文件内部的数据组织方式。

三、复合文件的特点
  1、
复合文件的内部是使用指针构造的一棵树进行管理的。编写程序的时候要注意,由于使用的是单向指针,因此当做定位操作的时候,向后定位比向前定位要快;
  2、
复合文件中的“流对象”,是真正保存数据的空间。它的存储单位为512字节。也就是说,即使你在流中只保存了一个字节的数据,它也要占据512字节的文件空间。啊~~~,这也太浪费了呀?不浪费!因为文件保存在磁盘上,即使一个字节也还要占用一个“簇”的空间那;
  3、
不同的进程,或同一个进程的不同线程可以同时访问一个复合文件的不同部分而互不干扰;
  4、
大家都有这样的体会,当需要往一个文件中插入一个字节的话,需要对整个文件进行操作,非常烦琐并且效率低下。而复合文件则提供了非常方便的“增量访问”能力;
  5、当频繁地删除文件,复制文件后,磁盘空间会变的很零碎,需要使用磁盘整理工具进行重新整合。和磁盘管理非常相似,复合文件也会产生这个问题,在适当的时候也需要整理,但比较简单,只要调用一个函数就可以完成了。

四、浏览复合文件
  VC6.0 附带了一个工具软件“复合文件浏览器”,文件名是“vc目录\Common\Tools\DFView.exe”。为了方便使用该程序,可以把它加到工具(tools)菜单中。方法是:Tools\Customize...\Tools卡片中增加新的项目。运行 DFView.exe,就可以打开一个复合文件进行观察了(注4)。但奇怪的是,在 Microsoft Visual Studio .NET 2003 中,我反而找不到这个工具程序了,汗!不过这恰好提供给大家一个练习的机会,在你阅读完本篇文章并掌握了编程方法后,自己写一个“复合文件浏览编辑器”程序,又练手了,还有实用的价值。

五、复合文件函数
  复合文件的函数和磁盘目录文件的操作非常类似。所有这些函数,被分为3种类型:WIN API 全局函数,存储 IStorage 接口函数,流 IStream 接口函数。什么是接口?什么是接口函数?以后的文章中再陆续介绍,这里大家只要把“接口”看成是完成一组相关操作功能的函数集合就可以了。

WIN API 函数

功能说明

StgCreateDocfile() 建立一个复合文件,得到根存储对象
StgOpenStorage() 打开一个复合文件,得到根存储对象
StgIsStorageFile() 判断一个文件是否是复合文件

 

IStorage 函数

功能说明

CreateStorage() 在当前存储中建立新存储,得到子存储对象
CreateStream() 在当前存储中建立新流,得到流对象
OpenStorage() 打开子存储,得到子存储对象
OpenStream() 打开流,得到流对象
CopyTo() 复制存储下的所有对象到目标存储中,该函数可以实现“整理文件,释放碎片空间”的功能
MoveElementTo() 移动对象到目标存储中
DestoryElement() 删除对象
RenameElement() 重命名对象
EnumElements() 枚举当前存储中所有的对象
SetElementTimes() 修改对象的时间
SetClass() 在当前存储中建立一个特殊的流对象,用来保存CLSID(注5)
Stat() 取得当前存储中的系统信息
Release() 关闭存储对象
 

IStream 函数

功能说明

Read() 从流中读取数据
Write() 向流中写入数据
Seek() 定位读写位置
SetSize() 设置流尺寸。如果预先知道大小,那么先调用这个函数,可以提高性能
CopyTo() 复制流数据到另一个流对象中
Stat() 取得当前流中的系统信息
Clone() 克隆一个流对象,方便程序中的不同模块操作同一个流对象
Release() 关闭流对象
 
WIN API 补充函数 功能说明
WriteClassStg() 写CLSID到存储中,同IStorage::SetClass()
ReadClassStg() 读出WriteClassStg()写入的CLSID,相当于简化调用IStorage::Stat()
WriteClassStm() 写CLSID到流的开始位置
ReadClassStm() 读出WriteClassStm()写入的CLSID
WriteFmtUserTypeStg() 写入用户指定的剪贴板格式和名称到存储中
ReadFmtUserTypeStg() 读出WriteFmtUserTypeStg()写入的信息。方便应用程序快速判断是否是它需要的格式数据。
CreateStreamOnHGlobal() 内存句柄 HGLOBAL 转换为流对象
GetHGlobalFromStream() 取得CreateStreamOnHGlobal()调用中使用的内存句柄

  为了让大家快速地浏览和掌握基本方法,上面所列表的函数并不是全部,我省略了“事务”函数和未实现函数部分。更全面的介绍,请阅读 MSDN。
下面程序片段,演示了一些基本函数功能和调用方法。
示例一:建立一个复合文件,并在其下建立一个子存储,在该子存储中再建立一个流,写入数据。

  1. void SampleCreateDoc()  
  2. {  
  3.   ::CoInitialize(NULL);   // COM 初始化  
  4.                // 如果是MFC程序,可以使用AfxOleInit()替代  
  5.   HRESULT hr;   // 函数执行返回值  
  6.   IStorage *pStg = NULL; // 根存储接口指针  
  7.   IStorage *pSub = NULL; // 子存储接口指针  
  8.   IStream *pStm = NULL; // 流接口指针  
  9.   
  10.   hr = ::StgCreateDocfile(  // 建立复合文件  
  11.         L"c:\\a.stg"// 文件名称  
  12.         STGM_CREATE | STGM_WRITE | STGM_SHARE_EXCLUSIVE, // 打开方式  
  13.         0, // 保留参数  
  14.         &pStg); // 取得根存储接口指针  
  15.   ASSERT( SUCCEEDED(hr) ); // 为了突出重点,简化程序结构,所以使用了断言。  
  16.                  // 在实际的程序中则要使用条件判断和异常处理  
  17.   
  18.   hr = pStg->CreateStorage( // 建立子存储  
  19.         L"SubStg"// 子存储名称  
  20.         STGM_CREATE | STGM_WRITE | STGM_SHARE_EXCLUSIVE,  
  21.         0,0,  
  22.         &pSub); // 取得子存储接口指针  
  23.   ASSERT( SUCCEEDED(hr) );  
  24.   
  25.   hr = pSub->CreateStream(  // 建立流  
  26.         L"Stm"// 流名称  
  27.         STGM_CREATE | STGM_WRITE | STGM_SHARE_EXCLUSIVE,  
  28.         0,0,  
  29.         &pStm); // 取得流接口指针  
  30.   ASSERT( SUCCEEDED(hr) );  
  31.   
  32.   hr = pStm->Write( // 向流中写入数据  
  33.         "Hello"// 数据地址  
  34.         5, // 字节长度(注意,没有写入字符串结尾的\0)  
  35.         NULL); // 不需要得到实际写入的字节长度  
  36.   ASSERT( SUCCEEDED(hr) );  
  37.   
  38.   if( pStm ) pStm->Release(); // 释放流指针  
  39.   if( pSub ) pSub->Release(); // 释放子存储指针  
  40.   if( pStg ) pStg->Release(); // 释放根存储指针  
  41.   
  42.   ::CoUninitialize() // COM 释放  
  43.            // 如果使用 AfxOleInit(),则不调用该函数  
  44. }  

 


图二、运行示例程序一后,使用 DFView.exe 打开观察复合文件的效果图

示例二:打开一个复合文件,枚举其根存储下的所有对象。

  1. #include // ANSI、MBCS、UNICODE 转换  
  2. void SampleEnum()   
  3. {   
  4.   // 假设你已经做过 COM 初始化了  
  5.   LPCTSTR lpFileName = _T( "c:\\a.stg" );  
  6.   HRESULT hr;  
  7.   IStorage *pStg = NULL;  
  8.   
  9.   USES_CONVERSION; // (注6)  
  10.   LPCOLESTR lpwFileName = T2COLE( lpFileName ); // 转换T类型为宽字符  
  11.   hr = ::StgIsStorageFile( lpwFileName ); // 是复合文件吗?  
  12.   if( FAILED(hr) )  
  13.     return;  
  14.   hr = ::StgOpenStorage( // 打开复合文件  
  15.         lpwFileName, // 文件名称  
  16.         NULL,  
  17.         STGM_READ | STGM_SHARE_DENY_WRITE,  
  18.         0,  
  19.         0,  
  20.         &pStg); // 得到根存储接口指针  
  21.   IEnumSTATSTG *pEnum=NULL; // 枚举器  
  22.   hr = pStg->EnumElements( 0, NULL, 0, &pEnum );  
  23.   ASSERT( SUCCEEDED(hr) );  
  24.   STATSTG statstg;  
  25.   while( NOERROR == pEnum->Next( 1, &statstg, NULL) )  
  26.   {  
  27.     // statstg.type 保存着对象类型 STGTY_STREAM 或 STGTY_STORAGE  
  28.     // statstg.pwcsName 保存着对象名称  
  29.     // ...... 还有时间,长度等很多信息。请查看 MSDN  
  30.     ::CoTaskMemFree( statstg.pwcsName ); // 释放名称所使用的内存(注6)  
  31.   }  
  32.   
  33.   if( pEnum ) pEnum->Release();  
  34.   if( pStg ) pStg->Release();  
  35. }  

 

六、小结
  复合文件,结构化存储,是微软组件思想的起源,在此基础上继续发展出了持续性、命名、ActiveX、对象嵌入、现场激活......一系列的新技术、新概念。因此理解和掌握 复合文件是非常重要的,即使在你的程序中并没有全面使用组件技术,复合文件技术也是可以单独被应用的。祝大家学习快乐,为社会主义软件事业而奋斗:-)

留作业啦......
作业1:写个小应用程序,从 MSWORD 的 doc 文件中,提取出附加信息(作者、公司......)。
作业2:写个全功能的“复合文件浏览编辑器”。

注1:踅摸(xuemo),动词,北方方言,寻找搜索的意思。
注2:问:为什么不上网查资料学习?
答:开什么国际玩笑!在那遥远的1995年代,我的500块工资,不吃不喝正好够上100小时的Internet网。
注3:OLE,对象的连接与嵌入。
注4:可以用 DFView.exe 打开 MSWORD 的 DOC 文件进行复合文件的浏览。但是该程序并没有实现国际化,不能打开中文文件名的复合文件,因此需要改名后才能浏览。
注5:CLSID,在后续的文章中介绍。
注6:关于 COM 中内存使用的问题,在后续的文章中介绍。

posted @ 2012-10-17 22:23 jackdong 阅读(361) | 评论 (0)编辑 收藏

      (为了方便大家下,我打包了放在一下地址:

1-6:http://download.csdn.net/detail/wangqiulin123456/4601530

7-12:http://download.csdn.net/detail/wangqiulin123456/4601508

13-16:http://download.csdn.net/detail/wangqiulin123456/4601493

          来自微软的权威技术专家将向您解释Windows操作系统的内部工作原理,从系统架构的大局观出发,逐步展示进程、线程、安全机制、内存管理和存储管理等子系统的工作方式。通过对底层原理的揭示,使您更进一步的理解Windows上各类程序的工作方式和如何进行错误诊断及性能优化。 本次课程的内容编排得到了国内知名技术作家,《Windows Internals》一书的中文译者,潘爱民先生的大力支持,同时TechNet也邀请到了众多微软一线技术专家进行讲解。这是一个为IT专业人员量身定做的Windows内部知识课程,在介绍原理的同时,也紧密地围绕实际案例和常见的故障进行分析点评。这是一个系统的学习Windows底层工作机制的好机会,课程内容深入浅出,精彩纷呈,绝对不容错过。

深入研究Windows内部原理系列之一:Windows的昨天、今天和明天
 

讲师信息:潘爱民
2007年01月25日 14:00-15:30
Level: 300

著名技术作家、微软亚洲研究院研究员潘爱民老师将在这次课程中跟听众分享Windows的发展历程和技术精萃,描绘操作系统的体系架构、Vista的内核变更以及今后版本Windows的发展趋势。

深入研究Windows内部原理系列之二:Windows体系结构-从操作系统的角度
 

讲师信息:张银奎
2007年01月26日 14:00-15:30
Level: 400

操作系统是计算机系统的灵魂和管理中心,也是软件系统中最复杂的部分。本讲座将以生动的讲解和丰富的演示带您领略Windows操作系统的核心架构和主要组件,包括HAL、内核、执行体、系统进程(IDLE、SMSS.EXE、WinLogon.EXE)和Windows子系统(CSRSS.EXE、WIN32K.SYS以及子系统DLL)等。并讨论中断管理、对象管理、和异常分发等系统机制和实现这些机制的基本数据结构。

深入研究Windows内部原理系列之三:Windows体系结构-从应用程序的角度
 

讲师信息:曾震宇
2007年01月29日 14:00-15:30
Level: 400

从服务器软件到Office办公应用,从联网游戏到即时消息,不管这些应用的复杂程度如何,他们都是一个个在操作系统控制和管理之下的可执行程序。本次课程邀请微软全球技术中心专家级工程师,为各位讲解一个程序是如何经历从启动、分配资源、运行、结束这一连串的过程,并且介绍其中的重要概念和排错诊断技巧。

深入研究Windows内部原理系列之四:Windows操作系统中的重要基本概念
 

讲师信息:高宇
2007年01月30日 14:00-15:30
Level: 400

进程、线程、资源分配、内存管理、Win32 API、服务、安全,这些是工作中常常提及但是又无法深入理解的神秘概念。在这次课程中,讲师将介绍Windows中最常见与最重要的一些基本概念. 使大家能够顺利地参与到本系列之后的讨论中去。

深入研究Windows内部原理系列之五:Windows Sysinternals工具集介绍
 

讲师信息:彭爱华
2007年01月31日 14:00-15:30
Level: 400

Sysinternals Suite(Windows Sysinternals工具集)包含一系列免费的系统工具,其中有大名鼎鼎的Process Explorer、FileMon、RegMon等(在Windows Vista下,FileMon和RegMon则被Process Monitor所代替),如果把系统管理员比喻成战士的话,那么Sysinternals Suite就是我们手中的良兵利器。熟悉和掌握这些工具,并且对Windows的体系有一定的了解,将大幅度的提高日常的诊断和排错能力。本课程将以任务驱动的模式,介绍几个经典的应用案例,来介绍Sysinternals Suite的强大功能。

深入研究Windows内部原理系列之六:Vista新特性底层揭秘
 

讲师信息:彭爱华
2007年02月01日 14:00-15:30
Level: 400

Windows Vista绝非仅仅是具有诸如3D切换、毛玻璃等炫目的界面效果,花钱购买了Windows Vista,而仅仅为了使用其界面效果,难免有点“买椟还珠”的感觉。实际上Windows Vista值得称道的是它具有很多全新的安全特性,例如用户帐户控制、IE保护模式、服务隔离和Windows资源保护等等。有了这些全新的安全特性,我们就可以在相当的程度上摆脱恶意软件的滋扰。Windows之父Jim Allchin曾经说过不要满足于只知道How-to、小技巧之类的知识,而是应该深入底层了解其内部原理。只有了解了这些安全特性的内在原理,才能真正了解Windows Vista是怎样精心替我们解决安全问题的,才能真正利用好这些安全特性。本课程将以UAC、IE保护模式为例,介绍这些安全特性的内在原理。

深入研究Windows内部原理系列之七:开机引导过程
 

讲师信息:张银奎
2007年02月02日 14:00-15:30
Level: 400

Windows的启动是一个复杂的过程,从加载器(NTLDR或WinLoad)开始工作到Windows子系统准备就绪,中间经历了若干个复杂的步骤,包括内核和执行体的初始化,创建系统进程和线程,对象管理器初始化基本对象,I/O管理器枚举设备并安装驱动程序,启动SMSS和WinLogon进程,运行Windows子系统进程。本讲座将解析以上各个步骤的来龙去脉,并探讨驱动的加载顺序、用户登录(Gina,SAM数据库,域身份验证)、系统服务程序、Shell等等启动过程密切相关的问题。

深入研究Windows内部原理系列之八:内存管理揭秘
 

讲师信息:徐晓卓
2007年02月05日 14:00-15:30
Level: 400

工欲善其事,必先利其器。如果能够深入了解Windows内存管理机制,那么无论在系统配置还是在故障排错方面,都能让我们直达根源,起到事半功倍的效果。本课程将全面介绍Windows内部内存管理机制,包括寻址原理、进程内存空间分布、核心态用户态内存管理原理以及虚拟内存管理原理等。同时将讨论应用程序中内存的使用问题,内存泄露的发生以及排除方法。

深入研究Windows内部原理系列之九:Windows的安全机制和实现
 

讲师信息:张瞰
2007年02月06日 14:00-15:30
Level: 400

Windows如何从操作系统层面保障所有程序的安全?访问控制列表,令牌、系统帐号、SAM数据库、GINA、交互式登陆、COM+,这些概念如何组成一个完整的Windows安全平台?这次课程将解答您这方面的疑问。

深入研究Windows内部原理系列之十:驱动和硬件的管理
 

讲师信息:张伟伟
2007年02月07日 14:00-15:30
Level: 400

驱动程序如何被Windows识别、加载和管理?随着Windows的发展,驱动程序的类型和作用经历了怎么样的变化?inf文件在驱动安装过程中起到了怎样的作用?Vista的驱动程序有哪些新变化?如果这方面的问题一直困扰着您,那这次课程是绝对不容错过的。

深入研究Windows内部原理系列之十一:存储和文件系统
 

讲师信息:高宇
2007年02月08日 14:00-15:30
Level: 400

课程将在宏观上简要介绍Windows的存储体系, 观察磁盘上的扇区怎样变成用户眼中的文件. 然后深入观察磁盘上的数据结构. 在分析枯燥的16进制数据的同时, 也会和大家讨论一些有趣和常见的错误现象。

深入研究Windows内部原理系列之十二:网络协议的构成和实现
 

讲师信息:高宇
2007年02月09日 14:00-15:30
Level: 400

课程内容包括Windows中的网络组件, 网络协议,重要网络服务的实现与特点。 Windows中的TCP/IP以及其上的服务将是本节的主要部分。

深入研究Windows内部原理系列之十三:如何诊断和调试蓝屏错误
 

讲师信息:张银奎
2007年02月12日 14:00-15:30
Level: 400

当Windows操作系统检测到来源于系统硬件或内核代码的严重错误时,为了避免继续运行可能导致的更严重后果,Windows会通过蓝屏报告错误并让整个系统以可控的方式停止运行(BSOD)。Windows提供了多种方法来诊断和调试蓝屏错误,包括故障转储文件(DUMP)、内核调试以及通过驱动程序注册并接收错误信息。本讲座将解释蓝屏产生的原因和过程,引发蓝屏错误典型的根源,并向您介绍使用WinDbg分析DUMP文件的高级技巧。

深入研究Windows内部原理系列之十四:用户模式的程序排错(上)
 

讲师信息:喻勇
2007年03月09日 14:00-15:30
Level: 400

“该程序执行了非法操作,即将被关闭”,这是我们耳熟能详的出错报告。程序为什么会崩溃?如何发现崩溃的原因并进行解决?在全面了解了Windows的体系结构和程序运行方式后,我们将进一步介绍访问越界、缓冲溢出、内存泄露等故障的原理,并理论联系实际,带领大家使用调试工具来解决一些常见的问题。

深入研究Windows内部原理系列之十五:用户模式的程序排错(下)
 

讲师信息:喻勇
2007年03月13日 14:00-15:30
Level: 400

“该程序执行了非法操作,即将被关闭”,这是我们耳熟能详的出错报告。程序为什么会崩溃?如何发现崩溃的原因并进行解决?在全面了解了Windows的体系结构和程序运行方式后,我们将进一步介绍访问越界、缓冲溢出、内存泄露等故障的原理,并理论联系实际,带领大家使用调试工具来解决一些常见的问题。

深入研究Windows内部原理系列之十六:使您成为Windows专家的一些学习习惯
 

讲师信息:喻勇
2007年03月15日 14:00-15:30
Level: 200

在系统的学习了前面的Windows内部原理之后,大家一定对这么多的技术细节和深入分析大呼过瘾,也一定想尽快地掌握这些知识。如何学好Windows?如何成为一个技术过硬的IT专业人士?作为这个技术大餐的最后一讲,讲师将跟大家分享一些学习的心得,如何找对突破方向和知识重点,循序渐进的进行系统的技术学习。同时也会指出常见的一些学习弊病和改进方法。最后,老师将推荐一些重要的书籍和学习资料供听众参考。

posted @ 2012-10-17 22:21 jackdong 阅读(892) | 评论 (0)编辑 收藏

     摘要: http://blog.csdn.net/wangqiulin123456/article/details/8072545目录(?)[-] 走马观花看COM服务器服务器生命其管理实现接口,从IUnknown开始构造器和析构器AddRef() 和 Release()QueryInterface()深入CoCreateInstance()COM服务器注册创建COM对象——类...  阅读全文
posted @ 2012-10-17 22:15 jackdong 阅读(421) | 评论 (0)编辑 收藏

     摘要: http://blog.csdn.net/wangqiulin123456/article/details/8026270目录(?)[-] COM到底是什么基本元素的定义 创建一个新对象删除对象 创建COM对象删除COM对象接下来将详细讨论IUnknown接口 基本接口――IUnknown仔细做好串处理 WideCharToMultiByte()wcstombs...  阅读全文
posted @ 2012-10-17 22:13 jackdong 阅读(329) | 评论 (0)编辑 收藏

http://www.cnblogs.com/lxconan/archive/2012/09/09/2677957.html

最近从架构的角度做了一个 Windows 8 下 Metro Style 应用程序开发介绍的讲座。以下是讲稿。

如有问题欢迎指正。

下载地址:

1          概述

这篇的标题叫做Windows RT Introduction而非Windows 8 Introduction是想强调此次介绍是从开发人员的角度而不是普通用户的角度出发的。同时,我们关注的是Metro Style应用而不是传统的Win32应用程序的开发。

实际上,使用C#或者HTML + Javascript书写一个Hello world应用的代码例子已经在网上泛滥了。但是仅有一个Hello world并不能够说你掌握了Win RT的开发。从Pro的角度来说我们应该弄清楚整件事情的细节。那么首先就应当是他的架构。这样写起程序来才能心定。

2          Windows 8 Metro与Desktop模式

2.1         两种模式

Windows 8的应用程序显示模式目前有两种,定义在METRO_MONITOR_MODE中:即传统的桌面模式(MMM_Desktop)以及Metro模式(MMM_Metro)。如果你是Windows Phone用户的话可能就会对Metro比较熟悉。事实上,微软在2009年启动Windows 8的研发工作时目标是创造一个完全不同以往的操作系统,完全不以之前的操作系统为蓝本。而后发现Desktop应用是不可或缺的部分而将两个部分进行合并。一开始用可能会有些别扭,但是我估计开发人员半天之内就能够熟练使用这个系统了。

2.2         Metro和Desktop的一些不同

既然有两种模式那么我们自然就会关注他们的不同点。这个问题应该从架构图上做一下说明但是我们可以先有一些直观的认识。

2.2.1          Message Loop

消息处理的编程是传统Desktop应用程序的重要部分。你需要书写维护Message Loop的代码。例如:在WinMain调用(或者其子例程中)你需要书写类似

 

while (::GetMessage(&message, NULL, 0, 0)) {

    ::TranslateMessage(&message);

    ::DispatchMessage(&message);

}

 

而在Window创建之前候你一定指定了

 

WNDCLASS wndClass;

// ...

wndClass.lpfnWndProc = WndProc;

 

这样你就可以在WndProc函数中决定特定message的流向了。对于绘图来说,你一定是接受了WM_PAINT消息,然后执行了区域重绘。

但在Metro App中这些都已经隐藏了,而且消息的细节也可能发生了变化。Metro App中你看不到消息循环。一切关于界面消息的分发都隐藏在了CoreDispatcher中。因此如果你用Spy++去试探Metro App的消息循环那么你什么都抓不到。

2.2.2          Display

在传统的Desktop应用程序中,绘图可能通过GDI,GDI+,DirectDraw,DirectX进行。同样通过捕获WM_PAINT消息或者当系统处于IDLE的时候进行绘图(对于游戏编程来说)。

而Metro App不会再支持GDI和GDI+,在Metro App中绘图只能通过DirectX来进行。确切的说是Direct3D和新公布的Direct2D、Direct Write API。因此Metro应用的所有绘图都是希望是硬件加速的。这种绘图更高效,解放CPU,而且一般不需要处理复杂的Dirty Region Repaint。

2.2.3          Life Cycle

Metro App并没有关闭窗口这种按钮。其生命周期是由系统托管的。系统会决定仅仅是挂起应用执行还是需要完全销毁应用进程。这和一般意义上的Desktop应用程序不一样。(当然,你也可以使用Alt+F4显示的结束Metro App的执行)。

2.2.4          Share & Communication

传统的桌面应用程序有多种手段进行公共组建的公用或IPC。但是在Metro App中,隔离是一个很重要的概念,应用的可执行部分,运行库,Isolated Storage都是独立的,不能够共用。同样,不能够使用传统的IPC机制。应用程序的互动仅仅可以通过内置的Contracts进行,关于这一部分内容可以查看MSDN:

http://msdn.microsoft.com/en-us/library/windows/apps/hh464906.aspx

2.2.5          Portability

传统的Desktop应用程序的支持大多为x86/64架构的处理器。由于Metro环境可以完整运行在ARM处理器上是一个重要的特性,因此Metro App可以运行在ARM处理器上,即同时部署在PC和移动设备上。

2.2.6          OS Access

当然为了Portability的要求,必然要求应用不能够越过Win RT的抽象,因此Metro是不能像Desktop App那样访问所有的Windows API的。

3          从Windows 8 API的架构图看Windows RT

我们对Windows RT的介绍都将围绕着这个图展开。

在这个图中,最底层的是NT的内核;在其上是Windows子系统。实际上NT至少有三个子系统,Windows子系统,POSIX子系统(Unix)和OS/2子系统。POSIX子系统和OS/2子系统实际还是在使用Windows子系统。 在Windows子系统上划分了不同的运行时(橙色)和程序库(浅蓝色),最上面的绿色是我们使用的各种开发语言。

这个架构图实际上说明了一切。并且消除了很多误解:

(1)       第一个误解是INFOQ指出的Windows RT和Win32是完全分开的。这源于微软发布的一幅饱受批评的架构图,在那张架构图中,Windows RT和Windows子系统竟然是并排排列的。这是很荒谬的,Windows RT实际上基于Windows子系统。首先Windows RT完全基于COM;其次Windows RT利用了一部分现有的Win32 API;其余的部分Windows RT则直接访问NT内核。

(2)       第二个误解是C++/CX。C++/CX是微软推荐的开发Windows RT的方式。他主要隐藏了COM的复杂性。关于这个问题我们后续会有说明。这个误解是C++/CX实际就是C++ CLI。实际上这是两个完全不同的东西,C++ CLI是运行在托管环境下的,而C++/CX完全是Native的。

3.1         Windows RT仅用于Metro应用

从架构图中可以看出,Win RT仅仅用于Metro应用。并秉承了我们刚才介绍的,简单部署,没有共享的组件,没有IPC,等等。

3.2         Windows RT构建与COM之上

这也是为什么说Windows RT是构建与Win32之上,因为COM是Win32重要的组成部分。这意味着:

(1)       你可以用之前所有的消费COM的方式来使用Windows RT,你可以用C,你可以用ATL或者新的WRL;

(2)       WRL完全符合传统的C++语法,这意味着你可以使用不同的编译器(例如Intel C++编译器)来构建Metro应用。但是微软显然希望大家都来使用C++/CX,WRL的文档跟没有差不多,现在也看不到一个完整的例子出现。

3.3         Windows RT限制了系统API的调用

Win RT是基于COM的,但是COM仅仅是一个二进制协议而已。在COM Interface实现中从技术上讲还是在调用Win32 API。但由于前面介绍的Win RT的设计要求,系统API的调用需要受到严格的限制。仅仅支持有限的API调用,因此在你希望使用一个Win32 API时,一定要查询MSDN上的Applied To一节,看看是否是Metro Style App | desktop App。

同样的道理,.NET的某些方法也在进行着系统调用,因此在使用.NET开发Metro Style应用程序的时候也并不是所有的程序集都能够支持。当然,如果使用P-Invoke的方式调用Win32 API那么危险性就会更大。

总之,在Metro应用中调用不支持的Win32 API会有如下的后果:

(1)       发生一个Runtime Exception;

(2)       应用程序失去响应,尤其是在使用和消息循环相关的代码时。例如对Metro App进程使用WaitForSingleObject(hProcess)。

(3)       调用成功,但是你的Metro App应用会被Windows Store驳回。

按照上述分析,那么即使你存在相当可观的COM代码库,也需要巨大的努力才能够保证他们在Metro App上正确运行(消除非法的系统调用)。对于新的应用来说,为了避免书写大量的COM开发代码,最好使用C++/CX进行开发了。

3.4         C++/CX

为什么会有C++/CX呢?这可以联想n年前我们为了避免C++开发COM的冗长的代码,转而使用C开发关键程序,而使用Visual Basic创建COM组件。现在时间到了2012年,VB6已经不在考虑范围之内了,于是C++/CX取代了他的位置。

C++/CX是Native的,但是它的语法为什么能够和C++ CLI保持近乎一致呢?这是因为Win RT本身虽然是Native的,但它以.NET兼容的方式暴露了元数据。但是我们在编程中要时刻想到,我们在操作实打实的Native对象。根本没有什么垃圾收集器在帮助我们。

那么为什么不单纯使用.NET开发Metro App呢?这是因为对于移动设备来说,CPU的速度和电池是两大局限,因此在近一年,Go Native的大潮终于袭来。目前:

(1)       iOS使用Objective-C进行程序开发,而且在移动设备上也是没有垃圾收集器的,需要手动释放使用的内存;

(2)       Android一开始使用Java进行开发,但是在糟糕的性能和社区的强大压力下,终于开放了C/C++开发接口;

(3)       WP7/8也出现了类似Android的情况。

目前客户端应用向更薄(核心应用向服务器移动),更快(运行速度快,耗电小),交互更丰富(没有动画你都对不起观众)的方向发展。因此开放Native接口是大势所趋,C/C++顺理成章的在Windows 8强势回归了。

但是,用.NET开发Metro应用也是一个不错的选择,尤其你的应用没有密集的运算(游戏)的情况下。你可以参考幻灯片中的Cheat Sheet。

posted @ 2012-10-16 23:56 jackdong 阅读(505) | 评论 (0)编辑 收藏

仅列出标题
共10页: First 2 3 4 5 6 7 8 9 10