Passing Managed Pointers to Unmanaged Code

team lib

You will sometimes find that you need to use a pointer to a managed type in unmanaged code. This will normally be a pointer that is passed as an argument to an unmanaged method, but when using C++ you can have managed pointers as members of unmanaged classes. This latter usage is described in the Using gcroot in Managed C++ section that follows .

You know that instances of managed types are controlled by the garbage collector; the reference count on the managed instance reflects the number of clients using the instance, and when the reference count falls to zero the object can be reclaimed by the next garbage collection. Collections can result in objects being moved, but because managed code never deals with the addresses of objects, this does not matter.

When a pointer to a managed object is to be used by unmanaged code, two things must be done:

  • The garbage collector must be made aware that there is another reference to the object, even though it cannot see the unmanaged client.

  • The object must not move or be collected during the time that the unmanaged code is using the managed object. This is done by pinning the object: fixing it in memory so that the garbage collector will not move it.

Platform Invoke will handle this for you: when a method takes a managed type as an argument, Platform Invoke will pin the managed object before passing its address to the unmanaged function. This will ensure that the managed object remains at the same address and will not be collected while it is in use in unmanaged code.

The System.Runtime.InteropServices.GCHandle (Garbage Collector Handle) class provides a way to use objects safely with unmanaged code when you are not using Platform Invoke. GCHandle can be used to provide a handle to any .NET object; the handle provides a way to use the managed object in unmanaged code. Table 13-4 shows the members of the GCHandle structure.

Table 13-4: The Members of the GCHandle Structure

Member

Description

AddrOfPinnedObject

Retrieves the address of a pinned object as an IntPtr . This function throws an InvalidOperationException if the handle is not a pinned handle.

Alloc

Static method that creates a GCHandle to represent a managed object.

Free

Releases a handle. The caller must ensure that this method is called only once on a handle. The method will throw an InvalidOperationException if the handle has already been freed or was not initialized .

IsAllocated

Returns true if the handle is currently allocated.

Target

Gets or sets the object that this handle represents.

GCHandle s are created using the static ( shared in Visual Basic .NET syntax) Alloc method of the GCHandle class, as shown in the following fragment of Visual C# code:

 //CreateanormalhandleforthemanagedobjectmyObject GCHandlegh=GCHandle.Alloc(myObject,GCHandleType.Normal); 

Four types of handle can be created by Alloc . A normal handle , GCHandleType.Normal , is an opaque handle; this means you cannot take the address of the object to which the handle refers. Normal handles are used when you want to prevent the garbage collector from collecting an object but you do not want to use the object in unmanaged code by taking its address.

A pinned handle , GCHandleType.Pinned , is similar to a normal handle, but it also allows the address of the object to be taken. For this to work, the object is pinned so that the garbage collector will not move it in memory. Because this reduces the collectors ability to manage its memory, the handle should be freed (via a call to Free ) as soon as possible. Once you have a pinned handle, you can use the GCHandle.AddrOfPinnedObject method to retrieve the objects address.

Note that pinned handles can be produced only for certain types of objects: strings, arrays, and blittable types. You can make managed types blittable by using the StructLayout attribute with either the Explicit or Sequential layout parameters. An InvalidArgumentException will be thrown if an invalid type is passed to Alloc when creating a pinning handle.

These are the two most common types of handle, but there are two others. A weak handle , GCHandleType.Weak , is similar to a normal handle, but it does not prevent the underlying managed object from being collected. If the object is collected, the handle will be zeroed out, and this can be tested using the IsAllocated property. Finally, a resurrection tracking handle , GCHandleType.WeakTrackResurrection , will allow the object to be collected, but the handle will not be zeroed out if the object is resurrected. Weak handles will always be zeroed, even if the object is resurrected.

Note 

Resurrection is a byproduct of the way in which objects are collected by the garbage collector. Chapter 12 described how objects that have finalizers are placed in a special queue; their finalizer methods are run by the finalizer thread, and they are collected the next time the collector runs. When an object is placed in the finalizer queue, it effectively comes back to lifeit is resurrected. If the objects finalizer method passes a reference to itself to any other object, the resurrected object will stay alive . You need to be aware, though, that the objects finalizer has already been run and this can cause the object not to behave as expected.

This sample program shows how you can use a GCHandle in managed C++ code:

 #include<iostream> usingnamespacestd; #using<mscorlib.dll> usingnamespaceSystem; usingnamespaceSystem::Runtime::InteropServices; voidmain() { //Aninstanceofamanagedtype String*s1=S"Astring"; //Createapinnedhandle GCHandlegh=GCHandle::Alloc(s1,GCHandleType::Pinned); //Gettheaddressandcasttoawchar_t wchar_t*buff= reinterpret_cast<wchar_t*>(gh.AddrOfPinnedObject().ToInt32()); //Modifythebuffer buff[2]=L'S'; wcout<< "bufferis'" <<buff<< "'" <<endl; //Freethehandle gh.Free(); Console::WriteLine("Stringis{0}",s1); } 

A GCHandle is represented by an IntPtr , so conversion operators are provided to let you convert between the GCHandle and IntPtr types. A simple cast can be used in Visual C#:

 //Ifnohandletypeisspecified,anormalhandleiscreated GCHandlegh=GCHandle.Alloc(theObject); IntPtrip=(IntPtr)gh; 

In managed C++ and Visual Basic .NET, you need to call the conversion operator method directly:

 //ManagedC++code GCHandlegh=GCHandle::Alloc(theObject); IntPtrip=GCHandle::op_Explicit(gh); 'VisualBasic.NETcode DimghAsGCHandle=GCHandle.Alloc(theObject) DimipAsIntPtr=GCHandle.op_Explicit(gh); 

Pinning in Managed C++

Chapter 12 introduced the __pin keyword and explained how it is used in managed C++. Creating a pinned GCHandle and using the __pin keyword are both procedures that can be used to pin objects in managed C++ code, but there are differences between them. The first difference is that __pin can be applied to any object type, whereas a pinned GCHandle can be created only for strings, arrays, and blittable types.

The second major difference is that the address returned by applying __pin to a System::String or System::Array object will be different from the one returned by GCHandle::AddrOfPinnedObject . The reason for this is that AddrOfPinnedObject is intelligent enough to return a pointer to the data buffer inside the string or array object, whereas __pin will simply return the address of the object. For other blittable types, AddrOfPinnedObject will return the address of the first data member, whereas __pin returns the address of the object.

Finally, __pin is more efficient than using a GCHandle because it is represented by one attribute, whereas creating a GCHandle adds the overhead of creating a GCHandle instance and calling methods.

Using gcroot in Managed C++

Managed C++ is rather unusual among .NET languages in that it allows you to mix managed and unmanaged code within the same application. This feature lets you use managed C++ code as a bridge between managed and unmanaged code.

For example, suppose you have existing C++ code that represents a data feed and fires events when new data arrives. The existing unmanaged code requires client code to implement handler functions and pass the addresses of those functions to the data feed so that they can be used as callbacks. How can you use the data feed code from Visual C# or another .NET language? You cant simply take the address of a C# method and expect unmanaged C++ code to use it as a callback. Even if you could, .NET code expects to use .NET events, not C++ callbacks.

One solution would be to write a managed C++ wrapper class to sit between the unmanaged C++ data provider and the Visual C# client. The managed C++ class implements the callbacks required by the unmanaged code, and its handler functions fire .NET events that can be consumed by Visual C# clients.

One problem with this design is that the unmanaged data provider class will need to hold a pointer to the managed wrapper class so that it can call the callback functions. This is a problem because unmanaged classes cannot contain pointers to managed types:

 classUnmanaged { Managed*pObj;//compilererror ... }; 

The reason for this is quite obvious: the unmanaged type is not under the control of the garbage collector, but it holds a reference to a managed type. The collector, then, has no way of knowing when it will be safe to collect the managed object because it cannot tell when the unmanaged object has finished with it.

You saw earlier in the chapter how GCHandle can be used to solve this problem. Visual C++ provides a template class, gcroot , that wraps a GCHandle and makes the handle easier to use. The gcroot class is a smart pointer: it takes a managed type as its template parameter and creates a GCHandle to represent the managed instance. The smart pointer lets you call managed instance methods through the GCHandle , and the handle is destroyed along with the gcroot object.

The example in Listing 13-1 shows how gcroot can be used. You can find this example in the Chapter13\GcRootDemo folder of the books companion content.

Listing 13-1: GcRootDemo.cpp
start example
 //Includethegcrootheaderfile #include<gcroot.h> #using<mscorlib.dll> usingnamespaceSystem; usingnamespaceSystem::Runtime::InteropServices; //Amanagedclass public__gcclassManaged { String*name; public: voidSetName(String*nm){name=nm;} voidSayHello(){Console::WriteLine(S"Hello,{0}",name);} }; //Anunmanagedclassthatcontainsapointertoamanagedobject classUnmanaged { gcroot<Managed*>pManaged; public: Unmanaged(Managed*pm,constchar*nm):pManaged(pm) { pManaged->SetName(nm); } //Callafunctiononthemanagedobject voidHello(){pManaged->SayHello();} }; voidmain() { //Createamanagedobject //Createandinitializeanunmanagedobject Unmanagedunman(pman, "Fred"); unman.Hello(); } 
end example
 

The program defines a managed class that holds a System::String as a data member. The SetName method provides a way to set the strings value, and the SayHello method prints out the current value as part of a message. The unmanaged class uses an instance of gcroot to encapsulate a pointer to the managed type, and it uses the gcroot instance to call functions on the managed type. Note how an unmanaged const char* is automatically marshaled to a System::String when it is used as the argument to SetName .

A closer look at the gcroot class shows that it provides the following operations:

  • An operator= function that allows copying of gcroot s

  • A conversion operation that allows conversion from the gcroot instance to the template parameter type

  • The operator- > function for smart pointer operation

The class makes operator & private: client code must not be allowed to take the address of the object and point to it from elsewhere because doing so could have serious consequences for the garbage collector.

 
team lib


COM Programming with Microsoft .NET
COM Programming with Microsoft .NET
ISBN: 0735618755
EAN: 2147483647
Year: 2006
Pages: 140

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net