Programming Microsoft Windows Ce Net 3Rd [Electronic resources] نسخه متنی

اینجــــا یک کتابخانه دیجیتالی است

با بیش از 100000 منبع الکترونیکی رایگان به زبان فارسی ، عربی و انگلیسی

Programming Microsoft Windows Ce Net 3Rd [Electronic resources] - نسخه متنی

| نمايش فراداده ، افزودن یک نقد و بررسی
افزودن به کتابخانه شخصی
ارسال به دوستان
جستجو در متن کتاب
بیشتر
تنظیمات قلم

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

روز نیمروز شب
جستجو در لغت نامه
بیشتر
لیست موضوعات
توضیحات
افزودن یادداشت جدید






Accessing the Underlying Operating System

Even with the new classes provided by the Compact Framework, the functionality of the Compact Framework base class library is significantly less than that of its desktop counterpart. The need to keep the run-time libraries small to reduce the hardware impact, the decision to focus on client-side processing, and the pressure to ship a version 1.0 product on time all resulted in a class library that, although functional, lacks some of the basics expected by .NET programmers.

Because of the limited class library, Compact Framework applications frequently need to escape the bounds of the .NET runtime sandbox and call unmanaged routines. The process of calling outside the .NET environment is called Process Invoke, almost always referred to by the abbreviation P/Invoke.


P/Invoke


The task of calling unmanaged code begins with declaring a static external method in a class with parameters that match the calling parameters of the unmanaged function. The method is then marked with a special attribute to indicate its purpose to the compiler. At run time, the method is simply called like any other method in the class. The call to the method results in the Compact Framework loading the DLL containing the function to be called, marshaling the calling parameters onto the stack, and calling the function.

The following is a simple but contrived example of calling outside the framework. Before I dive into the discussion of this example, be aware that there are managed ways of computing elapsed time without calling into unmanaged code. I'm simply using GetTickCount since it's a simple function with no parameters.

public class SomeClass
{
private uint OldCnt;
public void SomeClass()
{
OldCnt = 0;
}
[DllImport ("coredll.dll")]
private static extern uint GetTickCount ();
public uint TicksSinceLast (){
uint ticks = GetTickCount();
uint diff = ticks – OldCnt;
OldCnt = ticks;
return diff;
}
}

This code shows a class that contains two methods, GetTickCount and TicksSinceLast. The GetTickCount method is marked as static, meaning that it's defined by the class, not an instance of the class; and extern, meaning that the method body is defined outside the class. In this case, the method body is actually implemented in unmanaged code.

Just above the definition of GetTickCount is the DllImport attribute enclosed in the square brackets. The DllImport attribute marks the method as being a P/Invoke call to unmanaged code. The single parameter for DllImport, in this case, is the name of the unmanaged DLL that implements the function GetTickCount. The DLL, Coredll.dll, is the standard API DLL for Windows CE and exposes most of the functions supported by the Windows CE operating system, including GetTickCount.

The TicksSinceLast method calls GetTickCount just as it would any other managed method in the class. No special syntax is necessary for the method when the class calls it. There is also no need for the P/Invoke method to be marked private, although it typically is good practice since the programmer who wrote the class knows the method is a P/Invoke call and can provide proper precautions such as couching the call in a try, catch block to catch exceptions specific to the P/Invoke call.

The DllImport attribute class can specify more than simply the name of the DLL to call. Although the Compact Framework supports other fields in DllImport, only two are useful to the application: EntryPoint and SetLastError.

The EntryPoint field of DllImport allows the application to specify a name for the unmanaged entry point in the DLL that's different from the name of the managed method. The EntryPoint field is handy when calling a Windows CE API that has a string as a parameter. Win32 API convention specifies that the real name of a function with a string parameter have a suffix of either W or A, depending on whether the function expects Unicode- or ANSI-formatted strings. Even though Windows CE supports only Unicode entry points, the names of the functions exported by Coredll.dll have the W suffix. C and C++ applications don't normally see the W suffix because the .h files in the SDK redefine the generic function names used in the application without the W or A to the specific API name that's used when the application is compiled. The following code fragment shows an example of specifying the function name when calling the unmanaged API SetWindowText:

[DllImport ("coredll.dll", EntryPoint="SetWindowTextW")]
private static extern void SetWindowText (IntPtr h, string s);

The name traditionally used for the entry point is SetWindowText. However, Coredll.dll exports the function as SetWindowTextW. The use of the EntryPoint field in the DllImport attribute specifies the correct entry point name while retaining the traditional name for references within the managed code.

The other useful field in the DllImport attribute class is SetLastError. This field is defined as a bool. Setting this field to true tells the runtime to save the last error value set by the call to the unmanaged code. This allows the managed code to later call the GetLastWin32Error method of the Marshal class to retrieve the last error value. If the SetLastError field is not set to true, the default is not to save the last error value of the P/Invoke call.

None of the other fields in the DllImport attribute class that are supported by the .NET Compact Framework have much use. The CharSet field allows the application to specify whether the strings being passed to the unmanaged code should be converted to ANSI or remain Unicode. On the Compact Framework, the only values supported for the CharSet field are Auto and Unicode. Since Auto defaults to Unicode on the Compact Framework, these two values mean the same thing. The CallingConvention field can also be set, but here again, the single value supported by the Compact Framework has no real effect on the processing of the P/Invoke call.


P/Invoke Arguments


While declaring methods that call outside the run-time environment is easy, sometimes declaring the proper parameters for passing and receiving data can be a bit of a challenge. The Compact Framework is limited in the data types that can be easily passed between managed and unmanaged code.

Simple Types


The process of gathering the data and passing it to and from unmanaged code is called marshaling. The marshaling support in the Compact Framework is limited to blittable data types. Blittable types are represented in managed and unmanaged code in the same format and therefore do not require translation on the call stack between the two environments. Essentially, this definition covers all simple numeric and character data types in the Compact Framework, such as signed and unsigned longs, ints, shorts, bytes, and characters. The Compact Framework also supports passing one-dimensional arrays of blittable types and structures that contain blittable types. Strings can also be passed as parameters, although they are always passed as Unicode strings.

Table 23-1 relates managed types to their corresponding unmanaged types.













































Table 23-1: Cross-Reference Between Managed Data Types and Their Unmanaged C++ Counterparts.

Managed Type


Passed by Value


Passed by Reference


byte, sbyte


BYTE, char


byte [*], char [*]


short, ushort


SHORT, WORD


SHORT[*], WORD[*]


int, uint


Int, DWORD


int[*], DWORD[*]


long


unsupported


INT64[*]


float


unsupported


float[*]


double


unsupported


double[*]


IntPtr


PVOID


PVOID[*]


bool


BYTE[† *]


BYTE[*]


string


LPCWSTR [† *]


unsupported


StringBuilder


LPWSTR


unsupported


[*]A nonzero value is true.

[† *] The string type should be treated as read-only by the unmanaged routine.


Structures and Arrays


Simple structures can also be passed by reference in P/Invoke calls. A simple structure is one that contains only blittable types. The structure can't contain arrays or strings. The following code shows two code fragments, one managed and the other unmanaged. The simple structure passed from the managed to the unmanaged code contains three integers and must be defined in both the managed and the unmanaged code.

[StructLayout(LayoutKind.Sequential)]
public struct Size3D
{
public uint height;
public uint width;
public uint depth;
}
[DllImport ("Unmanaged.dll")]
private static extern uint GetContainerSize (ref Size3D b);
private uint ComputeVolume()
{
Size3D siz;
// Provide default size
siz.height = 1;
siz.width = 1;
siz.depth = 1;
// Call the unmanaged code
GetContainerSize (ref siz);
uint Volume = siz.height * siz.width * siz.depth;
}
//
// Unmanaged code
//
typedef struct {
DWORD dwHeight;
DWORD dwWidth;
DWORD dwDepth;
} SIZE3D;
DWORD GetContainerSize (SIZE3D *pSiz) {
pSiz->dwHeight = 43;
pSiz->dwWidth = 12;
pSiz->dwDepth = 2;
return 1;
}

The definition of the managed structure, Size3D, is preceded by a StructLayout attribute. The single parameter is the enumeration LayoutKind. The only enumeration value defined in the initial version of the Compact Framework is Sequential. Although this is the default layout in the Compact Framework, it's a good technique to specify this attribute for every structure being passed to unmanaged code because it's typically required on the desktop version of the .NET runtime. Specifying it here makes it easy to remember to use it if the code is reused on the desktop.

Arrays can be passed as well, with some limitations. One-dimensional arrays of blittable types can be passed. For example, an array of integers can be passed to unmanaged code, as shown in the following code:

[DllImport ("UMTest.dll")]
private static extern int DiffFromAve (int[] a, int cnt);
int ManagedRoutine ()
{
// Declare and initialize the array
int[] array = new int[4];
array[0] = 10;
array[1] = 15;
array[2] = 4;
array[3] = 30;
// Call the unmanaged routine
int ave = DiffFromAve (array, array.Length);
Console.WriteLine ("ave:{0} {1} {2} {3} {4}", ave,
array[0], array[1], array[2], array[3]);
return ave;
}
//
// Unmanaged code
//
DWORD DiffFromAve (int *pnArray, int nCnt) {
int i, sum = 0, ave = 0;
if (pnArray) {
// Compute sum
for (i = 0; i < nCnt; i++)
sum += pnArray[i];
// Compute ave
ave = sum / nCnt;
// Set differnce from ave
for (i = 0; i < nCnt; i++)
pnArray[i] -= ave;
}
return ave;
}

This code shows an array of four elements being passed to unmanaged code. Notice that the unmanaged code can read and write the elements of the array. The example is of course contrived since nothing is done in the unmanaged code that couldn't be accomplished in managed code. However, the example does show the process.

Working with Strings


One of the more nettlesome issues with calling unmanaged code is how to deal with strings. On the desktop, programmers actually have a somewhat more difficult time since they have to deal with both ANSI and Unicode strings. In the Compact Framework, we typically deal only with Unicode strings since the operating system we're calling, Windows CE, uses Unicode strings almost exclusively.

Passing a string down to unmanaged code is actually quite simple. A string class can simply be passed in the parameter list as it would be for any other method. The string appears to the unmanaged code as a pointer to a constant Unicode string. The constant modifier is necessary since the string can't be modified by the unmanaged code. The following routine passes down a string to an unmanaged routine:

int ManagedRoutine ()
{
uint len = MyGetLength ("This is a string");
}
//
// Unmanaged code
//
DWORD MyGetLength (LPCWSTR pszStr) {
DWORD dwLen;
dwLen = lstrlen (pszStr);
return dwLen;
}

To have the unmanaged routine modify the string takes a bit more work. Instead of passing a string class, the managed routine must pass a StringBuilder class. To do this, create a StringBuilder class large enough to hold the string that will be returned. The class can also be initialized with a string if the unmanaged code expects an initialized string. In the following code, the managed code calls the Windows CE function GetWindowText, which takes three parameters: the handle of the window to query, a buffer to hold the title text of the window, and the maximum number of characters that the buffer can hold.

[DllImport( "coredll.dll", EntryPoint="GetWindowTextW")]
public static extern int GetWindowText (IntPtr h, StringBuilder str,
int size);
public int ManagedRoutine ()
{
StringBuilder strWndText = new StringBuilder (256);
GetWindowText (h, strWndText, strWndText.Capacity);
}

This code first declares the external method GetWindowText, which is defined in Coredll.dll as GetWindowTextW since it returns a Unicode string. The managed routine simply creates a StringBuilder class of some large length and then passes it to the method. The Capacity method of StringBuilder returns the maximum number of characters that the instance of the class can hold.

The third way a string can be used in a P/Invoke call comes into play when an unmanaged routine returns a pointer to a string as the return value. The Compact Framework can't marshal any return parameter larger than 32 bits, but since the return value is a pointer to a string, this condition is satisfied.

When an unmanaged routine returns a pointer to a string, it should be cast as an IntPtr. The string pointed to by the IntPtr can then be converted to a managed string using the Marshal class's PtrToStringUni method. The following routine calls the GetCommandLine Windows API, which returns a pointer to the command line as its return value.

[DllImport ("coredll.dll")]
public static extern IntPtr GetCommandLine();
public int ManagedRoutine ()
{
IntPtr pCmdLine = GetCommandLine();
string strCmdLine = Marshal.PtrToStringUni (pCmdLine);
}

/ 169