📄 csdn_文档中心_com深入理解(下)——方法参数类型为cruntimeclass、void等.htm
字号:
document.write(".");
document.write(date);
// -->
</SCRIPT>
</B> </TD></TR>
<TR bgColor=#999999>
<TD colSpan=3 height=1></TD></TR></TBODY></TABLE>
<TABLE border=0 width=770>
<TBODY>
<TR>
<TD align=middle bgColor=#fafafa class=td1 vAlign=top width=150><BR>
<SCRIPT
src="CSDN_文档中心_COM深入理解(下)——方法参数类型为CRuntimeClass、void等.files/microsoft.js"></SCRIPT>
</TD>
<TD align=middle width=620>
<TABLE bgColor=#eeeeee border=0 cellPadding=0 cellSpacing=0 width=600>
<TBODY>
<TR bgColor=#ffffff>
<TD align=middle height=10 width=50></TD>
<TD align=right><A href="http://www.csdn.net/">CSDN</A> - <A
href="http://www.csdn.net/develop/">文档中心</A> - <FONT
color=#003399>Visual C++</FONT> </TD></TR>
<TR>
<TD align=middle height=5></TD>
<TD align=middle width=500></TD></TR>
<TR>
<TD align=middle bgColor=#003399 height=10><FONT
color=#ffffff>标题</FONT></TD>
<TD><B> COM深入理解(下)——方法参数类型为CRuntimeClass*、void*等</B> lop5712(原作)
</TD></TR>
<TR>
<TD align=middle height=5></TD>
<TD align=middle width=500></TD></TR>
<TR>
<TD align=middle bgColor=#003399><FONT color=#ffffff>关键字</FONT></TD>
<TD width=500> COM void*</TD></TR>
<TR>
<TD align=middle height=5></TD>
<TD align=middle width=500></TD></TR></TBODY></TABLE><!--文章说明信息结束//-->
<TABLE border=0 width=600>
<TBODY>
<TR>
<TD align=left><BR><FONT
style="FONT-FAMILY: '宋体'; FONT-SIZE: 10pt; LINE-HEIGHT: 150%">
<P align=center><FONT face=楷体_GB2312
size=5><STRONG>COM深入理解(下)</STRONG></FONT> </P>
<P align=right><FONT face=楷体_GB2312
size=4><STRONG>——方法参数类型为CRuntimeClass*、void*等</STRONG></FONT> </P>
<P>
本文上篇已经说明了类对象实际是一个结构实例,并且为了实现进程间传递类对象指针以达到引用的目的,需要为此类专门编写一个代理类,并在传递时例示(即实例化)其一个对象以实现代理对象。而此代理类必定分成两部分,即一部分的成员函数专门在客户进程被调用,另一部分专门在组件进程被调用以实现进程间的数据传递进而将客户的调用命令原封不动地传递给组件对象以实现客户操作组件对象。<BR>
上面的做法实际就是编写自定义汇集操作时应该干的事,只不过还需照着COM的几个附加规定来做,如必须实现IMarshal接口等。本文说明如何为这样的类型传递编写标准的代理/占位组件以跨进程传递类对象的指针(使用MIDL来完成)。</P>
<P>
为了在客户端生成一个代理对象,必须将某些信息传递过去,然后在客户端根据传递的信息构建一个代理对象。在IDL语言的类型定义中,没有类这种类型,因此是不可能让接口方法的参数类型为某个自定义类的指针。但是的确有此需要,则只能将类对象指针转成某种IDL中识别的类型,最好的候选人就是void*,然后借助MIDL生成的代码将构建代理对象的信息传递过去。<BR>
void*不带有任何语义,其仅表示一个地址,因此在IDL中传递void*是错误的,因为MIDL无法根据void*所带的语义确定应该如何汇集其指向内存中的内容。但是MIDL还是提供了多种途径来解决这个问题的,下面仅说明其中两个用得最多的方法:[call_as()]属性和[wire_marshal()]属性。</P>
<P><BR><FONT face=楷体_GB2312
size=4><STRONG>[local]和[call_as()]</STRONG></FONT></P>
<P> <STRONG>[local]</STRONG>
接口或接口方法都可以加上[local]属性以表示此方法或此接口中的方法不需要生成汇集代码,进而就避免了上面由于void*不带有任何语义而不能汇集其指向内容这个问题,因为不需要生成汇集代码,进而其所修饰的方法的参数可以为void*。此属性所修饰的方法或接口被称为本地方法或本地接口,因为这些方法没有汇集代码,不能进行远程调用。这在COM的标准接口中应用十分广泛。如查看IUnknown的IDL代码,其就是一个本地接口。再如查看IClassFactory接口的IDL定义,如下:<BR>[<BR>
<SPAN style="COLOR: blue">object</SPAN>,<BR> <SPAN
style="COLOR: blue">uuid</SPAN>(00000001-0000-0000-C000-000000000046),<BR>
<SPAN style="COLOR: blue">pointer_default</SPAN>(<SPAN
style="COLOR: blue">unique</SPAN>)<BR>]<BR><SPAN
style="COLOR: blue">interface</SPAN> IClassFactory :
IUnknown<BR>{<BR> <SPAN
style="COLOR: blue">typedef</SPAN> [<SPAN
style="COLOR: blue">unique</SPAN>] IClassFactory *
LPCLASSFACTORY;<BR> [<SPAN
style="COLOR: blue">local</SPAN>]<BR> HRESULT
CreateInstance(<BR> [<SPAN
style="COLOR: blue">in</SPAN>, <SPAN
style="COLOR: blue">unique</SPAN>] IUnknown *
pUnkOuter,<BR> [<SPAN
style="COLOR: blue">in</SPAN>] REFIID
riid,<BR> [<SPAN
style="COLOR: blue">out</SPAN>, <SPAN
style="COLOR: blue">iid_is</SPAN>(riid)] <SPAN
style="COLOR: blue">void</SPAN> **ppvObject);<BR>
[<SPAN
style="COLOR: blue">call_as</SPAN>(CreateInstance)]<BR>
HRESULT
RemoteCreateInstance(<BR>
[<SPAN style="COLOR: blue">in</SPAN>] REFIID
riid,<BR> [<SPAN
style="COLOR: blue">out</SPAN>, <SPAN
style="COLOR: blue">iid_is</SPAN>(riid)] IUnknown **
ppvObject);<BR> [<SPAN
style="COLOR: blue">local</SPAN>]<BR> HRESULT
LockServer(<BR> [<SPAN
style="COLOR: blue">in</SPAN>] BOOL fLock);<BR>
[<SPAN
style="COLOR: blue">call_as</SPAN>(LockServer)]<BR>
HRESULT <SPAN style="COLOR: blue">__stdcall</SPAN>
RemoteLockServer(<BR>
[<SPAN style="COLOR: blue">in</SPAN>] BOOL
fLock);<BR>}<BR>
其中的CreateInstance和LockServer就是本地函数,MIDL将不会为这两个函数生成汇集代码,也就是代理/占位代码,其表现就是类似下面的两个函数原型的代码:<BR>HRESULT
STDMETHODCALLTYPE IClassFactory_LockServer_Proxy( IClassFactory *
This,<BR>
BOOL fLock );<BR>HRESULT STDMETHODCALLTYPE
IClassFactory_LockServer_Stub( IClassFactory *
This,<BR>
BOOL fLock );<BR>
也就是说,当在.idl文件中检测到一个接口方法的定义时,MIDL都会为这个方法生成两个附加的函数,名字分别为<InterfaceName>_<MethodName>_Proxy和<InterfaceName>_<MethodName>_Stub,以分别作为代理和占位的代码。如上面的RemoteCreateInstance,将生成IClassFactory_RemoteCreateInstance_Proxy和IClassFactory_RemoteCreateInstance_Stub这么两个函数的声明和定义。<BR>
但是当方法被[local]属性修饰时,则不会生成上面的两个函数的声明和定义,因为它们被假定一定用于直接调用,不会有汇集的需要,因此没有汇集代码,并被称为本地方法。但它们还是会被加入接口这个函数指针数组的行列,即生成的接口头文件中依旧可以看见这类方法的声明(但是在类型库中却没有,这可以认为是MIDL的一个BUG,不过是可以绕过的)。<BR>
<STRONG>[call_as()]</STRONG>
接口方法可以被加上[call_as()]属性进行修饰,以指定此方法将被作为括号中指定的本地方法调用的替代品,即被作为什么调用。它不像[local]属性修饰的方法,其依旧会生成汇集代码,但却不会出现在接口中,即生成的头文件中,看不见这类方法的声明(但是在类型库中却看得见,这是一个BUG,可以通过预定义宏绕过)。此被称为方法别名,因为其将两个方法关联了起来,其中一个([local]修饰的)是另一个([call_as]修饰的)的别名,被实际使用。<BR>
如前面的RemoteLockServer就带有属性[call_as(LockServer)]以表示此函数是当客户调用LockServer时,并且需要进行汇集操作时调用的。将[local]修饰的方法称为本地版,[call_as()]修饰称为远程版,则可以认为远程版函数解决了本地版函数没有生成汇集代码的问题,因为本地版函数可能有某些特殊要求(如参数类型为void*)而不能生成汇集代码。<BR>
既然[call_as()]产生了一个函数别名,对两个函数进行了关联,因此必须有一种机制实现这种关联。MIDL就是通过要求开发人员自己编写本地版方法的汇集代码来实现这个关联关系。对于上面的LockServer,MIDL将会为其生成两个函数原型,如下:<BR>HRESULT
STDMETHODCALLTYPE IClassFactory_LockServer_Proxy( IClassFactory *
This,<BR>
BOOL fLock );<BR>HRESULT <SPAN style="COLOR: blue">__stdcall
</SPAN>IClassFactory_LockServer_Stub( IClassFactory *
This,<BR>
BOOL fLock );<BR>
但仅仅是原型,即声明,没有定义。因此开发人员需自己编写上面两个函数的定义。<SPAN
style="COLOR: red">注意:虽然名字是IClassFactory_LockServer_Stub,但它的原型正好和RemoteLockServer对调,以实现将远程版函数传递过来的参数再转成本地版的参数。</SPAN><BR>
因此关联的过程就是:客户调用IClassFactory_LockServer_Proxy,然后开发人员编写此函数,并在其中将传进来的MIDL不能或不希望被处理的参数类型转成IClassFactory_RemoteLockServer_Proxy的参数形式,并调用之以传递参数。在组件端,COM运行时期库调用开发人员编写的IClassFactory_LockServer_Stub(注意:此函数的原型不是LockServer,而是RemoteLockServer)以将通过网络传过来的参数换成原始的MIDL不能或不希望被处理的参数形式,并调用传进来的IClassFactory*参数的LockServer方法以实现调用了组件对象的方法,然后返回。下面举个简例:<BR>
有个自定义类CA,如下:<BR><SPAN style="COLOR: blue">class</SPAN>
CA<BR>{<BR> <SPAN style="COLOR: blue">long</SPAN>
m_a, m_b;<BR><SPAN
style="COLOR: blue">public</SPAN>:<BR> <SPAN
style="COLOR: blue">long</SPAN> GetA();<BR> <SPAN
style="COLOR: blue">void</SPAN> SetA( <SPAN
style="COLOR: blue">long</SPAN> a );<BR>};<BR>
欲在下面的接口中传递其对象指针:<BR><SPAN
style="COLOR: green">///////////////////////abc.idl/////////////////////////</SPAN><BR><SPAN
style="COLOR: blue">import</SPAN> "oaidl.idl";<BR><SPAN
style="COLOR: blue">import</SPAN>
"ocidl.idl";<BR><BR>[<BR> <SPAN
style="COLOR: blue">object</SPAN>,<BR> <SPAN
style="COLOR: blue">uuid</SPAN>(1A201ABC-A669-4ac7-9E02-2DA772E927FC),<BR>
<SPAN style="COLOR: blue">pointer_default</SPAN>(<SPAN
style="COLOR: blue">unique</SPAN>)<BR>]<BR><SPAN
style="COLOR: blue">interface</SPAN> IAbc :
IUnknown<BR>{<BR> [<SPAN
style="COLOR: blue">local</SPAN>] HRESULT GetA( [<SPAN
style="COLOR: blue">out</SPAN>] <SPAN
style="COLOR: blue">void</SPAN>* pA );<BR> [<SPAN
style="COLOR: blue">call_as</SPAN>( GetA )] HRESULT RemoteGetA(
[<SPAN style="COLOR: blue">out</SPAN>] <SPAN
style="COLOR: blue">long</SPAN> *pA, [<SPAN
style="COLOR: blue">out</SPAN>] <SPAN
style="COLOR: blue">long</SPAN> *pB );<BR>};<BR>
新建一DLL工程,关掉“预编译头文件”编译开关,将生成的abc_i.c、abc_p.c、dlldata.c和abc.h加到工程中,并建立一个abc.def文件加入到工程中以导出几个必要的用于注册的函数,如下:<BR><SPAN
style="COLOR: green">;;;;;;;;;;;;;;;;;;;;;;;;abc.def;;;;;;;;;;;;;;;;;;;;;;;;;</SPAN><BR>LIBRARY
"abc"<BR>EXPORTS<BR>
DllCanUnloadNow
PRIVATE<BR> DllGetClassObject
PRIVATE<BR> DllRegisterServer
PRIVATE<BR> DllUnregisterServer
PRIVATE<BR> 并新添加一个abc.cpp文件,如下:<BR><SPAN
style="COLOR: green">///////////////////////abc.cpp/////////////////////////</SPAN><BR><SPAN
style="COLOR: blue">#include</SPAN> "abc.h"<BR><SPAN
style="COLOR: blue">#include</SPAN> <new><BR><BR><SPAN
style="COLOR: blue">class</SPAN> CA<BR>{<BR><SPAN
style="COLOR: blue">public</SPAN>:<BR> <SPAN
style="COLOR: blue">long</SPAN> m_a, m_b;<BR>
<SPAN style="COLOR: blue">long</SPAN> GetA();<BR>
<SPAN style="COLOR: blue">void</SPAN> SetA( <SPAN
style="COLOR: blue">long</SPAN> a );<BR>};<BR>HRESULT
STDMETHODCALLTYPE IAbc_GetA_Proxy( IAbc *This, <SPAN
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -