Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Bi-directional RPC

0.00/5 (No votes)
4 May 2003 1  
How to use RPC for doing bi-directional client/server procedure calls.

Sample Image - birpc.jpg

Introduction

RPC is frequently not used directly, so, usually you don't have to know the RPC API when you are using DCOM to communicate between systems on the www. But - as you will see later - there are times when the un-cover RPC is very useful.

For instance: In the conventional RPC programming, you have two pieces of software, each in distinguishable executable form.

app A : the client - send request to be served.
app B : the server - complies to the client request.

This is nice and satisfactory most of the time. But, for getting this goal - you don't need to use the RPC directly- the distributed COM is more than enough.

So, when do you really need the RPC?

Well, suppose that one executable plays the role of either the client or the server, alternatively. Beyond that, suppose that the client-role and the server-role use the same interface and the same functions.

Confused?

Don't be !!!

The solution

  1. Try the downloaded demo project and scan carefully the source code for further information...
  2. Read the following lines before the great trial:

Explanation

I assume you already know how to produce the RPC code by the MIDL compiler, so I'll explain just the changes needed to be done:

  1. Some variables in the RPC code have to be replaced by similar variables so there will not be duplicated variables because the client and the server code use similar names for three variables.
     __MIDL_ProcFormatString replaced by __MIDL_ProcFormatString2
     
     __MIDL_TypeFormatString replaced by  __MIDL_TypeFormatString2
       
     <interface_name>_StubDesc replaced by <interface_name>_StubDesc2

    The changes above should be done just in the client side of the source code.

    We can't use one instance of those variables for the client and the server side, because in each situation those variables have different values;

  2. Add all the files needed to be involved in the RPC story, in one project, rather than while the RPC is regular implemented - in two different projects.
  3. Also, change the name of the functions involved in the RPC story:
      <func-name>(<parameters>) to <func-name>2(<parameters>).

    This is a snippet code from the demo project. The server side interface implements the GetName function. Because both the client side and the server side implements the following function:

       void GetName( 
        /* [size_is][string][out] */ unsigned char __RPC_FAR name[  ])
       {
        ....
       }
       

    ...and because we compiled together the client and the server code, we have to change the func-name in the server side to avoid duplication, as follows:

       void GetName2( 
        /* [size_is][string][out] */ unsigned char __RPC_FAR name[  ])
       {
        ....
       }

    This is the function generated by MIDL - the one and only change is the name of the internal function GetName (the pure implementation func - stub naked).

    void __RPC_STUB
    details_GetName(
        PRPC_MESSAGE _pRpcMessage )
    {
        MIDL_STUB_MESSAGE _StubMsg;
        unsigned char ( __RPC_FAR *name )[  ];
        RPC_STATUS _Status;
        
        ((void)(_Status));
        NdrServerInitializeNew(
                              _pRpcMessage,
                              &_StubMsg,
                              &details_StubDesc);
        
        name = 0;
        RpcTryFinally
            {
            RpcTryExcept
                {
                if(_StubMsg.Buffer > _StubMsg.BufferEnd)
                    {
                    RpcRaiseException(RPC_X_BAD_STUB_DATA);
                    }
                }
            RpcExcept( RPC_BAD_STUB_DATA_EXCEPTION_FILTER )
                {
                RpcRaiseException(RPC_X_BAD_STUB_DATA);
                }
            RpcEndExcept
            if(100 * 1 < 0)
                {
                RpcRaiseException(RPC_X_INVALID_BOUND);
                }
            name = (unsigned char (*)[])NdrAllocate(&_StubMsg,100 * 1);
            
            // GetName(*name) is the original source line produced
    
            // by midl compiler.
    
            GetName2(*name); 
                             
            
            _StubMsg.BufferLength = 0U;
            _StubMsg.MaxCount = 100;
            
            NdrConformantStringBufferSize( 
                 (PMIDL_STUB_MESSAGE) &_StubMsg,
                 (unsigned char __RPC_FAR *)*name,
                 (PFORMAT_STRING) &__MIDL_TypeFormatString.Format[2] );
            
            _pRpcMessage->BufferLength = _StubMsg.BufferLength;
            
            _Status = I_RpcGetBuffer( _pRpcMessage ); 
            if ( _Status )
                RpcRaiseException( _Status );
            
            _StubMsg.Buffer = 
              (unsigned char __RPC_FAR *) _pRpcMessage->Buffer;
            
            _StubMsg.MaxCount = 100;
            
            NdrConformantStringMarshall( 
                 (PMIDL_STUB_MESSAGE)& _StubMsg,
                 (unsigned char __RPC_FAR *)*name,
                 (PFORMAT_STRING) &__MIDL_TypeFormatString.Format[2] );
            
            }
        RpcFinally
            {
            if ( name )
                _StubMsg.pfnFree( name );
            
            }
        RpcEndFinally
        _pRpcMessage->BufferLength = 
            (unsigned int)((long)_StubMsg.Buffer - 
            (long)_pRpcMessage->Buffer);
        
    }

Practice

To test the demo-project, launch two copies of the GETNAME application. In any copy type two distinguish end points, and your name against your enemy's name (or your wife - ditto).

         end point of    end point of     your name
         remote server   local server
        ---------------  -------------    ------------

app A -   55459            55460            moshe     

app B -   55460            55459            shoshana

Click on PlayServer button on both copies. As you can see, each application points to the other application, so the communication is bi-directional. Thus each application is both the client and the server, with the same request (i.e. - get the name from the remote application).

The IP in this demo, represented by 'localhost' string, points to the local computer, represented by 'localhost'.

Click on 'get name from remote server' button - in each copy of the application, and you will get the name of the opposite application's user.

Bravo !!!

Compatibility note

The demo project works well in Windows 98 and Windows 2000.

Clarification

To make it simple and to highlight the topic of this article, I didn't use some necessary code (RPC exception, thread's status checking of activities from cradle to garden of Eden and other useful status handling).

License

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