Late binding. What is "binding" and what makes it late? - Incredible adventures in code

Late binding from COM components

Before the client executable can call the methods and properties of the bean object, it needs to know the memory addresses of those methods and properties. There are two different technologies that client programs can use to determine these addresses.

Early-binding programs learn addresses early in the compilation/execution process, at compile time. When a program is compiled by early binding, the compiler uses the component's type library to include the addresses of the methods and properties of the component in the client executable so that the addresses can be accessed very quickly and error-free. The COM interop technologies that have been discussed so far use early binding.

For their part, late-bound programs learn property and method addresses late in the compilation/execution process, at the very moment when these properties and methods are called. Late-bound code typically accesses client objects through basic data types such as object , and uses the run-time environment to dynamically determine method addresses. While late-bound code allows for some complex programming techniques such as polymorphism, it comes with some associated overhead, which we'll see shortly.

But first, let's check how late binding is done using reflection in C# (Reflection is a way that code uses at run time to determine information about server-side class interfaces; see Chapter 5.)

When you late-bind to a COM object in a C# program, you do not need to create an RCW for the COM component. Instead, the GetTypeFromProgID class method of the Type class is called to instantiate an object that represents the type of the COM object. The Type class is a member of the System.Runtime.InteropServices namespace, and in the code below we configure a Type object for the same data access COM component used in the previous examples:


Type objCustomerTableType;

When there is a Type object that encapsulates the type information of a COM object, it is used to create an instance of the COM object itself. This is done by passing a Type object to the CreateInstance class method of the Activator.CreateInstance instantiates a COM object and returns a late-bound reference to it, which can be stored in a reference of type object.

object objCustomerTable;
objCustomerTable = Activator.CreateInstance(objCustomerTableType);

Unfortunately, it is not possible to call methods directly on a reference of type object . To access the COM object, you must use the InvokeMember method of the Type object that was created first. When the InvokeMember method is called, it is passed a reference to a COM object, along with the name of the COM method to be called, and an array of type object of all the method's incoming arguments.

ObjCustomerTableType.InvokeMember("Delete", BindingFlags.InvokeMethod, null, objCustomerTable, aryInputArgs);

Recall again the sequence of actions:

1. Create a Type object for a COM object type using the Type.GetTypeFromProgID() class method.

2. Use this Type object to create a COM object using Activator.CreateInstance() .

3. Methods are called on the COM object by calling the InvokeMember method on the Type object and passing the object reference to it as the input argument. Below is a sample code that combines all of this into one block:

using System.Runtime.InteropServices;
Type objCustomerTableType;
object objCustomerTable;
objCustomerTableType=Type.GetTypeFromProgID("DataAccess.CustomerTable");
objCustomerTable=Activator.CreateInstance(ObjCustomerTableType);
objCustomerTableType.InvokeMember("Delete", BindingFlags, InvokeMethod, null, objCustomerTable, aryInputArgs);
objCustomerTableType = Type.GetTypeFromProgID("DataAccess.CustomerTable");

While C#'s late-binding features avoid the difficulties of RCW, there are some drawbacks that you should be aware of.

First, late binding can be dangerous. When using early binding, the compiler can query the COM component's type library to verify that all methods called on COM objects actually exist. With late binding, there is nothing to prevent a typo in a call to the InvokeMember() method, which can generate a runtime error.

Before touching on the use of virtual functions, it is necessary to consider such concepts as early and late binding. Let's compare two approaches to buying, for example, a kilogram of oranges. In the first case, we know in advance that we need to buy 1 kg. oranges. Therefore, we take a small package, not a lot, but enough money to be enough for this kilogram. In the second case, we, leaving the house, do not know what and how much we need to buy. Therefore, we take a car (what if there will be a lot of things and heavy things), we stock up on packages of large and small sizes and take as much money as possible. We go to the market and it turns out that we need to buy only 1 kg. oranges.

The above example to a certain extent reflects the meaning of the use of early and late binding, respectively. Obviously, for this example, the first option is optimal. In the second case, we foresaw too much, but we did not need it. On the other hand, if on the way to the market we decide that we do not need oranges and decide to buy 10 kg. apples, then in the first case we will no longer be able to do this. In the second case, it's easy.

Now consider this example from a programming point of view. When using early binding, we kind of say to the compiler: "I know exactly what I want. Therefore, hard (statically) bind all function calls." When using the late binding mechanism, we kind of say to the compiler: "I don't know what I want yet. When the time comes, I'll tell you what and how I want."

Thus, during early binding, the caller and callee are bound at the first opportunity, usually at compile time.

When late-binding the called method and the calling method, they cannot be bound at compile time. Therefore, a special mechanism has been implemented that determines how the called and calling methods will be bound when the call is actually made.

Obviously, the speed and efficiency of early binding is higher than that of late binding. At the same time, late binding provides some binding flexibility.

Finally, we got to the virtual functions and methods themselves. Unfortunately, it is rather difficult to draw any analogy with the physical world to illustrate virtual methods. Therefore, we will immediately consider this issue from the point of view of programming.

So what are virtual methods used for? Virtual methods exist so that the "heir" behaves differently from the "ancestor", while maintaining the compatibility property with it.

Here is the definition of virtual methods:

virtual method is a method that, when declared in descendants, replaces the corresponding method everywhere, even in methods declared on an ancestor, if it is called on a descendant.

The address of a virtual method is known only at the time of program execution. When a virtual method is called, its address is taken from the virtual method table of its class. Thus, what is needed is called.

The advantage of using virtual methods is that it uses exactly the mechanism of late binding, which allows the processing of objects whose type is not known at compile time.

To illustrate the use of virtual methods, I will give an example in the language C++ which I borrowed from one C++ Tutorial. Even if you are not very familiar with this language, I hope that my explanations will at least somehow explain its meaning.

#include // connection of the standard C++ library, // which describes some functions used in the program class vehicle // class "vehicle" ( int wheels; float weight; public: // start of the public (open) section of the class virtual void message (void) (cout message(); // call the message method of the delete unicycle object; // delete the unicycle object // All subsequent blocks of 3 lines are absolutely identical to the first block // with the only difference that the class of the created object // changes to car, truck, boat unicycle = new car; unicycle->message(); delete unicycle; unicycle = new truck; unicycle->message(); delete unicycle; unicycle = new boat; unicycle->message(); delete unicycle; )

The results of the program (output on the screen):

Vehicle Car Vehicle Boat

Let's consider the given example. We have three classes car, truck And boat, which are derived from the base class vehicle. In the base class vehicle virtual function is described message. In two of the three classes car, boat) also describes its functions message, and in the class truck there is no description of its function message. All the lines, to which I did not comment, have no fundamental meaning for this example. Now let's go over the main block of the program - functions main(). Describing a variable unicycle, as a pointer to an object of type vehicle. I will not go into details why exactly a pointer to an object. So it is necessary. In this case, treat the pointer as if it were the object itself. Details of working with pointers can be found in the descriptions of a particular OOP language. Then we create an object of the class vehicle, variable unicycle points to this object. After that we call the method message object unicycle, and in the next line we delete this object. In the next three blocks of 3 lines, we carry out similar operations, with the only difference being that we work with class objects car, truck, boat. Using a pointer allows us to use the same pointer for all derived classes. We are interested in calling the function message for each of the objects. If we did not indicate that the function message class vehicle is virtual ( virtual), then the compiler would statically (hard) bind any method call to the pointer object unicycle with method message class vehicle, because when describing, we said that the variable unicycle points to a class object vehicle. Those. would produce early binding. The output of such a program would be the output of the four lines "Vehicle". But due to the use of a virtual function in the class, we got slightly different results.

When working with class objects car And boat their own methods are called message, which is confirmed by displaying the corresponding messages on the screen. At the class truck no method message, for this reason, the corresponding method of the base class is called vehicle.

Very often a class containing a virtual method is called a polymorphic class. The most important difference is that polymorphic classes allow the processing of objects whose type is not known at compile time. Functions declared in the base class as virtual can be modified in derived classes, and the binding will not occur at the compilation stage (what is called early binding), but at the time the method is called (late binding).

Virtual methods are described using the keyword virtual in the base class. This means that in a derived class, this method can be replaced by a method that is more appropriate for that derived class. Declared virtual in the base class, the method will remain virtual for all derived classes. If a virtual method is not overridden in a derived class, then the call will find a method with that name up the class hierarchy (i.e., in the base class).

The last thing to talk about virtual functions is the concept of abstract classes. But we'll look at that in the next step.

2

Say there was no hello function and we just call ob.display basically, then it calls class B's display function, not class A.

The display() function call is set once by the compiler to the version defined in the base class. This is called static function call resolution or static binding - the function call is fixed before the program is executed. This is also sometimes referred to as early binding because the display() function is set at compile time.

Now how can it call the display function of the derived class without using a virtual keyword (late binding) before the display function in the base class?

Now in this program, passing the object as call by value, call by pointer and call by reference to the Hello function work fine. Now, if we are using Polymorphism and want to map a member function of a derived class if it is called, we must add the virtual keyword before the mapping function in the base. If you pass the value of an object when called with a pointer and call by reference it is a function call in a derived class, but if you pass an object by value it doesn't why is that so?>

Class A ( public: void display(); // virtual void display() ( cout<< "Hey from A" <display() ) int main() ( B obj; Hello(obj); // obj //&ob return 0; )

  • 2 answers
  • Sorting:

    Activity

4

now how can it call the derived class's mapping function without using the virtual (late binding) keyword before the mapping function in the base class?

A non-virtual function is simply resolved by the compiler according to the static type of the object (or reference or pointer) it calls. Thus, the given object of the derived type, as well as a reference to its subobject:

Bb; A&a=b;

you will get different results from calling a non-virtual function:

b.display(); // called as B a.display(); // called as A

If you know the real type, then you can specify what you want to call this version:

static_cast (a).display(); // called as B

but what would be horribly wrong if the object a refers to is not of type B .

Now, if we are using Polymorphism and we want to map a member function of a derived class if it is called, we must add a virtual keyword before the mapping function in the base.

To correct. If you make a function virtual, then it will be resolved at run time according to the dynamic type of the object, even if you use a different type of reference or pointer to access it. So both of the above examples would call it B .

If we pass the value of an object by call by pointer and call by reference, it calls the function in the derived class, but if we pass the object by value, that doesn't mean why is that?

If you pass it by value, then you cuts its: copying only the A part of the object to make a new object of type A . So, whether or not that function is virtual, calling it on that object will choose the version of A , since it's A and nothing but A .

0

wikipedia says that object slicing happens because there is no place to store extra members of the derived class in the superclass, so it's slicing. Why doesn't slicing of objects happen if we pass it by reference or pointer? Why does the superclass get extra space to store it? -

Applying reflection, late binding, and attributes

In this article, I propose to look at a complex example of using reflection, late binding and attributes. Let's assume that the task was to develop a so-called extensible application to which third-party tools could be connected.

What exactly is meant by extensible application? Consider the Visual Studio 2010 IDE. During development, this application was provided with special "traps" (hook) to allow other software vendors to connect their special modules. Clearly, the Visual Studio 2010 developers couldn't add references to nonexistent .NET external assemblies (i.e. use early binding), so how did they manage to provide the necessary hook methods in the application? One of the possible ways to solve this problem is described below.

    First, any extensible application must have some sort of input mechanism to allow the user to specify the plug-in (for example, a dialog box or an appropriate command line flag). This requires the use of dynamic loading.

    Second, any extensible application must necessarily be able to determine whether a module supports the functionality (eg, a set of interfaces) needed to connect it to the environment. This requires the use of reflection.

    Third, any extensible application must be sure to obtain a reference to the required infrastructure (eg, a set of interface types) and call its members to invoke the underlying functionality. This may require the use of late binding.

If an extensible application is initially programmed to request certain interfaces, it is able to determine at run time whether the type of interest can be invoked and, after the type successfully passes such a check, allow it to support additional interfaces and access their functionality in a polymorphic manner. This is exactly the approach taken by the developers of Visual Studio 2010, and there is nothing particularly complicated in it.

The first step is to create an assembly with the types that each snap-in must use in order to be able to connect to the application being extended. To do this, create a project of type Class Library (Class Library), and define the following two types in it:

Using System; namespace PW_CommonType ( public interface IApplicationFunc ( void Go(); ) public class InfoAttribute: System.Attribute ( public string CompanyName ( get; set; ) public string CompanyUrl ( get; set; ) ) )

Next, you need to create a type that implements the IApplicationFunc interface. To keep the example of creating an extensible application simple, let's keep this type simple. Let's create a new project of type Class Library in C# and define a class type named MyCompanyInfo in it:

Using System; using PW_CommonType; using System.Windows..Go() ( MessageBox.Show("Important information!"); ) ) )

And finally, the last step is to create the most extensible Windows Forms application, which will allow the user to select the desired snap-in using the standard Windows file open dialog box.

Now we need to add to it a reference to the PW_CommonType.dll assembly, but not to the CompanyInfo.dll code library. You also need to import the System.Reflection and PW_CommonType namespaces into the main form code file (to open it, right-click in the form's visual designer and select View Code from the context menu). Recall that the purpose of this appendix is ​​to see how to use late binding and reflection to test individual binaries produced by other vendors for their ability to act as plug-ins.

data . The goal of polymorphism, as applied to object-oriented programming, is to use a single name to define common actions for a class.

In the Java language, object variables are polymorphic. For example:
class King ( public static void main(String args) ( King king = new King() ; king = new AerysTargaryen() ; king = new RobertBaratheon() ; ) ) class RobertBaratheon extends King ( ) class AerysTargaryen extends King ( )
A variable of type King can refer to an object of type King or to an object of any subclass of King.
Let's take the following example:

class King ( public void speech() ( System .out .println ("I"m the King of the Andals!" ) ; ) public void speech(String quotation) ( System .out .println ("Wise man said: " + quotation) ; ) public void speech(Boolean speakLoudly) ( if (speakLoudly) System .out .println ( "I"M THE KING OF THE ANDALS!!!11") ; else System .out .println ("i"m... the king..." ) ; ) ) class AerysTargaryen extends King ( @Override public void speech() ( System .out .println ("Burn them all... " ) ; ) @Override public void speech(String quotation) ( System .out .println (quotation+ " ... And now burn them all!" ) ; ) ) class Kingdom ( public static void main(String args) ( King king = new AerysTargaryen() ;king.speech("Homo homini lupus est" ) ; ) )
What happens when a method belonging to an object is called king?
1. The compiler checks the declared type of the object and the name of the method, enumerates all methods with the namespeech in the AerusTargarien class and all public methods speech in superclassesAerus Targarien. The compiler now knows the possible candidates when calling the method.
2. The compiler determines the types of arguments passed to the method. If a single method is found whose signature matches the arguments, a call is made.This processking.speech("Homo homini lupus est") the compiler will choose the methodspeech(String quotation), but not speech().
If the compiler finds multiple methodswith suitable parameters (or none), an error message is issued.



The compiler now knows the name and types of the parameters of the method to be called.
3. In case the called method isprivate, static, finalor constructor, static binding is used ( early binding). In other cases, the method to be called is determined by the actual type of the object through which the call occurs. Those. used during program execution. dynamic binding (late binding).

4. The virtual machine pre-creates a method table for each class that lists the signatures of all methods and the actual methods to be called.
Method table for classKing looks like that:
  • speech() - King. speech()
  • speech(String quotation) -King. speech(String quotation )
  • King. speech(Boolean speakLoudly )
And for the classAerysTargaryen - so:
  • speech() - AerysTargaryen . speech()
  • speech(String quotation) - AerysTargaryen. speech(String quotation )
  • speech(Boolean speakLoudly) -King. speech(Boolean speakLoudly )
Methods inherited from Object are ignored in this example.
When calledking.speech() :
  1. The actual type of the variable is determinedking . In this case, thisAerysTargaryen.
  2. The virtual machine determines the class to which the method belongsspeech()
  3. The method is called.
Linking all methods inJavais carried out polymorphically, through late binding.Dynamic binding has one important feature: it allowsmodify programs without recompiling their codes. This makes programsdynamically expandable ( extensible).
What happens if you call a dynamically bound method on the object being constructed in a constructor? For example:
class King ( King() ( System .out .println ("Call King constructor" ) ; speech() ; //polymorphic method overriden in AerysTargaryen) public void speech() ( System .out .println ("I"m the King of the Andals!" ) ; ) ) class AerysTargaryen extends King ( private String victimName; AerysTargaryen() ( System .out .println ( "Call Aerys Targaryen constructor") ; victimName = "Lyanna Stark" ; speech() ; ) @Override public void speech() ( System .out .println ("Burn " + victimName + "!" ) ; ) ) class Kingdom ( public static void main(String args) ( King king = new AerysTargaryen() ; ) ) Result:

Call King constructor Burn null! Call Aerys Targaryen constructor Burn Lyanna Stark!
The base class constructor is always called during the construction of the derived class. The call automatically travels up the inheritance chain so that the constructors of all base classes along the inheritance chain are eventually called.
This means that when the constructor is called new AerysTargaryen() will be called:
  1. new Object()
  2. new King()
  3. new AerysTargaryen()
By definition, the job of a constructor is to give life to an object. Inside any constructor, an object can only be partially formed - it is only known that the objects of the base class have been initialized. If the constructor is just another step in constructing an object of a class derived from the class of the given constructor, the "derived" parts have not yet been initialized at the time the current constructor is called.

However, a dynamically bound call can go to the "outer" part of the hierarchy, that is, to derived classes. If it calls a derived class method in the constructor, this can lead to manipulation of uninitialized data, which is what we see as a result of this example.

The result of the program operation is determined by the execution of the object initialization algorithm:

  1. The memory allocated for the new object is filled with binary zeros.
  2. Base class constructors are called in the order described earlier. At this point the overridden method is called speech() (yes, before calling the class constructorAerysTargaryen), where it is found that the variable victimName is null because of the first step.
  3. The class member initializers are called in the order they are defined.
  4. The body of the derived class constructor is executed.
In particular, because of such behavioral issues, it is worth adhering to the following rule for writing constructors:
- perform in the constructor only the most necessary and simple actions to initialize the object
- whenever possible, avoid calling methods that are not defined as private or final (Which is the same in this context.)
Materials used:
  1. Eckel B.- Thinking in Java , 4th Edition - Chapter 8
  2. Cay S. Horstmann, Gary Cornell - Core Java 1 - Chapter 5
  3. Wikipedia
Share: