Thursday, October 22, 2009

Network Data Representation


3 4



Network Data Representation



As a component technology that offers location transparency, COM+ must tackle the problem of providing platform-dependent method parameter transformations. The technology subset of COM+ that deals with component distribution, DCOM, is based on DCE RPC. This technology in turn is based on DCE Network Data Representation (NDR) for shuttling parameter data among platforms. Whenever any method parameter is marshaled for transmission over the network, proxy and stub make calls to NDR routines to package the parameter value for insertion into and extraction from a data buffer.



The Platform SDK contains little information about Microsoft's NDR implementation. But Network Data Representation encodes to a binary format that is interchangeable among DCE RPC implementations by different vendors and on different platforms. Short of ordering DCE documentation from the Open Group, this NDR label format description from the RpcNdr.h header file gives us a glimpse of how NDR tags data type formats internally:





/*********************************************************************

�����Network�Computing�Architecture�(NCA)�definition:

�����Network�Data�Representation�(NDR):�Label�format:
�����An�unsigned�long�(32�bits)�with�the�following�layout:

�����3�3�2�2�2�2�2�2�2�2�2�2�1�1�1�1�1�1�1�1�1�1
�����1�0�9�8�7�6�5�4�3�2�1�0�9�8�7�6�5�4�3�2�1�0�9�8�7�6�5�4�3�2�1�0
����+---------------+---------------+---------------+-------+-------+
����|���Reserved����|���Reserved����|Floating-Point�|�Int���|�Char��|
����|���������������|���������������|Representation�|�Rep.��|�Rep.��|
����+---------------+---------------+---------------+-------+-------+

�����Where

���������Reserved:

�������������Must�be�zero�(0)�for�NCA�1.5�and�NCA�2.0.

���������Floating-Point�Representation�is:

�������������0�-�IEEE
�������������1�-�VAX
�������������2�-�Cray
�������������3�-�IBM

���������Int�Rep.�is�Integer�Representation:

�������������0�-�Big�Endian
�������������1�-�Little�Endian

���������Char�Rep.�is�Character�Representation:

�������������0�-�ASCII
�������������1�-�EBCDIC

�����The�Microsoft�Local�Data�Representation�(for�all�platforms�that�are
�����of�interest�currently)�is�defined�below:

�**************************************************************************/

#define�NDR_CHAR_REP_MASK���������������(unsigned�long)0X0000000FL
#define�NDR_INT_REP_MASK����������������(unsigned�long)0X000000F0L
#define�NDR_FLOAT_REP_MASK��������������(unsigned�long)0X0000FF00L

#define�NDR_LITTLE_ENDIAN���������������(unsigned�long)0X00000010L
#define�NDR_BIG_ENDIAN������������������(unsigned�long)0X00000000L

#define�NDR_IEEE_FLOAT������������������(unsigned�long)0X00000000L
#define�NDR_VAX_FLOAT�������������������(unsigned�long)0X00000100L
#define�NDR_IBM_FLOAT�������������������(unsigned�long)0X00000300L

#define�NDR_ASCII_CHAR������������������(unsigned�long)0X00000000L
#define�NDR_EBCDIC_CHAR�����������������(unsigned�long)0X00000001L

#if�defined(__RPC_MAC__)
#define�NDR_LOCAL_DATA_REPRESENTATION���(unsigned�long)0X00000000L
#define�NDR_LOCAL_ENDIAN����������������NDR_BIG_ENDIAN
#else
#define�NDR_LOCAL_DATA_REPRESENTATION���(unsigned�long)0X00000010L
#define�NDR_LOCAL_ENDIAN����������������NDR_LITTLE_ENDIAN
#endif



Fortunately, we do not need to understand how NDR is implemented in order to use it. Whenever we use NDR through COM+ interface method calls, the entire mechanism is completely hidden from us. But we would like to add NDR data transformations to our list of persistence solutions, which so far include only manual data transformation, frameworks, and portable formats. NDR data transformation routines could be superior to any of these options in COM+ projects because COM+ objects already rely on NDR for interface method calls. Therefore, we know that NDR can handle the same types that we use in our COM+ objects. The technology mismatch that existed when negotiating type support with third-party solutions vanishes here. In addition, we know that an NDR implementation must be present on all the platforms on which we intend to deploy our COM+ project. Since NDR is an integral part of DCOM, if such an implementation were not available, we couldn't deploy on that platform in the first place.



To have NDR assist us with the transformation of particular data types as required by the type-stream model, we must somehow get access to NDR functionality that we can inform of the data type to be translated and the data value. Fortunately, such functionality is available at the application level; it is called the NDR serialization services. The technique of using NDR directly to transform data also is called pickling.



Serialization services do not provide API functions of the form EncodeShortInteger, DecodeShortInteger, EncodeDouble, DecodeDouble, and so on. Instead, two kinds of serialization services exist: procedure serialization and type serialization. The MIDL compiler gives applications access to both when you apply the encode and decode attributes to a procedure or typedef in an application configuration file (ACF). These attributes can be applied to an entire interface instead, in which case MIDL will generate serialization stubs rather than remote RPC stubs for all procedures and types in the interface. Procedure serialization packages a procedure's parameter set in a user-controlled buffer instead of sending it over a remote channel. Type serialization encodes or decodes an individual type to or from a buffer.



Just as regular remote procedures require binding handles to inform them of the current call context,5 procedure and type serialization functions require a serialization handle to inform them of the serialization context. The implicit_handle and explicit_handle interface attributes control whether serialization functions retrieve this handle from a global variable or whether the MIDL compiler generates an argument in the signatures through which the handle value is passed to them, respectively. In either case, it is your responsibility to initialize the serialization context with one of the MesEncodeXXXHandleCreate or MesDecodeXXXHandleCreate functions before invoking any serialization function.



Two pieces of information are conveyed to serialization functions through the serialization context: which serialization style should be used for the call, and the address of the buffer into which encoded information should be written or from which information to be decoded should be read.



There are three serialization styles:




  • Fixed buffer serialization. You supply a buffer large enough to hold the encoded parameters or type. For type serialization, you can use the _AlignSize functions generated by MIDL for each type to be serialized. They determine the space plus alignment padding necessary for each type instance when serializing more than one instance of the same or different types into the same buffer. When decoding, you provide a buffer with the entire set of encoded data necessary to complete at least one decoding call.


  • Dynamic buffer serialization. When encoding, the marshaling buffer is allocated by the serialization function instead of you. Because you pass this entire buffer to the serialization function when decoding, there is no difference between fixed and dynamic serialization for decoding operations.


  • Incremental serialization. Instead of providing a buffer directly, you provide a set of functions through which the serialization routines will manipulate the buffer. When encoding, serialization routines call your allocation and write functions. The allocation function is called whenever the serialization routine requires a certain amount of space to store encoded data. The write function is called to notify you when a certain amount of space in the buffer has been written and can be flushed to whatever medium the application uses. When decoding, serialization routines call your read function to request some number of bytes from the buffer of encoded data. With incremental serialization, you can call more than one serialization function and pass the same serialization handle. The serialization functions make sure that the data is written to properly aligned locations in the buffer.





To have MIDL generate an encoding and decoding function for each of the types of the type stream interface not handled by CTypeStreamImpl, we define the interface RITransmitTypes in an ACF like this:





[
����explicit_handle,
����encode,
����decode
]
interface�RITransmitTypes
{
}



In the like-named IDL file, RITransmitTypes appears as follows:





[
����uuid(BC649B32-951B-11d2-A934-945C32000000),
����version(1.0)
]
interface�RITransmitTypes
{
����typedef�signed�short����t_Short;
����typedef�signed�int������t_Int;
����typedef�signed�long�����t_Long;
����typedef�signed�hyper����t_Hyper;
����typedef�float�����������t_Float;
����typedef�double����������t_Double;
����typedef�CHAR������������t_Char;
����typedef�WCHAR�����������t_WChar;
����typedef�OLECHAR���������t_OleChar;
����typedef�CURRENCY��������t_Currency;
����typedef�DATE������������t_Date;
����typedef�VARIANT���������t_Variant;

����typedef�struct�SCharArr
����{
��������ULONG�nSize;
��������[size_is(nSize)]�const�CHAR*�pnChars;
����}�t_SCharArr;

����typedef�struct�SWCharArr
����{
��������ULONG�nSize;
��������[size_is(nSize)]�const�WCHAR*�pnWChars;
����}�t_SWCharArr;

����typedef�struct�SOleCharArr
����{
��������ULONG�nSize;
��������[size_is(nSize)]�const�OLECHAR*�pnOleChars;
����}�t_SOleCharArr;
}



The _c.c file MIDL will generate from these definitions contains functions such as these for each RITransmitTypes type:





size_t
t_Short_AlignSize(
����handle_t�_MidlEsHandle,
����t_Short�__RPC_FAR�*�_pType)
{
����return�NdrMesSimpleTypeAlignSize(_MidlEsHandle);
}

void
t_Short_Encode(
����handle_t�_MidlEsHandle,
����t_Short�__RPC_FAR�*�_pType)
{
����NdrMesSimpleTypeEncode(
��������������������������_MidlEsHandle,
��������������������������(�PMIDL_STUB_DESC��)&RITransmitTypes_StubDesc,
��������������������������_pType,
��������������������������2);
}

void
t_Short_Decode(
����handle_t�_MidlEsHandle,
����t_Short�__RPC_FAR�*�_pType)
{
����NdrMesSimpleTypeDecode(
��������������������������_MidlEsHandle,
��������������������������_pType,
��������������������������6);
}



A separate argument, _handle_t, is generated for each function, as we expected. Note that we did not have to request Variant type marshaling support from NDR, since CTypeStreamImpl handles the Variant type. However, as previously mentioned, CTypeStreamImpl's Variant handling is limited to a subset of possible Variant values. By requesting Variant type marshaling directly from NDR, COM+'s custom marshaling routines will be invoked when encoding and decoding the Variant type because the Variant is defined to be wire-marshaled in Oaidl.idl in this fashion:





typedef�[wire_marshal(wireVARIANT)]�struct�tagVARIANT�VARIANT;

struct�_wireVARIANT�{
����DWORD��clSize;�������/*�wire�buffer�length�in�units�of�hyper�(int64)�*/
����DWORD��rpcReserved;��/*�for�future�use�*/
����USHORT�vt;
����USHORT�wReserved1;
����USHORT�wReserved2;
����USHORT�wReserved3;
����[switch_type(ULONG),�switch_is(vt)]�union�{
����[case(VT_I4)]�������LONG����������lVal;������/*�VT_I4����������������*/
����[case(VT_UI1)]������BYTE����������bVal;������/*�VT_UI1���������������*/
����[case(VT_I2)]�������SHORT���������iVal;������/*�VT_I2����������������*/
����[case(VT_R4)]�������FLOAT���������fltVal;����/*�VT_R4����������������*/
����[case(VT_R8)]�������DOUBLE��������dblVal;����/*�VT_R8����������������*/
����[case(VT_BOOL)]�����VARIANT_BOOL��boolVal;���/*�VT_BOOL��������������*/
����[case(VT_ERROR)]����SCODE���������scode;�����/*�VT_ERROR�������������*/
����[case(VT_CY)]�������CY������������cyVal;�����/*�VT_CY����������������*/
����[case(VT_DATE)]�����DATE����������date;������/*�VT_DATE��������������*/
����[case(VT_BSTR)]�����wireBSTR������bstrVal;���/*�VT_BSTR��������������*/
����[case(VT_UNKNOWN)]��IUnknown�*����punkVal;���/*�VT_UNKNOWN�����������*/
����[case(VT_DISPATCH)]�IDispatch�*���pdispVal;��/*�VT_DISPATCH����������*/
����[case(VT_ARRAY)]����wireSAFEARRAY�parray;����/*�VT_ARRAY�������������*/

����[case(VT_RECORD,�VT_RECORD|VT_BYREF)]
������������������������wireBRECORD���brecVal;���/*�VT_RECORD������������*/

����[case(VT_UI1|VT_BYREF)]
������������������������BYTE�*��������pbVal;�����/*�VT_BYREF|VT_UI1������*/
����[case(VT_I2|VT_BYREF)]
������������������������SHORT�*�������piVal;�����/*�VT_BYREF|VT_I2�������*/
����[case(VT_I4|VT_BYREF)]
������������������������LONG�*��������plVal;�����/*�VT_BYREF|VT_I4�������*/
����[case(VT_R4|VT_BYREF)]
������������������������FLOAT�*�������pfltVal;���/*�VT_BYREF|VT_R4�������*/
����[case(VT_R8|VT_BYREF)]
������������������������DOUBLE�*������pdblVal;���/*�VT_BYREF|VT_R8�������*/
����[case(VT_BOOL|VT_BYREF)]
������������������������VARIANT_BOOL�*pboolVal;��/*�VT_BYREF|VT_BOOL�����*/
����[case(VT_ERROR|VT_BYREF)]
������������������������SCODE�*�������pscode;����/*�VT_BYREF|VT_ERROR����*/
����[case(VT_CY|VT_BYREF)]
������������������������CY�*����������pcyVal;����/*�VT_BYREF|VT_CY�������*/
����[case(VT_DATE|VT_BYREF)]
������������������������DATE�*��������pdate;�����/*�VT_BYREF|VT_DATE�����*/
����[case(VT_BSTR|VT_BYREF)]
������������������������wireBSTR�*����pbstrVal;��/*�VT_BYREF|VT_BSTR�����*/
����[case(VT_UNKNOWN|VT_BYREF)]
������������������������IUnknown�**���ppunkVal;��/*�VT_BYREF|VT_UNKNOWN��*/
����[case(VT_DISPATCH|VT_BYREF)]
������������������������IDispatch�**��ppdispVal;�/*�VT_BYREF|VT_DISPATCH�*/
����[case(VT_ARRAY|VT_BYREF)]
������������������������wireSAFEARRAY�*pparray;��/*�VT_BYREF|VT_ARRAY����*/
����[case(VT_VARIANT|VT_BYREF)]
������������������������wireVARIANT�*�pvarVal;���/*�VT_BYREF|VT_VARIANT��*/

����[case(VT_I1)]�������CHAR����������cVal;������/*�VT_I1����������������*/
����[case(VT_UI2)]������USHORT��������uiVal;�����/*�VT_UI2���������������*/
����[case(VT_UI4)]������ULONG���������ulVal;�����/*�VT_UI4���������������*/
����[case(VT_INT)]������INT�����������intVal;����/*�VT_INT���������������*/
����[case(VT_UINT)]�����UINT����������uintVal;���/*�VT_UINT��������������*/
����[case(VT_DECIMAL)]��DECIMAL�������decVal;����/*�VT_DECIMAL�����������*/

����[case(VT_BYREF|VT_DECIMAL)]
������������������������DECIMAL�*�����pdecVal;���/*�VT_BYREF|VT_DECIMAL��*/
����[case(VT_BYREF|VT_I1)]
������������������������CHAR�*��������pcVal;�����/*�VT_BYREF|VT_I1�������*/
����[case(VT_BYREF|VT_UI2)]
������������������������USHORT�*������puiVal;����/*�VT_BYREF|VT_UI2������*/
����[case(VT_BYREF|VT_UI4)]
������������������������ULONG�*�������pulVal;����/*�VT_BYREF|VT_UI4������*/
����[case(VT_BYREF|VT_INT)]
������������������������INT�*���������pintVal;���/*�VT_BYREF|VT_INT������*/
����[case(VT_BYREF|VT_UINT)]
������������������������UINT�*��������puintVal;��/*�VT_BYREF|VT_UINT�����*/
����[case(VT_EMPTY)]����;������������������������/*�nothing��������������*/
����[case(VT_NULL)]�����;������������������������/*�nothing��������������*/
����};
};



As you can see, provisions are made to handle the various reference types, as well as a SafeArray.



No comments:

Post a Comment