Location Transparency Location transparency is the ability to use a software component in the exact same way regardless of whether it is running in the same process as its client or whether the software component is running on the same machine as its client or on a remote machine. If a software component does reside in the same process as its client, making a method call is simple. A method call simply involves pushing the parameters that are being passed to the method along with the return addressthe location where the flow of control should continue after the method callonto the stack. The flow of control then jumps to the memory address in the process where the method resides. The method then pulls the parameters off the stack and executes its logic, and then the flow of control jumps to the return address. The return value from the method is either pulled off the stack or perhaps out of a CPU register. Any output parameters can be pulled off the stack also. Again, this is how it typically works; compilers are free to implement this logic in different ways. The process that I just described will only work if the calling code and the method reside in the same process and share the same memory address space. If two pieces of code share the same memory address space, memory addresses that are valid for one piece of code are also valid for the other. This means that you can pass pointers between the two pieces of code and you can jump from one point in the code to another using the memory address of a routine. If the calling code resides in a different process than the method or if the calling code does not share the same address space, you must use a process called marshalling. MarshalingMarshaling is a technique for making method calls across process and machine boundaries. The essential idea behind marshaling is that we want to take a stack frame that resides in one process and turn it into a data stream that we can pass to another process on the same machine or to a different machine on our network. At the receiving process (or machine), we will unpack the data stream, reconstruct the stack frame, make the method call, and then send any return values and output values back as a data stream to the caller, which can then put them on its own stack. To the caller, it appears that we made a method call normally, and the return values and output parameters came back. The whole idea behind location transparency is to make this process as simple (to the developer) as possible. Ideally, making a call to a remote object or an object in another process should be as simple as making a direct method call to an object that resides in the same process. Marshaling is usually implemented using a proxy that resides in the calling process. This proxy looks exactly like the object with the method that you are trying to call. It has the same methods, and those methods have the same parameters and return values. When you call the proxy, the proxy takes the method call and serializes it into a data stream. This message is sent by some network protocol (if the caller and callee reside on different machines) or by some form of Interprocess Communication (IPC) (if the caller and callee reside on the same machine) to the receiving process or machine. On the receiving process or machine, a piece of software, which is usually called a stub, receives the message, unpacks the message, calls the requested method, and then sends the return values back as a message. The proxy receives this message and updates the stack frame (return value and parameters) on the client. The network protocol and logic that connect the proxy and the stub are called a channel. So far, I have talked about marshaling only in the context of making method calls across process and machine boundaries. If you are an experienced COM programmer, you know that marshaling was also required when you made method calls across Apartments in the same process. The .NET Framework and the CLR does not support Apartments, but it does support something called an AppDomain, which is a logical subprocess within a physical process. Method calls that are being made across AppDomains must also be marshaled, even if the caller and the calling code reside in the same process. The CLR is smart enough to optimize the marshaling process if the caller's AppDomain and the called object's AppDomain reside in the same process. Marshaling is required when making method calls across AppDomains because two applications running in different AppDomains are completely isolated from one another, even if the two AppDomains are running in the same process. In other words, memory faults in one application won't bring the entire process down. The two applications can be independently stopped and debugged , and an application running in one AppDomain cannot directly access resources (memory, file handles, and so forth) held by an application in another AppDomain, even if the two applications share the same process. With COM, the only way to provide this sort of isolation between two applications was to separate them into different processes. However, with the .NET Framework, the CLR provides a level of type safety and code verification. These make it possible to provide this same level of isolation while avoiding the need for expensive (in terms of time) process switches. Conceptually, COM and .NET implement marshaling in a very similar manner. They both use proxies, stubs, and channels. They differ substantially though in the following areas:
CONSTRUCTING THE PROXY AND STUBOne of the main keys to making the marshaling process as seamless as possible is to hide the fact that the client is communicating with the server through a proxy. Ideally, the proxy should be created automatically by the operating system or runtime only when it is needed, and calling methods through the proxy should be as simple as making a direct method call. The underlying operating system or runtime code must therefore have complete meta information about the remote object. It must know the return type of each remotely callable method. It must know the type and size of each parameter, and it must know the directionality of each parameter, that is, which parameters are inputs, which are outputs, and which are both input and output. With COM, there were two ways to provide this information to the COM runtime:
Figure 2-8 shows how you would generate a custom marshaling/proxy-stub DLL using IDL and the MIDL compiler. Figure 2-8. Using MIDL in COM to generate a marshalling/proxy-stub DLL.
You (or your development environment) would generate the IDL file. You then compile this IDL file using MIDL. MIDL will generate several source files. You then compile and link these source files with a few system libraries to create your marshaling/proxy-stub DLL. You then had to install and register this DLL on both the client and server machine. The COM runtime then automatically uses this DLL whenever you make a cross-process , remote (or inter-Apartment) method call on an object for which the DLL can provide a proxy and stub. Using type library marshaling was much simpler, and this is why it eventually became the preferred approach. With this approach, all you had to do was to generate a type library for each COM server. You could generate this type library using an IDL description of the classes and interfaces in a COM server. Many development environments, VB for example, generated a type library automatically and embedded it within the DLL or executable that they generated. After you registered this type library, the type library marshaler that was included with the COM runtime would use the type library to get all the meta information it needed to construct a proxy for a remote or cross-process method call. With .NET, creating the proxy and stub is much easier. Once again, the copious metadata that is embedded in every assembly comes to your aid. This metadata contains all the information that the Remoting infrastructure in the .NET Framework needs to create a proxy for any class within an assembly. There's no need to create a separate description of your classes and interfaces in IDL and no need for a separate proxy/stub DLL. In order to be remotable, a class must instead inherit from a base class called System.MarshalByRefObject as shown in the following: Class MyRemotableClass : MarshalByRefObject { } After you have done this, the CLR is smart enough to create a proxy automatically whenever a reference to an object is passed out of the AppDomain in which the object resides. An object can be passed out of its own host AppDomain as a parameter or a return value of a remote method call. A reference is also passed out of its AppDomain when you instantiate a remote object in a different AppDomain using .NET Remoting. A remote object in this context means that it can be located in any of the following locations relative to its client:
If you wanted to be a stickler for details, you could argue that the mere fact that you must inherit from a different class ( System.MarshalByRefObject instead of System.Object ) to create a Remotable class is a violation of location transparency. It's hard to argue with that, but, considering how much easier it is to use .NET remoting than DCOM (as you will see in chapter 11), I think that this is a small price to pay.
SERIALIZING METHOD CALLS INTO A DATA STREAMWhen you are accessing a remote object, the proxy will take the method calls that you make on the object and serialize all the information needed to make the method call (an identifier for the method, the parameters, and so forth). The proxy then serializes it into a data stream that can be sent across processes or across the network. COM uses a format called Network Data Representation (NDR). .NET gives you more choices. For a start, the .NET Remoting architecture includes the notion of a Formatter and a Channel. Formatters are responsible for serializing method calls into a data stream; Channels are responsible for transporting the data stream across the network (or between processes on the same machine). There are two available channels in the .NET Remoting infrastructure: an HTTP Channel (see System.Runtime.Remoting.Channels.HTTP) and a TCP channel (see System.Runtime.Remoting.Channels.TCP). There are also two types of Formatters: a SOAP formatter (see System.Runtime.Serialization.Formatters.Soap) and a binary formatter (see System.Runtime.Serialization.Formatters.Binary). Both formatters use the metadata in the remote object's assembly to convert a method call into an XML-based SOAP message in the case of the SOAP formatter and to a proprietary binary format in the case of the binary formatter. By default, the HTTP channel uses the SOAP formatter, and the TCP channel uses the binary formatter. However, you can change this if you like and use the binary formatter with the HTTP channel, for instance. Using the TCP channel with the binary formatter is in some ways equivalent to using DCOM. The key difference between .NET Remoting and DCOM is the availability of the HTTP Channel and the SOAP formatter. Both of which make it easier to make method calls across the Internet. COMPARING THE NETWORK PROTOCOLSBoth COM and DCOM gave you some flexibility in the network protocol that you used when making method calls across a network. With DCOM, you could use the dcomcnfg tool to choose the network protocols that DCOM would use and the order that the protocols would be chosen in as shown in Figure 2-9. Figure 2-9. Available network protocols with DCOM.
With DCOM, TCP and User Datagram Protocol (UDP) were the most commonly used protocols although you could select others. With .NET, HTTP will be the most commonly used protocol. Rather than using a tool like dcomcnfg, you will select the protocoland even the portthat you will use when you select your channel. You can select a channel either programmatically using the RegisterWellKnownType or RegisterActivatedType methods on the RemotingServices class or using a configuration file. You can see that, with regard to location transparency, .NET is just as capable as COM/DCOM (although it's not quite as transparent). The copious metadata that managed code compilers generate obviates the need for IDL and for IDL-generated proxy-stub marshalling DLLs. With .NET's formatter and channel architecture, you have far more choices in how method calls are serialized into a network-transportable message and how those messages are transmitted across the network. |
Team-Fly |
Top |