📄 csdn_文档中心_com深入理解(下)——方法参数类型为cruntimeclass、void等.htm
字号:
style="COLOR: blue">void</SPAN> *pA )<BR>{<BR>
<SPAN style="COLOR: blue">if</SPAN>( !pA
)<BR> <SPAN
style="COLOR: blue">return</SPAN>
E_INVALIDARG;<BR> CA *pAA = <SPAN
style="COLOR: blue">reinterpret_cast</SPAN>< CA* >( pA
);<BR><BR><SPAN style="COLOR: green">//
调用远程版的代理函数以传递参数,由MIDL生成</SPAN><BR> <SPAN
style="COLOR: blue">return</SPAN> IAbc_RemoteGetA_Proxy( This,
&pAA->m_a, &pAA->m_b );<BR>}<BR>HRESULT
STDMETHODCALLTYPE IAbc_GetA_Stub( IAbc *This, <SPAN
style="COLOR: blue">long</SPAN> *pA, <SPAN
style="COLOR: blue">long</SPAN> *pB )<BR>{<BR>
<SPAN style="COLOR: blue">void</SPAN> *p = CoTaskMemAlloc( <SPAN
style="COLOR: blue">sizeof</SPAN>( CA ) );<BR>
<SPAN style="COLOR: blue">if</SPAN>( !p
)<BR> <SPAN
style="COLOR: blue">return</SPAN> E_FAIL;<BR> CA
*pAA = <SPAN style="COLOR: blue">new</SPAN>( p ) CA; <SPAN
style="COLOR: green">// 生成一个类对象</SPAN><BR><BR><SPAN
style="COLOR: green">// 调用对象的本地方法</SPAN><BR>
HRESULT hr = This->GetA( pAA );<BR> <SPAN
style="COLOR: blue">if</SPAN>( SUCCEEDED( hr )
)<BR>
{<BR> *pA =
pAA->m_a;<BR> *pB =
pAA->m_b;<BR> }<BR><BR><SPAN
style="COLOR: green">// 释放资源</SPAN><BR>
pAA->~CA();<BR> CoTaskMemFree( p
);<BR> <SPAN style="COLOR: blue">return</SPAN>
hr;<BR>}<BR>
最后添加预定义宏REGISTER_PROXY_DLL和_WIN32_WINNT=0x500,并连接rpcrt4.lib库文件,确保没有打开/TC或/TP编译开关以保证对上面的abc.cpp进行C++编译,而对MIDL生成的.c的源文件进行C编译。<BR>
使用时如下:<BR>IAbc *pA; <SPAN style="COLOR: green">//
假设已初始化</SPAN><BR>CA a;<BR>pA->GetA( <SPAN
style="COLOR: blue">reinterpret_cast</SPAN>< <SPAN
style="COLOR: blue">void</SPAN>* >( &a )
);<BR> 而组件实现的代码如下:<BR>STDMETHODIMP CAbc::GetA(
<SPAN style="COLOR: blue">void</SPAN> *pA
)<BR>{<BR> <SPAN style="COLOR: blue">if</SPAN>(
!pA )<BR> <SPAN
style="COLOR: blue">return</SPAN>
E_INVALIDARG;<BR><BR> *<SPAN
style="COLOR: blue">reinterpret_cast</SPAN>< CA* >( pA ) =
m_A;<BR> <SPAN style="COLOR: blue">return</SPAN>
S_OK;<BR>}<BR>
如上就实现了将类CA的对象进行传值操作,但不是传址操作。前面已说明,欲进行后者,必须编写相应的代理类。先使用上面的方法将必要的信息传递后,再根据传递的信息初始化类CA的代理对象以建立连接。一般如非得已最好不要编写代理对象,而通过将类转成接口形式,由MIDL辅助生成代理/占位组件以变相实现。<BR>
下面介绍使用[wire_marshal()]属性进行传值操作。</P>
<P><BR><FONT face=楷体_GB2312
size=4><STRONG>[wire_marshal()]</STRONG></FONT></P>
前面使用方法别名机制实现了传递自定义数据类型,但是其是以方法为单位进行处理的,当要多次使用某一个数据类型时,如前面的CA*,如果对每个使用到CA*的方法都进行上面的操作,很明显地效率低下,为此MIDL提供了[wire_marshal()]属性(当然不止这么一个属性)。<BR>
[wire_marshal()]属性只能用于类型定义,即typedef中,使用语法如下:<BR><SPAN
style="COLOR: blue">typedef</SPAN> [wire_marshal(<I>wire_type</I>)]
<I>type-specifier</I> <I>userm-type</I>;<BR>
其将一个线类型(wire-type,即MIDL可以直接处理的类型)和一个描述类型(type-specifier,即不能或不打算被MIDL处理的特殊数据类型)相关联,并用一个可识别名字(userm-type)标识。其和[transmit_as()]属性类似,都是将两个类型进行关联,就如前面的[local]和[call_as()]将两个方法进行关联一样,只不过[wire_marshal()]是直接将描述类型按IDL的列集格式(网络数据描述NDR——Network
Data
Representation)列集到指定的缓冲区中,而[transmit_as()]还需汇集代码在中间再转换一次,因此[wire_marshal()]的效率要更高,只不过由于需要编写列集代码,因此需要了解NDR格式,处理数据对齐等问题,所以显得麻烦和复杂。最常见的应用就是句柄的定义,如下:<BR><SPAN
style="COLOR: blue">typedef</SPAN> <SPAN
style="COLOR: blue">union</SPAN> _RemotableHandle <SPAN
style="COLOR: blue">switch</SPAN>( <SPAN
style="COLOR: blue">long</SPAN> fContext )
u<BR>{<BR> <SPAN style="COLOR: blue">case</SPAN>
WDT_INPROC_CALL: <SPAN
style="COLOR: blue">long</SPAN>
hInproc;<BR> <SPAN style="COLOR: blue">case</SPAN>
WDT_REMOTE_CALL: <SPAN
style="COLOR: blue">long</SPAN> hRemote;<BR>}
RemotableHandle;<BR><SPAN style="COLOR: blue">typedef</SPAN> [<SPAN
style="COLOR: blue">unique</SPAN>] RemotableHandle *
wireHWND;<BR><SPAN style="COLOR: blue">#define</SPAN>
DECLARE_WIREM_HANDLE(name)
\<BR>
<SPAN style="COLOR: blue">typedef</SPAN> [wire_marshal(wire ##
name)] <SPAN style="COLOR: blue">void</SPAN> *
name<BR>DECLARE_WIREM_HANDLE( HWND );<BR>
也就是说我们常用的HWND类型是:<BR><SPAN style="COLOR: blue">typedef</SPAN>
[wire_marshal( wireHWND )] <SPAN style="COLOR: blue">void</SPAN>*
HWND;<BR>
即其在应用程序中(即客户或组件,即代理/占位的使用者)是void*类型,当需要传输时,实际是传输结构RemotableHandle的一个实例,而此结构是一个以fContext为标识的联合,实际为8字节长。<BR>
为了实现上面提到的void*和RemotableHandle*的关联,开发人员必须提供下面四个函数的定义:<BR><SPAN
style="COLOR: blue">unsigned long</SPAN> __RPC_USER <
<I>userm-type</I> >_UserSize( <SPAN style="COLOR: green">//
返回欲请求的缓冲区大小</SPAN><BR> <SPAN
style="COLOR: blue">unsigned long</SPAN> __RPC_FAR *pFlags,
<SPAN style="COLOR: green">// 一个标志参数,后叙</SPAN><BR>
<SPAN style="COLOR: green">//
给出当前已经请求的缓冲区大小,返回的大小应该以此作为起点</SPAN><BR> <SPAN
style="COLOR: blue">unsigned long</SPAN>
StartingSize,<BR> < <I>userm-type</I> >
__RPC_FAR * pUser_typeObject ); <SPAN style="COLOR: green">//
欲传递的描述类型的实例</SPAN><BR><SPAN style="COLOR: blue">unsigned char</SPAN>
__RPC_FAR * __RPC_USER < <I>userm-type</I>
>_UserMarshal( <SPAN style="COLOR: green">//
列集</SPAN><BR> <SPAN style="COLOR: blue">unsigned
long</SPAN> __RPC_FAR * pFlags, <SPAN
style="COLOR: green">// 标志参数</SPAN><BR> <SPAN
style="COLOR: blue">unsigned char</SPAN> __RPC_FAR *
Buffer, <SPAN style="COLOR: green">//
已分配的缓冲器有效指针</SPAN><BR> < <I>userm-type</I> >
__RPC_FAR * pUser_typeObject ); <SPAN style="COLOR: green">//
欲列集的描述类型的实例</SPAN><BR><SPAN style="COLOR: blue">unsigned char</SPAN>
__RPC_FAR * __RPC_USER < <I>userm-type</I>
>_UserUnmarshal( <SPAN style="COLOR: green">//
散集</SPAN><BR> <SPAN style="COLOR: blue">unsigned
long</SPAN> __RPC_FAR * pFlags, <SPAN
style="COLOR: green">// 标志参数</SPAN><BR> <SPAN
style="COLOR: blue">unsigned char</SPAN> __RPC_FAR *
Buffer, <SPAN style="COLOR: green">//
列集数据的缓冲器指针</SPAN><BR> <SPAN
style="COLOR: green">//
描述类型的实例指针,从列集数据中散集出描述类型后,放在此指针所指内存之中</SPAN><BR>
< <I>userm-type</I> > __RPC_FAR * pUser_typeObject );<BR><SPAN
style="COLOR: blue">void</SPAN> __RPC_USER < <I>userm-type</I>
>_UserFree( <SPAN style="COLOR: green">//
释放UserUnmarshal中分配的内存</SPAN><BR> <SPAN
style="COLOR: blue">unsigned long</SPAN> __RPC_FAR *
pFlags, <SPAN style="COLOR: green">//
标志参数</SPAN><BR> <SPAN style="COLOR: green">//
UserUnmarshal中的pUser_typeObject参数,一个描述类型的实例的指针</SPAN><BR>
< <I>userm-type</I> > __RPC_FAR * pUser_typeObject
);<BR>
对于前面的HWND,开发人员就必须提供如下四个函数的定义(当然Microsoft是已经提供了的):<BR><SPAN
style="COLOR: blue">unsigned long</SPAN>
__RPC_USER<BR> HWND_UserSize( <SPAN
style="COLOR: blue">unsigned long</SPAN>*, <SPAN
style="COLOR: blue">unsigned long</SPAN>, HWND* );<BR><SPAN
style="COLOR: blue">unsigned char</SPAN>*
__RPC_USER<BR> HWND_UserMarshal</SPAN>( <SPAN
style="COLOR: blue">unsigned long</SPAN>*, <SPAN
style="COLOR: blue">unsigned</SPAN><SPAN style="COLOR: blue">
char</SPAN>*, HWND* );<BR><SPAN style="COLOR: blue">unsigned
char</SPAN>* __RPC_USER<BR>
HWND_UserUnmarshal</SPAN>( <SPAN style="COLOR: blue">unsigned
long</SPAN>*, <SPAN style="COLOR: blue">unsigned char</SPAN>*, HWND*
);<BR><SPAN style="COLOR: blue">void</SPAN>
__RPC_USER<BR> HWND_UserFree( <SPAN
style="COLOR: blue">unsigned long</SPAN>*, HWND*
);<BR>
在MIDL生成的汇集代码中,遇到方法参数类型为HWND时,发生如下事情:<BR> 1.
调用HWND_UserSize并传递应用程序(客户或组件,视HWND是in参数还是out参数)传进来的HWND的实例以得到欲传递此实例需要的缓冲区大小<BR>
2. 在RPC通道上分配相应的内存块<BR> 3.
调用HWND_UserMarshal,依旧传递前面的HWND实例以及分配到的缓冲区的指针以将此HWND实例列集到缓冲区中<BR>
4. 通过RPC通道将缓冲区内容传递到对方进程空间中<BR> 5.
调用HWND_UserUnmarshal,并传递通过RPC通道得到的列集数据缓冲区的指针和生成的一临时HWND实例的指针以记录散集出来的HWND实例<BR>
6. 以返回的HWND实例为参数调用应用程序的方法<BR> 7.
调用HWND_UserFree,传递前面因调用HWND_UserUnmarshal而生成的临时记录散集出的HWND实例的指针以释放因此分配的内存<BR>
以上,就是[wire_marshal()]属性对线类型和描述类型的绑定的实现。但其中漏了一点,就是标志参数pFlags的使用。此标志参数是一个4字节数字,其高16位是一些关于NDR格式的编码规则,以使得NDR引擎(将填写好的缓冲区内容按NDR格式串的规则进行排列以在网上传输的程序)能做出正确的数据转换。其低16位是一个MSHCTX枚举值,指明调用环境,是进程内还是跨进程、是远程还是本地(具体信息还请查阅MSDN),因而可以在上面的四个函数中根据此值作出相应的优化。<BR>
下面为上面的CA*实现[wire_marshal()]属性。<BR>
前面已经了解到,CA*由于在IDL中没有对应的类型,应该使用void*来进行传递,在abc.idl中增加如下代码:<BR><SPAN
style="COLOR: blue">typedef</SPAN> <SPAN
style="COLOR: blue">struct</SPAN> _SA<BR>{<BR>
<SPAN style="COLOR: blue">long</SPAN> a, b;<BR>} *PSA;<BR><SPAN
style="COLOR: blue">typedef</SPAN> [wire_marshal( PSA )] <SPAN
style="COLOR: blue">void</SPAN>* PA;<BR>
并为接口IAbc增加一个方法:<BR>HRESULT SetA( [<SPAN
style="COLOR: blue">in</SPAN>] PA a );<BR>
接着在abc.cpp中增加如下代码:<BR><SPAN style="COLOR: blue">unsigned</SPAN>
<SPAN style="COLOR: blue">long</SPAN> __RPC_USER PA_UserSize( <SPAN
style="COLOR: blue">unsigned</SPAN> <SPAN
style="COLOR: blue">long</SPAN>* <SPAN style="COLOR: green">/*
pFlags
*/</SPAN>,<BR>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -