Chapter 22. Working with Unmanaged Code

After completing this chapter, you will be able to:

  • Explain the issues that affect managed and unmanaged code.

  • Use managed objects in unmanaged code.

  • Use the Platform Invoke mechanism to call unmanaged functions in DLLs.

Although the primary focus of this book is using C++/CLI with the Microsoft .NET Framework, at times you’ll have to call functions outside the .NET environment.

The System::Runtime::InteropServices namespace contains classes and structures to help with interoperation between .NET and the outside world. In this chapter, I’ll introduce one feature of the namespace—the Platform Invoke mechanism for calling unmanaged functions within DLLs. We also investigate some of the other issues that surround interacting with unmanaged code. Chapter 24, considers considers interoperating between the Component Object Model (COM) and .NET.

Managed vs. unmanaged code

Code and data that live in the .NET world are called managed because locations and lifetimes are managed by the Common Language Runtime (CLR). Code and data that exist outside of .NET are called unmanaged, because there is no central mechanism for managing their lifetimes. Sometimes you have to mix the two, calling existing unmanaged code from within .NET. This section introduces some of the issues and techniques that you’ll need to consider in this situation.

Mixed classes

Although managed classes are normally composed of other managed types, it is possible to mix managed and unmanaged types as members of classes under some circumstances. It is also possible to have a pointer to an unmanaged object as a member of a managed class, as in this example:

ref class ManagedClass
{
    UnmanagedClass *puc;
    ...
};

Notice the use of the asterisk (*) rather than the caret (^): this is a pointer to an unmanaged type, not a handle.

Because the member is unmanaged, it’s up to you to manage the lifetime of the object at the other end of the pointer. You should handle this carefully: unmanaged objects sometimes need explicit deletion at a particular point in the code, and this might not fit well with the .NET garbage collection model. However, you can declare destructors for managed classes and use delete on objects of managed types, so it’s possible to arrange for correct object deallocation in most circumstances.

You can’t have an unmanaged object as a member of a managed class, such as is illustrated in the following:

ref class ManagedClass
{
    UnmanagedClass obj;    // C4368: mixed types are not supported
    ...
};

An unmanaged object will only work as a class member if the host object is explicitly deleted at some point: at the end of the enclosing block for an automatic variable, at the end of the process for a global variable, or when delete is called on a pointer. Managed objects don’t work in this way, and the garbage collector can’t collect an unmanaged object.

It’s impossible to have a handle to a managed type as part of an unmanaged class, as shown here:

class UnmanagedClass
{
    ManagedClass ^obj;    // C3265: cannot declare a managed 'obj'
                          // in an unmanaged 'UnmanagedClass'
    ...
};

Because the unmanaged object doesn’t exist in the .NET world, the handle to the contained object is invisible to the garbage collector. Thus, the garbage collector doesn’t know who has a reference to the object or when it can be collected.

The GCHandle type

There is a way to use a managed type as part of an unmanaged class by using the GCHandle type provided in the System::Runtime::InteropServices namespace. GCHandle asks the runtime to give you a “handle” to refer to a managed object from unmanaged code. You use the GCHandle::Alloc static method to create the handle, and the handle’s Free method to release it again. Here’s how you’d use GCHandle if you wanted to pass a pointer to a managed object to unmanaged code:

  1. Create a GCHandle to refer to your object. GCHandles can be converted to and from integers for ease of passing them between functions.

  2. Pass the GCHandle to the unmanaged code. As long as the handle hasn’t been freed, the runtime won’t collect the object.

  3. Call Free on the handle when the unmanaged code no longer needs it. At this point, the runtime is free to collect the object if no one else is using it.

To help you use GCHandles within unmanaged code without your having to get into the details of using Alloc and Free, Microsoft provides a helper template class called gcroot. The following exercise shows you how to use gcroot to include a pointer to a managed type as part of an unmanaged class:

  1. Start Microsoft Visual Studio 2012 and create a new CLR Console Application project named Manage.

  2. Add an #include directive for the gcroot.h system header file just below the stdafx.h include directive.

    #include <gcroot.h>

    This system header file defines the gcroot helper class.

  3. Add a using directive to the top of the code to make it easier to use the System::Runtime::Interop Services namespace.

    using namespace System::Runtime::InteropServices;
  4. Add the definition of a simple managed class to the code.

    ref class MClass
    {
    public:
        int val;
        MClass(int n) : val(n) { }
    };

    This class simply wraps an integer, whose value is set in the constructor.

  5. Add the definition of an unmanaged class.

    class UClass
    {
    public:
        gcroot<MClass^> mc;
    
        UClass(gcroot<MClass^> pmc) : mc(pmc) { }
    
        int getValue()
        {
            return mc->val;
        }
    };

    The definition of the mc variable is an example of using a template class. The definition effectively creates a gcroot variable that wraps a GCHandle to an MClass pointer. The GCHandle is created when the gcroot object is created, and it is freed when the gcroot object is destroyed.

    A UClass object is passed a handle to a managed MClass object when it is created, and this handle is stored away in the gcroot object. The getValue function simply returns the public val member from the MClass object by value, so you can verify that the code really lets you access a managed object from an unmanaged context.

  6. Modify the main function to use the classes.

    int main(array<String^>^ args)
    {
        Console::WriteLine("Testing...");
    
        // Create a managed object
        MClass ^pm = gcnew MClass(3);
    
        // Create an unmanaged object
        UClass uc(pm);
    
        Console::WriteLine("Value is {0}", uc.getValue());
    
        return 0;
    }

    The code first creates a managed object and initializes it with an integer. The pointer to this object is then used to initialize an unmanaged object, and the getValue function is used to extract the value from the managed object before printing it out. When the UClass object goes out of scope, the gcroot is destroyed, which frees the GCHandle and, in turn, frees up the managed object.

    Tip

    If the managed type that you want to use with gcroot has a destructor, using the auto_gcroot type (declared in <auto_gcroot.h>) will call the destructor on the object when the gcroot goes out of scope.

  7. Build and run the application.

Pinning and boxing

This section discusses two C++/CLI concepts, pinning and boxing, and shows you how they’re used in code.

Interior pointers

Before getting to pinning, let’s briefly discuss interior pointers. We will do this by looking at a scenario in which you have a managed object, and you want to pass it to an unmanaged function that requires a pointer.

You probably know that the garbage collector can (and does) move objects around on the managed heap to maximize free space. This means that you can’t use an unmanaged pointer to refer to a managed object, because the address held in the pointer could end up pointing to the wrong place if the garbage collector moves the object. In fact, the compiler will give you an error if you try to use an ordinary pointer with a managed object.

An interior pointer is a pointer whose address will be updated if the object to which it refers is moved. They are called “interior” pointers because you use them to point to a member within a managed object.

Note

You can’t use an interior pointer to point to a “whole” managed object; you can only point to a field within an object.

Pinning pointers

The CLR assumes that it can move objects around in the managed heap whenever it wants. At times, however, you might need to tell the CLR to leave objects where they are. For example, if you want to pass a pointer to a managed object to an unmanaged function, you don’t want the CLR to move the object around in memory while the object is being used by the unmanaged code.

A pinning pointer is a pointer to a managed object, but the value of the pointer cannot be changed, which means that the garbage collector cannot move it around in memory. Thus, creating a pinning pointer to an object gives you a pointer that can safely be passed out to unmanaged code because you can be sure that the address is going to remain valid.

You can use pinning on all or part of a managed object, and pinning a member of a managed object results in the entire object being pinned. For example, pinning the first element of an array will result in the entire array being pinned. The object will remain pinned until there are no references left to the pinning pointer.

The code fragment that follows shows the creation and use of a pinning pointer. First, assume that we have an unmanaged function that takes a pointer to an integer.

void someFunc(int *p)
{
    // Do something with the integer value...
    int n = *p;
}

Here is how we could use this with a managed array:

// Create a managed array of int
array<int> ^arr = gcnew array<int>(5);

// Create a pinning pointer to the first element
// Note there is no '^', and that '&' is used to take the address of the object
pin_ptr<MyClass> pin = &arr[0];

// Pass the integer member to an unmanaged function
someFunc(pin);

// Zero out the pinning pointer
// The array is not pinned any more
pin = nullptr;

After the array element has been pinned, you can pass its address to the unmanaged function, confident that the int won’t be moved around in memory. Observe how there is an implicit conversion between pin_ptr<int> and int*, so you don’t need to convert it yourself. When you’re finished, assigning nullptr to the pinning pointer frees the array object so that it can be moved.

Boxing and unboxing

Boxing and unboxing, which will be discussed in a moment, make it possible for value types to be treated as objects. Chapter 9, covers value types in detail and teaches that they are fundamentally different from reference types. To recap, value types have three particular properties:

  • Value types are stored on the stack, unlike references, which are stored on the run-time heap.

  • Instances of value types are always accessed directly, unlike reference types, which are accessed through references. This means that you don’t use the new operator when creating instances. It also means that value types are not garbage-collected.

  • Copying value types copies the value rather than the reference.

Anything that wraps a simple value, such as a Boolean or an integer, and that is less than about 16 bytes in size is a good candidate for making a value type. Because value types aren’t accessed via references, they can be far more efficient than the equivalent reference types but can’t be regarded as objects in the same way that reference types can. This becomes a problem when you want to use a value type in a context where an object reference is needed. For example, consider the overload of the Console::WriteLine function that performs formatted output, whose prototype is shown here:

static void WriteLine(String^, Object^);

The first String^ parameter is the format string, and the second is a handle to any .NET reference type. Because value types aren’t accessed by references, you can’t directly specify a value type. But, you will find that the following works, even though “12” is not an instance of a reference type:

int foo = 12;
Console::WriteLine("foo is {0}", foo);

Boxing

Boxing wraps a value type in an object “box” so that it can be used where an object reference is needed. In C++/CLI, this wrapping is done automatically.

The following three things happen when an object is boxed:

  • A managed object is created on the CLR heap.

  • The value of the value type is copied, bit by bit, into the managed object.

  • The address of the managed object is returned.

Be aware that the managed object contains a copy of the value type. This means that any modifications you might make to the managed wrapper don’t propagate back to the original value. You can see this happening if you look at the generated code the IL disassembler tool (ISDASM). The IL generated for the preceding two lines of C++/CLI code look something like this:

IL_0002:  ldc.i4.s   12
IL_0004:  stloc.1
IL_0005:  ldstr "Value is {0}"
IL_000a:  ldloc.1
IL_000b:  box        [mscorlib]System.Int32
IL_0010:  call       void [mscorlib]System.Console::WriteLine(string, object)

The first line pushes a literal 12 onto the stack, and the second line stores it (stloc) into a local variable. After the string literal is pushed onto the stack, the ldloc instruction takes the local variable and pushes it back onto the stack. You can see that the next line is a box instruction, which generates an object to hold the integer before calling WriteLine.

Unboxing

What if you want to retrieve the value from a boxed object? The following brief exercise shows you how to get the value back out of a boxed object by using a cast.

  1. Create a new CLR Console Application project named Boxing.

  2. Edit the main function to create an integer and box it.

    int main(array<String^>^ args)
    {
        Console::WriteLine("Boxing Example");
    
        // Create an int
        int foo = 12;
    
        // It will get boxed automatically
        Object ^obj = foo;
    
        // Use the boxed object
        Console::WriteLine("Value of foo is {0}", obj);
    
        return 0;
    }
  3. Add the following code to get the value back out of the box:

    // Unbox the value
    int fooTwo = safe_cast<int>(obj);
    
    Console::WriteLine("fooTwo is {0}", fooTwo);

    The safe_cast checks to see whether a boxed int is on the other end of the obj pointer; if it is, it returns an int.

    Note

    The safe_cast is explored in Chapter 3, but let’s take a moment to consider it here. Like dynamic_cast, a safe_cast is performed at run time. It checks whether the type on the other end of the handle is of the right type. If it is, the cast is performed and the value returned. Unlike dynamic_cast, which returns a null if the types don’t match, safe_cast will throw an exception.

  4. Build and run the application.

Using P/Invoke to call functions in the Win32 API

Although it’s possible to do a great deal by using the functionality provided in the .NET Framework, at times you’ll need to use code that wasn’t written for .NET to accommodate situations such as the following:

  • You need to call a Microsoft Windows API function that doesn’t have a .NET equivalent.

  • You have some code in a Dynamic-Link Library (DLL) that originated outside .NET and can’t be rewritten.

  • You have code that needs to be written in a language that’s not yet supported by the .NET Framework.

Whatever the reason, the code you’re calling exists outside the .NET-managed environment, so you need a way to pass function calls into and out of .NET. The mechanism to do this is called P/Invoke (for Platform Invoke, pronounced “p-invoke”). It is provided to let you call functions in DLLs.

Using P/Invoke involves adding a prototype to your code that uses attributes to inform .NET about the function you’re proposing to call. In particular, you need to specify the name of the DLL containing the function, the name of the function, what arguments the function takes, and what the function returns.

A mechanism such as P/Invoke is necessary to facilitate communication between managed and unmanaged code. Take strings as an example: A string in C++/CLI is a handle to a String object, but in standard C++, a string isn’t represented by an object. Instead, a string is a pointer to a series of memory locations that contain characters and is terminated by a null. If you’re going to pass a string data between managed and unmanaged code, something has to convert between the corresponding managed and unmanaged data types. This conversion process is called marshaling, and it is one of the tasks that P/Invoke performs for you.

The following exercise shows you how to call an unmanaged function in one of the Windows system DLLs. The obvious candidate for this exercise is MessageBox for two reasons: first, it’s a stand-alone function and doesn’t require any setting up; second, it’s obvious whether the call has worked.

The MessageBox function—that is, the MessageBoxA and MessageBoxW functions—reside in the User32.dll system DLL. Three system DLLs contain the unmanaged Windows API code:

  • User32.dll, which contains functions for message handling, timers, menus, and communications

  • Kernel32.dll, which contains low-level operating system functionality for memory management and resource handling

  • GDI32.dll, which contains the GDI graphics subsystem code

How do you know which DLL holds a particular system function? If you look the function up in the Platform SDK, you’ll usually find a clue in the “Requirements” section at the end of the topic. For example, the Help topic for MessageBox has the following lines:

Library: User32.lib

DLL: User32.dll

The first line indicates that if you want to use MessageBox in traditional C++ code, you’ll have to link with a library named User32.lib, and the second denotes that the code actually resides in User32.dll.

Now that you know where you can find the MessageBox function, here’s the exercise:

  1. Start a new CLR Console Application project named Message.

  2. Add a using directive to the top of the project.

    using namespace System::Runtime::InteropServices;

    Most of the interop features are part of the System::Runtime::InteropServices namespace, and it’s much easier to use if you declare the namespace.

  3. Add the P/Invoke prototype for the MessageBox function before the main routine:

    // Set up the import
    [DllImport("User32.dll", CharSet=CharSet::Auto)]
    int MessageBox(IntPtr hwnd, String ^text,
                          String ^caption, unsigned int type);

    There is quite a lot to explain about these few lines of code. The prototype for the Message Box function is declared by using the DllImport attribute. The two parameters passed to the attribute are the name of the DLL in which the function resides, and (because this is a function that uses characters or strings) an indication of which version to use. CharSet::Auto leaves it up to the target platform to decide which version to call and how to convert the string arguments.

    The first argument to MessageBox is a “handle to the owning window.” This is a handle in the original Win32 sense, and it is basically a pointer. This is used to establish the MessageBox as a child of another window, and we’re not concerned about it here. The rather strange choice of argument name (hwnd) comes from the original type, HWND.

    Note

    An IntPtr is an integer type large enough to hold a native pointer, so it will be 32 bits on 32-bit Windows and 64 bits on 64-bit systems. It is commonly used in interop to pass pointers to and from unmanaged code.

    Notice how String handles are used to pass string information, where the original function would require a Windows LPTSTR type. The P/Invoke marshaling automatically converts the data when making the call. The final argument is the style of MessageBox, which governs which icon and buttons it will display. The default value is zero, which just displays an OK button.

  4. Add code to the main function to call MessageBox:

    int main(array<String^>^ args)
    {
        Console::WriteLine("P/Invoke Example");
    
        String ^theText = "Hello World!";
        String ^theCaption = "A Message Box...";
        MessageBox(IntPtr::Zero, theText, theCaption, 0);
    
        return 0;
    }

    The first argument is passed as IntPtr::Zero, which is how you assign a null value to an IntPtr. We pass null because in this simple example we aren’t concerned with setting the owner.

  5. When you build and run the application, you’ll see a MessageBox displayed on the screen, as shown in the following screen shot:

    A screenshot of the console window showing how P/Invoke allows you to execute unmanaged API calls from managed code. A standard message box appears in the middle of the screen with the words “Hello World” as the caption, a title, and an OK button.

The DllImportAttribute class

You used the DllImportAttribute class in the previous exercise to provide a prototype for an unmanaged function. This class has a number of fields (data members) that can be used when constructing the prototype, and they’re listed in the following table:

Field

Description

BestFitMapping

Selects a suitable replacement character where an exact conversion does not exist, for example, using “c” instead of a © symbol.

CallingConvention

Defines the calling convention used when passing arguments to the unmanaged function.

CharSet

Defines how characters and strings are to be handled during marshaling.

EntryPoint

Indicates the name or ordinal number of the DLL function to be called.

ExactSpelling

Indicates whether the name of the entry point should be modified to correspond to the character set in use.

PreserveSig

Used for COM methods, this field should be set to true if the return values from methods shouldn’t be altered in any way.

SetLastError

If true, the caller can use the Win32 GetLastError function to determine whether an error occurred.

ThrowOnUnmappableCharacter

If true, will throw an exception when a best-fit match for a character is not available.

Let’s look at the more common fields in detail. CallingConvention defines how arguments are passed between the managed and unmanaged code, and will take one of the values in the Calling Convention enumeration. Different languages use different ways of passing arguments, so Windows supports a number of different calling conventions. C and C++ normally use the C calling convention, often known as Cdecl, whereas many other Windows languages use the standard calling convention, often abbreviated to StdCall. You call Windows API functions by using StdCall, which is the default, unless you use the CallingConvention field to choose another.

With CharSet, you can specify how characters and strings are to be marshaled. It takes one of the values from the CharSet enumeration. You can specify CharSet::Ansi, in which case all characters and strings are converted to one-byte ANSI characters and an “A” is appended to the name of the DLL entry point. Choosing CharSet::Unicode converts characters and strings to use two-byte Unicode characters and appends a “W” to the entry point name. However, it’s usually sufficient to specify CharSet::Auto, which chooses the best option for the host system.

Using the EntryPoint field, you can specify the name or ordinal number of the entry point in the DLL. If you don’t specify this field, as in the preceding exercise, the entry point name is taken to be the function name given in the prototype. A name given using the EntryPoint field takes precedence over the prototype name, so this gives you the ability to provide synonyms for unmanaged functions if you want to refer to them by another name when calling them in your code. The following code fragment shows how you could define a synonym for the MessageBox function:

[DllImport("User32.dll", EntryPoint="MessageBox",
               CharSet=CharSet::Auto)]
int WindowsMessageBox(IntPtr hwnd, String ^text,
                      String ^caption, unsigned int type);

You call the function as WindowsMessageBox, and the call is mapped onto the appropriate MessageBox entry point in User32.dll.

Passing structures

You’ll often need to pass structured data to arguments to unmanaged functions, and you must do this carefully. In particular, you need to specify the way structures are laid out in memory to be sure that they are passed around correctly. You specify the layout of structures and classes by using the StructLayoutAttribute and FieldOffsetAttribute classes.

You add StructLayoutAttribute to managed types to define a formatted type with a particular layout. There are three possible layout types that you can specify for a formatted type:

  • Automatic layout (LayoutKind::Auto), in which the runtime might reorder the members if it is more efficient. You never use automatic layout for types that are going to be used with P/Invoke because you need to be sure that everything stays in the same order.

  • Explicit layout (LayoutKind::Explicit), in which members are ordered according to byte offsets specified by FieldOffset attributes on each field.

  • Sequential layout (LayoutKind::Sequential), in which members appear in unmanaged memory in the same order they appear in the managed definition.

The following exercise shows how to call an unmanaged Windows API function that needs to be passed a structure. The function is GetSystemPowerStatus, which reports on the AC and battery status of the system. The Windows API defines a structure SYSTEM_POWER_STATUS, which contains the status information. The definition of this unmanaged structure is shown here:

typedef struct _SYSTEM_POWER_STATUS {
    BYTE ACLineStatus;
    BYTE  BatteryFlag;
    BYTE  BatteryLifePercent;
    BYTE  Reserved1;
    DWORD  BatteryLifeTime;
    DWORD  BatteryFullLifeTime;
} SYSTEM_POWER_STATUS, *LPSYSTEM_POWER_STATUS;

The prototype for the GetSystemPowerStatus function in the API documentation is this:

BOOL GetSystemPowerStatus(
  LPSYSTEM_POWER_STATUS lpSystemPowerStatus  // status
);

The function takes a pointer to a SYSTEM_POWER_STATUS structure, fills it in, and hands back the filled structure, returning a Boolean value to let you know whether it worked. Your task is to call this function, passing over a structure, and then display the results.

  1. Create a new CLR Console Application project named PowerMonitor.

  2. Add the following using directive:

    using namespace System::Runtime::InteropServices;

    This makes it easier to refer to the attributes we’ll be using later.

  3. Define a managed equivalent for the structure.

    [StructLayoutAttribute(LayoutKind::Sequential)]
    ref class PStat {
    public:
        System::Byte ACLineStatus;
        System::Byte BatteryFlag;
        System::Byte BatteryLifePercent;
        System::Byte Reserved1;
        System::UInt32 BatteryLifeTime;
        System::UInt32 BatteryFullLifeTime;
    };

    Our equivalent of SYSTEM_POWER_STATUS is a managed class named PStat. The original definition contains two Windows data types: BYTE, which represents a one-byte integer, and so can be represented by the System::Byte type; and DWORD, which is a 32-bit unsigned integer, and so is represented by System::UInt32. The StructLayoutAttribute is attached to the class, and LayoutKind::Sequential is specified so that the layout of the members will remain the same as the data is passed through P/Invoke.

  4. Define the prototype for the GetSystemPowerStatus function, as shown here:

    // Define the BOOL type
    typedef int BOOL;
    
    // Prototype for the function
    [DllImport("Kernel32.dll", CharSet=CharSet::Auto)]
    BOOL GetSystemPowerStatus(PStat ^ps);

    BOOL is a Windows type representing a Boolean value and is actually a typedef for an integer. It has been widely used in the Windows API because C lacks a true Boolean type. The prototype uses the real name of the function as it occurs in Kernel32.dll, and the single argument is given as a handle to our managed type.

  5. Write the code to call the function. Edit the main function to create a PStat object and use it to call the function, as illustrated in the following:

    int main(array<String^>^ args)
    {
        Console::WriteLine("Power Status Test...");
        PStat ^ps = gcnew PStat();
    
        BOOL b = GetSystemPowerStatus(ps);
        Console::WriteLine("Got status, return was {0}", b);
    
        return 0;
    }

    If the call worked, the return value should be nonzero, which represents a Boolean true value.

  6. Build and run the application at this point, correcting any errors and checking the output.

  7. Add code to report on the members of the class.

    // Report on the AC line status
    Console::Write("AC line power status is ");
    switch(ps->ACLineStatus) {
    case 0:
        Console::WriteLine("'off'");
        break;
    case 1:
        Console::WriteLine("'on'");
        break;
    case 255:
        Console::WriteLine("'unknown'");
        break;
    }
    
    // Report on the battery status
    Console::Write("Battery charge status is ({0})",
                  ps->BatteryFlag);
    if (ps->BatteryFlag & 1)
        Console::Write(" 'high'");
    if (ps->BatteryFlag & 2)
        Console::Write(" 'low'");
    if (ps->BatteryFlag & 4)
        Console::Write(" 'critical'");
    if (ps->BatteryFlag & 8)
        Console::Write(" 'charging'");
    if (ps->BatteryFlag & 128)
        Console::Write(" 'no system battery'");
    Console::WriteLine();
    
    // What's the percentage charge left in the battery?
    // A value of 255 means unknown
    if (ps->BatteryLifePercent == 255)
        Console::WriteLine("Battery life unknown");
    else
        Console::WriteLine("Battery life is {0}%",
            ps->BatteryLifePercent);
    
    // How many seconds battery life is left?
    if (ps->BatteryLifeTime == -1)
        Console::WriteLine("Battery life in seconds: Unknown");
    else
        Console::WriteLine("Battery seconds remaining: {0} secs",
                          ps->BatteryLifeTime);

    The first check is on the ACLineStatus field, which will have the value 0 (on), 1 (off), or 255 (unknown). The second check is on the status of the battery, and this value can be made up of one or more of the values 1 (high charge), 2 (low charge), 4 (critically low charge), 8 (charging), and 128 (no battery present). Each of these represents a particular bit position within the result, and the bitwise OR operator (&) is used to check which bits are set.

    The final two checks print out the percentage of lifetime left in the battery and the number of seconds. If the function can’t determine the number of seconds, it will return –1 in this field.

  8. Build and run the application. You will obviously achieve the best results if you run it on a laptop.

Quick reference

To

Do this

Obtain a safe handle to a managed object so that it won’t be garbage-collected while being used.

Use the System::Runtime::InteropServices::GCHandle::Alloc function to wrap a pointer to a managed object in a GCHandle. The easiest way to do this is to use the gcroot helper class. For example:

Foo ^ff = gcnew Foo();
gcroot<Foo^> pf = ff;

This code wraps the pointer to the Foo object with a GCHandle, and handles cleanup when the gcroot is destroyed.

Fix all or part of a managed object in memory so that it can be used safely by unmanaged code.

Use pin_ptr<> to create a pinning pointer. For example:

pin_ptr<Foo> p = gcnew Foo();

The managed Foo object won’t be moved in memory or garbage-collected until the pinning pointer goes out of context or has null assigned to it.

Convert a value type to an object so that it can be used where an object is required.

This will happen automatically. Note that the value in the box is a copy of the original.

Retrieve the value from a boxed object.

Use safe_cast to cast the boxing object to the correct type, and then dereference the pointer. For example:

int myVal = safe_cast<int>(po);

Call an unmanaged function in a DLL.

Use the P/Invoke mechanism by declaring a prototype for the unmanaged function that uses the DllImport attribute to specify the DLL in which the function resides and other optional parameters.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.37.38