Invoking Methods in Unmanaged DLLs

I l @ ve RuBoard

Invoking Methods in Unmanaged DLLs

Despite its aim of being a globally portable platform, the Java language has always acknowledged the potential need to interact with non-Java code. The Java platform defines the Java Native Interface (JNI), which allows a developer to create native language adapters (typically written in C or C++) for interfacing with non-Java libraries. JNI provides tools and defines language bindings, allowing you to pass Java objects and types to a native language adapter, which can then convert them to native language types before forwarding them to the target library. Although JNI can generate skeleton code for an adapter, you must implement the adapter yourself. Thus, although the scheme used is highly flexible, you must have a good understanding of how to map Java types to native types and vice versa to use it successfully.

The JNI is not available in J#, but if you want to access functions implemented in native DLLs, all is not lost. J/Direct, a feature that Microsoft provided with Visual J++ 6.0, is also available with J#.

Using J/Direct

J/Direct uses compiler directives to define native methods declaratively . The common language runtime loads the target DLL and performs any data marshaling indicated automatically without your having to write an adapter. All you have to do is specify which functions in which DLLs you want to call, and then you can call those functions as if they were regular methods in the common language runtime. The common language runtime provides the .NET interop marshaler that converts data from managed J# formats and passes them to the unmanaged space as the corresponding unmanaged types. It converts output parameters and return values back to managed J# types.

Isomorphic and Nonisomorphic Types

Managed types that have directly equivalent unmanaged types can be passed from managed to unmanaged space without the marshaler having to perform any work. This is because the layout of these types in memory is the same in managed and unmanaged space. Such types are referred to as isomorphic (or blittable ). In other instances, the marshaler might need to perform some rearrangement of data in memory. These types are referred to as nonisomorphic. In the simplest cases, the compiler and the runtime can establish how to map J# types into their unmanaged equivalents automatically. Table 13-1 lists the mappings that are implemented automatically when you use J/Direct with J#. In more complex cases, such as when you pass complex data types or interfaces as parameters, you might need to provide explicit information to the marshaler about the various conversions required.

Table 13-1. Java and Windows Native Type Mappings

Java Type

Native Type

byte

BYTE or CHAR

short

SHORT or WORD

int

INT , UINT , LONG , ULONG, HWND or DWORD

Char

TCHAR

Long

__int64

Float

Float

Double

Double

boolean

BOOL

java.lang.String , System.String (immutable)

LPCTSTR (ANSI) or LPCWSTR (Unicode); not allowed as a return type

java.lang.StringBuffer (mutable)

LPTSTR (ANSI) or LPWSTR (Unicode); not allowed as a return type

java.lang.Integer

4-byte integer

java.lang.Boolean

4-byte BOOL

java.lang.Char

CHAR (ANSI) or WCHAR (Unicode)

java.lang.Short

2-byte SHORT

java.lang.Float

4-byte FLOAT

java.lang.Object , System.Object

Pointer to a struct

Void

VOID ; allowed only as a return type

byte[]

BYTE *

short[]

WORD *

char[]

TCHAR *

int[]

DWORD *

float[]

float *

double[]

double *

long[]

__int64 *

Warning

The Java language does not have any unsigned types, so an int marshaled as a UINT might contain a negative value and be misinterpreted by the unmanaged function that is called. Therefore, you must perform all possible range checking on parameters before passing them to DLLs if you want to avoid potentially confusing results.


Declaring Native Methods

You declare a native method and bring its name into scope in J/Direct using the @dll.import directive. For example, the MessageBeep function, which is used to output the various burps and other noises emitted by Windows and implemented in \WINDOWS\System32\User32.dll, is defined in the Platform SDK in this way:

 BOOLMessageBeep(UINTuType//soundtype); 

The corresponding J/Direct definition that you can create in J# is as follows :

 /**@dll.import("USER32.DLL")*/ privatestaticnativebooleanMessageBeep(intuType); 

Notice the use of @dll.import before the method declaration, which indicates the DLL supplying the native method. The method itself must be declared as static native . Having declared the method, you can invoke it as if it were a regular Java method.

Note

You can omit the .dll extension from the filename in the @dll.import directive as long as the file actually has the extension .dll . (Some earlier versions of Windows had EXE files that were really DLLs, so we always prefer to be explicit.)


Marshaling Struct Objects

Many native methods take complex parameters, which are usually implemented as struct objects. Java doesn't have struct objects ”instead, you have to define classes that have the same public fields as these structures. For example, consider the FlashWindowEx method, which is also implemented in User32.dll. This method lets you flash the title bar and taskbar button for a window to attract the user's attention. Windows itself often uses this feature if an application that has been minimized suddenly requires the user 's attention, but you can also exploit it for other ends.

The C++ definition of the FlashWindowEx function is shown here:

 BOOLFlashWindowEx(PFLASHWINFOpfwi//flashstatusinformation); 

The parameter is a pointer to a FLASHWINFO structure, which is defined like this:

 typedefstruct{ UINTcbSize;//Thesizeofthestruct HWNDhwnd;//ThehandleoftheWindowtoflash DWORDdwFlags;//Howtoflashthewindow UINTuCount;//Howmanytimestoflashthewindow DWORDdwTimeout;//Timebetweenflashes }FLASHWINFO,*PFLASHWINFO; 

To use FlashWindowEx from J#, you must first define a class that matches the FLASHWINFO structure:

 /**@dll.struct(pack=4)*/ publicclassFLASHWINFO { intcbSize; inthWnd; intdwFlags; intuCount; intdwTimeout; } 

The @dll.struct directive identifies the class to the marshaler. The actual names used by the fields (and the class itself) do not have to be the same as the original structure, although it makes things easier to understand and maintain if they are.

You must take care of one complication when you marshal J# classes as struct objects: field packing and alignment. By default, in the common language runtime, fields in classes are aligned on 8-byte boundaries. Therefore, in the FLASHWINFO class there will be 4 bytes of dead space between each field, (An int is 4 bytes in size.) However, the corresponding structure in the Windows API will align these same fields on 4-byte boundaries, resulting in a mismatch between the FLASHWINFO class and the FLASHWINFO struct. To fix this problem, you can specify the pack modifier for the @dll.struct directive. Using a value of 4, as shown above, causes fields to be aligned on 4-byte boundaries with no dead space in between.

Caution

Packing is quite a complex topic, and the way in which fields in a struct are packed depends to some extent on how the struct is defined and the compiler options that were used to build the native DLL. Do not blindly use the value 4 as a magic number everywhere ”it might not always work. You must understand a little about the native structure that's being mapped to. For more information about the relationship between field alignment and packing levels, see the Windows Platform SDK documentation or the ANSI C draft 3.5.2.1.


After you define the class and declare the native method, you can use them. The JDirect project, which is included in the book's sample files, shows a simple Windows-based application written in J# that uses the FlashWindowEx method in the constructor of the main form (called CakeForm , in the file CakeForm.jsl). Part of the code is shown here:

 publicclassCakeFormextendsSystem.Windows.Forms.Form { /** *ConstantsusedbytheFlashWindowExfunction */ privatefinalintFLASHW_STOP=0x00000000; privatefinalintFLASHW_CAPTION=0x00000001; privatefinalintFLASHW_TRAY=0x00000002; privatefinalintFLASHW_ALL=(FLASHW_CAPTIONFLASHW_TRAY); privatefinalintFLASHW_TIMER=0x00000004; privatefinalintFLASHW_TIMERNOFG=0x0000000C; publicCakeForm() { //Getthehandleofthewindow IntPtrhWnd=this.get_Handle(); //CreateandpopulateaFLASHWINFOobject FLASHWINFOwindowInfo=newFLASHWINFO(); //ThesizeofthewindowInfoobject windowInfo.cbSize=20; //Thehandleofthecurrentwindow windowInfo.hWnd=hWnd.ToInt32(); //Flashthecaptionandtaskbarbuttoncontinuouslyuntilstopped windowInfo.dwFlags=FLASHW_TIMERFLASHW_ALL; //Numberoftimestoflash-ignoredifflashingcontinuously windowInfo.uCount=0; //Flashtwiceasecond windowInfo.dwTimeout=500; //CallFlashWindowExtostartthewindowflashing FlashWindowEx(windowInfo); } } 

The Platform Invoke Service

Strictly speaking, J/Direct is provided with J# for backward compatibility with existing Visual J++ projects, and you should not try to use it from languages other than J#. However, the .NET Framework provides its own generic mechanism for invoking unmanaged code: the Platform Invoke Service (P/Invoke). P/Invoke is more powerful and flexible than J/Direct, and you should prefer it over J/Direct if you're developing code from scratch.

As with J/Direct, using P/Invoke involves creating a method definition (or prototype) for each unmanaged function and specifying whether any special marshaling of parameters is required. Let's look at an example.

Calling the System Restore API

If you're familiar with Windows XP, you might have come across the System Restore API. This feature, which is commonly used by application installation programs, provides a facility to undo any changes made to the system if it becomes unstable as a result of the installation. Using the System Restore API, you can record the current state of the operating system and installed applications. If any subsequent changes ”installed applications or devices, for example ”cause instability in the system, you can roll them back. The System Restore API was designed to be called from applications developed using C++ or C. The current release of the .NET Framework Class Library does not provide direct access to the System Restore API, so if you want to employ it from J#, you must use P/Invoke.

The System Restore API works by monitoring selected files on a drive-by-drive basis and making copies of these files before changing them when the system is updated. Toward this end, a driver in the Windows XP operating system intercepts operations on files. The copies are compressed to save space. Each set of saved files is referred to as a restore point . A user can revert the system to the state determined by any restore point captured on the computer using the System Restore tool, which is available from the System Tools folder in the Accessories program group .

In the following example, we'll examine how to invoke the System Restore API from a J# program. The application itself will create a restore point, install a dummy application, and then commit the restore point. You can then use the System Restore tool to show that the restore point was successfully created and then roll back the application installation and uninstall it.

Tip

For the System Restore API to work, you must make sure that System Restore is enabled. In Control Panel, select Performance and Maintenance, and click System. In the System Properties dialog box click the System Restore tab and make sure that the Turn Off System Restore check box is deselected.


To use the System Restore API, you must define the J# equivalent of the data structures used by the various System Restore methods, and provide information on how to marshal any nonisomorphic data that will not be automatically converted by P/Invoke. You must also declare the System Restore API methods to bring them into scope in the J# application and to direct P/Invoke to the appropriate unmanaged DLL. A common strategy is to create a J# class to act as a wrapper for the unmanaged method calls and data structures. This class can be deployed in its own reusable library, although to keep this example simple, we have defined it in the same project as the code that calls it.

The System Restore API contains two important methods, SRSetRestorePoint and SRRemoveRestorePoint , along with two data structures, RESTOREPOINTINFO and STATEMGRSTATUS . The API is implemented in the unmanaged library Srclient.dll. The file SrRestorePtApi.h, which is supplied with the Platform SDK (not Visual Studio .NET), contains C++ definitions of these functions and data structures, as shown next .

Note

The System Restore API actually defines two versions of the SRSetRestorePoint function and the RESTOREPOINTINFO data structure ”for ANSI and Unicode implementations . The function and structure are distinguished by different suffixes in their names ” A for ANSI and W for Unicode. We'll restrict our discussion to the Unicode version here, mainly because the common language runtime uses Unicode.


SrRestorePtApi.h
 /************************************************************************** Copyright(c)2000MicrosoftCorporation ModuleName: SRRestorePtAPI.h Abstract: ThisfilecontainsthedeclarationsfortheSRRESTOREPT_API **************************************************************************/ #if!defined(_SRRESTOREPTAPI_H) #define_SRRESTOREPTAPI_H // //TypeofEvent // #defineMIN_EVENT100 #defineBEGIN_SYSTEM_CHANGE100 #defineEND_SYSTEM_CHANGE101 #defineBEGIN_NESTED_SYSTEM_CHANGE102//forWhistleronly-usethis //topreventnestedrestorepts #defineEND_NESTED_SYSTEM_CHANGE103//forWhistleronly-usethis //topreventnestedrestorepts #defineMAX_EVENT103 // //TypeofRestorePoints // #defineMIN_RPT0 #defineAPPLICATION_INSTALL0 #defineAPPLICATION_UNINSTALL1 #defineDESKTOP_SETTING2/*Notimplemented*/ #defineACCESSIBILITY_SETTING3/*Notimplemented*/ #defineOE_SETTING4/*Notimplemented*/ #defineAPPLICATION_RUN5/*Notimplemented*/ #defineRESTORE6 #defineCHECKPOINT7 #defineWINDOWS_SHUTDOWN8/*Notimplemented*/ #defineWINDOWS_BOOT9/*Notimplemented*/ #defineDEVICE_DRIVER_INSTALL10 #defineFIRSTRUN11 #defineMODIFY_SETTINGS12 #defineCANCELLED_OPERATION13/*OnlyvalidforEND_SYSTEM_CHANGE*/ #defineBACKUP_RECOVERY14 #defineMAX_RPT14 #defineMAX_DESC64 #defineMAX_DESC_W256//longerforWhistler // //forMillenniumcompatibility // #pragmapack(push,srrestoreptapi_include) #pragmapack(1) // //Restorepointinformation // typedefstruct_RESTOREPTINFOA{ DWORDdwEventType;//TypeofEvent-BeginorEnd DWORDdwRestorePtType;//TypeofRestorePointApp //install/uninstall INT64llSequenceNumber;//SequenceNumber-0forbegin CHARszDescription[MAX_DESC];//Description-Nameof //Application/Operation }RESTOREPOINTINFOA,*PRESTOREPOINTINFOA; typedefstruct_RESTOREPTINFOW{ DWORDdwEventType; DWORDdwRestorePtType; INT64llSequenceNumber; WCHARszDescription[MAX_DESC_W]; }RESTOREPOINTINFOW,*PRESTOREPOINTINFOW; // //StatusreturnedbySystemRestore // typedefstruct_SMGRSTATUS{ DWORDnStatus;//StatusreturnedbyStateManagerProcess INT64llSequenceNumber;//SequenceNumberfortherestorepoint }STATEMGRSTATUS,*PSTATEMGRSTATUS; #pragmapack(pop,srrestoreptapi_include) #ifdef__cplusplus extern "C" { #endif // //RPCcalltosetarestorepoint // //ReturnvalueTRUEifthecallwasasuccess //FALSEifthecallfailed // //IfpSmgrStatusnStatusfieldissetasfollows // //ERROR_SUCCESSIfthecallsucceeded(returnvaluewillbeTRUE) // //ERROR_TIMEOUTIfthecalltimedoutduetoawaitonamutexfor //forsettingrestorepoints. // //ERROR_INVALID_DATAIfthecancelrestorepointiscalledwith //aninvalidsequencenumber // //ERROR_INTERNAL_ERRORIfthereareinternalfailures. // //ERROR_BAD_ENVIRONMENTIftheAPIiscalledinSafeMode // //ERROR_SERVICE_DISABLEDIfSystemRestoreisDisabled. // //ERROR_DISK_FULLIfSystemRestoreisfrozen(Windows //Whistleronly) // //ERROR_ALREADY_EXISTSIfthisisanestedrestorepoint BOOL__stdcall SRSetRestorePointA(PRESTOREPOINTINFOApRestorePtSpec,//[in]RestorePointspecification PSTATEMGRSTATUSpSMgrStatus//[out]Statusreturned); BOOL__stdcall SRSetRestorePointW(PRESTOREPOINTINFOWpRestorePtSpec, PSTATEMGRSTATUSpSMgrStatus); DWORD__stdcall SRRemoveRestorePoint(DWORDdwRPNum); #ifdef__cplusplus } #endif #ifdefUNICODE #defineRESTOREPOINTINFORESTOREPOINTINFOW #definePRESTOREPOINTINFOPRESTOREPOINTINFOW #defineSRSetRestorePointSRSetRestorePointW #else #defineRESTOREPOINTINFORESTOREPOINTINFOA #definePRESTOREPOINTINFOPRESTOREPOINTINFOA #defineSRSetRestorePointSRSetRestorePointA #endif #endif//!defined(_RESTOREPTAPI_H) 
Defining Constants

The first task is to map the constants into J# syntax. In the previous example using J/Direct, we converted these definitions into private static final constants. This is OK, and it works, but it might be useful to make them more widely available in case the same constants need to be used in other classes or projects. The most obvious way to do this is to create a wrapper class and expose these constants as public static final values, as shown in the SystemRestoreWrapper class below (which is supplied in the SystemRestore project). This class can then be packaged as a library assembly, which allows it to be used elsewhere:

 /** *ClassthatwrapstheSystemRestoreAPIconstantsandfunctions *Definitionsaretakenfromsrrestoreptapi.h,suppliedwiththePlatform *SDK,andconvertedtoJ#syntax */ publicclassSystemRestoreAPIWrapper { //TypeofEvent publicstaticfinalintMIN_EVENT=100; publicstaticfinalintBEGIN_SYSTEM_CHANGE=100; publicstaticfinalintEND_SYSTEM_CHANGE=101; publicstaticfinalintBEGIN_NESTED_SYSTEM_CHANGE=102; publicstaticfinalintEND_NESTED_SYSTEM_CHANGE=103; publicstaticfinalintMAX_EVENT=103; //TypeofRestorePoints publicstaticfinalintMIN_RPT=0; publicstaticfinalintAPPLICATION_INSTALL=0; publicstaticfinalintAPPLICATION_UNINSTALL=1; publicstaticfinalintDESKTOP_SETTING=2; publicstaticfinalintACCESSIBILITY_SETTING=3; publicstaticfinalintOE_SETTING=4; publicstaticfinalintAPPLICATION_RUN=5; publicstaticfinalintRESTORE=6; publicstaticfinalintCHECKPOINT=7; publicstaticfinalintWINDOWS_SHUTDOWN=8; publicstaticfinalintWINDOWS_BOOT=9; publicstaticfinalintDEVICE_DRIVER_INSTALL=10; publicstaticfinalintFIRSTRUN=11; publicstaticfinalintMODIFY_SETTINGS=12; publicstaticfinalintCANCELLED_OPERATION=13; publicstaticfinalintBACKUP_RECOVERY=14; publicstaticfinalintMAX_RPT=14; publicstaticfinalintMAX_DESC_W=256; } 
Mapping Data Structures

The next task is to define the J# versions of the RESTOREPOINTINFO and STATEMGRSTATUS structures. You can accomplish this as before, by creating J# classes to represent these structures. The type of each field should match the corresponding type in the original structures. Remember that some types are isomorphic or are transformed automatically (for example, J# int and long fields are automatically converted into the equivalent 32-bit and 64-bit integer quantities when they're passed across to the unmanaged space) and others might require some explicit conversion. Taking the STATEMGRSTATUS structure (the simpler of the two) first, the equivalent J# class looks like this:

 publicclassSTATEMGRSTATUS { publicSystem.UInt32nStatus; publiclongllSequenceNumber; } 

Note that the Java language does not have unsigned types. For example, the closest Java type to DWORD in Windows is int , as you saw when we used J/Direct. However, the System namespace defines the UInt32 structure, which is used as the basis for unsigned 32-bit integer values in other .NET languages. You can also use this structure from J#. The marshaler recognizes this type and knows how to convert it to an unsigned 32-bit unmanaged value. The System namespace also contains the Int64 structure, which is equivalent to a Java language long , and Int32 , which is the same as a Java language int . However, the J# compiler will forbid you from exposing value types such as Int64 and Int32 , for which there are corresponding Java language types in the metadata of an assembly (although you can use them internally within classes). The only exceptions to this rule are the value types that have no Java language equivalent, such as UInt32 .

One additional piece of information is required. Because you have the same data packing and alignment issues mentioned previously, the marshaler needs to know how to lay out the data in memory when it is passed between managed and unmanaged space. You indicate this by tagging the class with the System.Runtime.InteropServices.StructLayoutAttribute . This attribute expects a System.Runtime.InteropServices.LayoutKind parameter to its constructor. LayoutKind is an enumeration containing three values: Explicit , Sequential , and Auto . Using the explicit option allows you to define exactly where each field in the class starts. (We'll look at this shortly.) The auto option allows the common language runtime to lay out the contents of the structure as it sees fit. The sequential option causes the fields to be laid out sequentially in the order in which they appear in the class.

The StructLayoutAttribute class also defines the Pack member, which you can use to control the alignment of sequential fields by specifying the byte-boundary where a field starts after the previous one finishes. Although Pack is an integer, you should set it to the value 0, 1, 2, 4, 8, 16, 32, 64, or 128. (0 specifies use of the default packing alignment.) A compiler error will be generated if you specify a value other than one of those in this set. By default, fields are aligned on 8-byte boundaries. To work properly, the fields in the STATEMGRSTATUS structure must be aligned on 4-byte boundaries. (See the earlier discussion on packing with J/Direct.) The completed STATEMGRSTATUS class is shown here:

 /**@attributeStructLayoutAttribute(LayoutKind.Sequential,Pack=4)**/ publicclassSTATEMGRSTATUS { publicUInt32nStatus; publiclongllSequenceNumber; } 

The RESTOREPOINTINFO structure requires a little more thought. The fields dwEventType , dwRestorePtType , and llSequenceNumber are straightforward enough. (They're similar to the fields in STATEMGRSTATUS .) But the szDescription field needs careful handling. If you look at the original C++ definition of RESTOREPOINTINFO , you'll see that the szDescription field is described as an array of Unicode characters :

 WCHARszDescription[MAX_DESC_W]; 

Arrays in the common language runtime are totally different from arrays in C++. It is actually more convenient in J# to define the szDescription field as a String and specify explicitly how the value should be marshaled into unmanaged space. To do this, you apply the System.Runtime.InteropServices.MarshalAsAttribute to the String . The constructor for MarshalAsAttribute expects a System.Runtime.InteropServices.UnmanagedType value as its parameter. This parameter indicates the target type in unmanaged space. The UnmanagedType enumeration contains values covering most basic unmanaged types. You can create your own custom marshaler if you need to marshal data in a different manner.

Table 13-2 describes the members of the UnmanagedType enumeration. Note that you can also apply the MarshalAsAttribute to individual method parameters that are being passed to unmanaged space, so some of the members in the table are not applicable to data structures.

Table 13-2. Members of the UnmanagedType Enumeration

Member

Description

AnsiBStr

An ANSI character string prefixed with a single-byte length count.

AsAny

Uses reflection to dynamically determine the source type and performs automatic marshaling of the result.

Bool

A 4-byte Boolean value; false is indicated by 0, and true is indicated by any nonzero value.

BStr

A Unicode character string prefixed with a 2-byte length count.

ByValArray

A fixed-length array that appears in a structure and whose size is specified by the SizeConst field of the MarshalAsAttribute . You can determine the type of the individual target elements by using automatic marshaling from managed space, or you can specify the type explicitly using the ArraySubType field of the MarshalAsAttribute .

ByValTStr

A fixed-length character array that appears in a structure. The CharSet member of the StructLayoutAttribute containing this field indicates the type of characters (ANSI or Unicode) in the target array. As with ByValArray , you specify the length of the target array using the SizeConst field of the MarshalAsAttribute .

Currency

A COM currency type that is intended to be used primarily to convert System.Decimal values.

CustomMarshaler

Indicates that a custom marshaler should be used. The MarshalType field of the MarshalAsAttribute specifies the fully qualified name (assembly, class, version, and so on) of the custom marshaler class.

Error

This is applied to a method parameter rather than to a field in a structure. It is attached to a 32-bit integer parameter to indicate that the parameter should be marshaled as the HRESULT of a COM method call.

FunctionPtr

A function pointer.

I1

A 1-byte signed value.

I2

A 2-byte signed value.

I4

A 4-byte signed value.

I8

An 8-byte signed value.

IDispatch

A COM IDispatch interface pointer.

Interface

A COM interface pointer. The interface must provide metadata specifying the GUID that identifies it.

IUnknown

A COM IUknown interface pointer.

LPArray

A C-style array. You specify the length by using the SizeConst field of the MarshalAsAttribute if the array is fixed length, or you indicate a parameter that holds the length of the array using the SizeParamIndex field (for method calls only ”this is analogous to using the size_is modifier with COM).

LPStr

An ANSI character string (single-byte characters, null terminated ).

LPStruct

A pointer to a C-style struct .

LPTStr

A platform-dependent character string that is based on the character type the operating system uses. For example, in Windows 98 this is an ANSI string, but in Windows XP it is a Unicode string.

LPWStr

A Unicode character string.

R4

A 4-byte floating point number.

R8

An 8-byte floating point number.

SafeArray

A COM SafeArray . This is a self-describing type that includes the number of dimensions, the bounds of each dimension, and the type of data held in the array.

Struct

A C-style struct that is used primarily with common language runtime value types.

SysInt

A platform-dependent integer that is based on the data width used by the operating system. For example, in Windows 98 this is a 4-byte integer, and in 64-bit Windows it is an 8-byte integer.

TBStr

A platform-dependent, length-prefixed string. For example, the string will comprise ANSI characters in Windows 98 and Unicode characters in Windows XP.

U1

A 1-byte unsigned integer.

U2

A 2-byte unsigned integer.

U4

A 4-byte unsigned integer.

U8

An 8-byte unsigned integer.

VariantBool

A 2-byte COM Boolean value ( false = 0, true = “1).

VBByRefStr

A Microsoft Visual Basic string that is passed by reference.

Returning to the RESTOREPOINTINFO class, the szDescription field should be a String marshaled as a ByValTStr , with the SizeConst property set to the MAX_DESC_W constant exposed by the SystemRestoreAPIWrapper class. The completed class is shown below. The characters in the string will be Unicode, and this is indicated using the CharSet member of the StructLayoutAttribute . Also, to add a bit of variation (and to show you how to use the feature), the layout of the class is specified using LayoutKind.Explicit . Each field is tagged with a System.Runtime.InteropServices.FieldOffsetAttribute that indicates the position of the start of the field from the beginning of the class.

 /**@attributeStructLayoutAttribute(LayoutKind.Explicit, CharSet=CharSet.Unicode)**/ publicclassRESTOREPOINTINFO { /**@attributeFieldOffsetAttribute(0)**/ publicUInt32dwEventType; /**@attributeFieldOffsetAttribute(4)**/ publicUInt32dwRestorePtType; /**@attributeFieldOffsetAttribute(8)**/ publiclongllSequenceNumber; /**@attributeFieldOffsetAttribute(16)**/ /**@attributeMarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst=SystemRestoreAPIWrapper.MAX_DESC_W)**/ publicStringszDescription; } 
Defining the System Restore API Methods

The final step is to define the SRSetRestorePoint and SRRemoveRestorePoint functions. You do this by declaring them with System.Runtime.InteropServices.DllImportAttribute , which specifies the name of the native DLL that implements the function. A convenient place to put these definitions is in the SystemRestoreAPIWrapper class. Both methods are implemented in Srclient.dll (located in the Windows\System32 folder). Like the FlashWindowEx function shown earlier, these methods should be declared as native static . They are also declared as public to make them accessible to the outside world.

Remember that the SRSetRestorePoint method contains a character string in its second parameter. The CharSet field of the DllImportAttribute should be set to indicate the type of characters used ”it's Unicode in our example. Furthermore, if you examine the original C++ file, SrRestorePtApi.h, you'll see that there is actually no method called SRSetRestorePoin t. Instead, there are two versions called SRSetRestorePointA (the ANSI version of the function) and SRSetRestorePointW (the Unicode version). The C++ header file uses a macro to alias the name SRSetRestorePoint to whichever of these two functions is appropriate for the current platform. The DllImportAttribute also allows you to alias methods, using the EntryPoint field. Set it to the name of real function inside the DLL.

Choosing Between ANSI and Unicode Methods Automatically

The alias used by the SRSetRestorePoint method is actually redundant ”we've included it primarily to show you how to use the Alias field. All Win32 API functions that handle strings are supplied in two versions: one that takes ANSI arguments, and one that takes Unicode arguments. The name of the ANSI version of a function must end with an A , while the name of the Unicode version of the same function must end with a W . When an unmanaged function that uses strings is invoked, the common language runtime will perform a match using an algorithm that depends on the character type used by the platform.

In the case of an ANSI platform (such as Windows 98), the common language runtime will first try to call the function using the name specified in the managed declaration. If this fails, the common language runtime will look for a function with an A suffix. If the platform is Unicode based, the common language runtime will first try the W suffix and will search using the declared name only if this fails (as you can see, the search sequence is different for ANSI and Unicode). In both cases, if the search is unsuccessful , the common language runtime will look for a mangled version of the function name (such as _SRSetRestorePoint@32 ) based on the size of the parameter list. If this fails as well, the common language runtime will throw an exception ( java.lang.UnsatisfiedLinkError ).

An alternative approach is to specify the CharSet field for DllImport ­Attribute . If this field is set to CharSet.Unicode , the common language runtime will automatically attempt to execute the W version of a function if it cannot locate a function with the original name. This saves the common language runtime from having to examine the platform. Similarly, specifying CharSet.Ansi will direct the common language runtime to use the A version of the function. (The default, CharSet.Auto , causes the common language runtime to look for one or the other version, depending on the platform.)

Using an alias is faster than making the common language runtime work out the name of the method, but there's one major drawback: If you hard-code an alias to refer to the Unicode version of a function, the call will not work on an ANSI platform. Specifying CharSet.Unicode or CharSet.Ansi can be equally restrictive . If you want the maximum portability, let the common language run ­time figure out whether to use ANSI or Unicode.

SRSetRestorePoint returns a BOOL in the C++ implementation; this is mapped to a Java language boolean by the marshaler. The two parameters are defined in C++ as pointers. The Java language does not have explicit pointers, but objects are always marshaled by reference when you pass them as parameters, and the marshaler will automatically convert an object reference into a struct pointer. However, if you want to make this marshaling explicit, you can apply a MarshalAsAttribute that specifies UnmanagedType.LPStruct as the native data type, as shown in the following code

The SRRemoveRestorePoint method is simpler. It is also located in Srclient.dll, but it does not require any aliasing. The return value from SRRemove ­RestorePoint is a DWORD in C++. This is a 4-byte unsigned quantity that corresponds to a System.UInt32 in the .NET Framework. The parameter is also a UInt32 .

 publicclassSystemRestoreAPIWrapper { /**@attributeDllImportAttribute("srclient.dll", CharSet=CharSet.Unicode, EntryPoint= "SRSetRestorePointW")**/ publicstaticnativebooleanSRSetRestorePoint(/**@attributeMarshalAsAttribute(UnmanagedType.LPStruct)**/ RESTOREPOINTINFOpRestorePtSpec, /**@attributeMarshalAsAttribute(UnmanagedType.LPStruct)**/ STATEMGRSTATUSpSMgrStatus);/** @attribute DllImportAttribute("srclient.dll") **/ 
 publicstaticnativeUInt32SRRemoveRestorePoint(intdwRPNum); } 
Invoking the System Restore API

The Installer class available in the book's sample files in the SystemRestore project is a simple test harness that exercises the System Restore API from J#. The code creates a restore point by calling SRSetRestorePoint . The RESTOREPOINTINFO parameter is populated with information about the restore point to be created, the dwEventType field is set to the BEGIN_SYSTEM_CHANGE constant to indicate that a new restore point is being created, and the dwRestorePtType field is set to APPLICATION_INSTALL to indicate the type of the restore point. (You can also create restore points before you perform other operations, such as installing a new device driver or changing desktop settings.) The szDescription field is set to a text string that indicates the purpose of the restore point. This description is displayed by the System Restore tool when the user wants to select a restore point to roll back, so it's helpful to users to make it meaningful. The STATEMGRSTATUS parameter to SRSetRestorePoint is filled in by the operating system when the method is called and returned to the application. It will contain information such as a unique sequence number used to identify the restore point internally and a status field that's filled in with a reason code if the call to SRSetRestorePoint fails. The return value from SRSetRestorePoint indicates whether the call was successful.

If the restore point is created successfully, the code will continue by installing a dummy application consisting of an EXE file, a DLL, and an INI file residing under a new folder called Cake Application. (We had to get cakes in here somewhere!) This code uses classes and methods found in the System.IO namespace of the .NET Framework Class Library and has nothing to do with the System Restore API.

Once the new application has been installed, the method SRSetRestorePoint is called again to complete the restore point and commit the changes made to your hard disk. The dwEventType field of the RESTOREPOINTINFO parameter is set to END_SYSTEM_CHANGE , and the llSequenceNumber field is set to the sequence number that was returned when the restore point was created. If all is well, SRSetRestorePoint will return true and the program will finish with the new application having been successfully installed. If SRSetRestorePoint returns false , an error must have occurred, so the application is deleted and the restore point is removed by calling the SRRemoveRestorePoint .

There is a slight anomaly in the System Restore API at this point. The SRRemoveRestorePoint function expects the sequence number of the restore point to be removed as its parameter. The type specified in the System Restore API for this parameter is DWORD ( UInt32 in J#). However, the type of the sequence number returned in the STATEMGRSTATUS class is an INT64 ( Int64 in J#). C++ will allow you to assign an INT64 to a DWORD without a murmur. But J#, having Java-like semantics, is a lot fussier and won't let you commit such a heinous crime. Instead, you must explicitly cast from a long to a UInt32 .

Viewing the Results

If you compile and run the program, you should get output similar to that shown in Figure 13-1.

Figure 13-1. The output of the SystemRestore program

Notice that the folder C:\Cake Application was created. It contains Application.exe, along with the Library subfolder that contains the files Application.dll and Application.ini, as shown in Figure 13-2.

Figure 13-2. Windows Explorer showing the Cake Application folder

You can verify that the restore point was created using the System Restore tool. From the Start menu, choose All Programs, Accessories, System Tools, and then select System Restore to launch the program. The Welcome To System Restore dialog box will appear. Click Next to display the restore points that were created on your system, as shown in Figure 13-3.

Figure 13-3. The System Restore user interface

The calendar will default to the current date, and you should see the text Cake Application Installation listed as a restore point. You can uninstall the Cake Application by selecting this restore point and clicking Next. The computer will restart in maintenance mode, roll back the application installation, and then reboot. (Afterwards, you can undo this undo operation and restore the application if you want to!)

Other P/Invoke Issues

You can put P/Invoke to many interesting uses, which this chapter cannot thoroughly cover in the space available. However, before leaving the subject let's look at a few important wrinkles , issues, and other tips.

Using Callbacks

Unmanaged code can call back into managed space using a delegate. Delegate parameters are automatically marshaled as function pointers by P/Invoke, although it doesn't hurt to be explicit and provide the MarshalAsAttribute with the argument UnmanagedType.FunctionPtr . (It's good documentation.) The Enumerator class shown below (available in the file Enumerator.jsl in the sample project EnumWindows) uses a delegate as the parameter to the native EnumWindows function found in User32.dll. This is the classic function that enumerates the top-level windows on the screen by calling a specified routine and passing the handle of each window visited in turn to that routine.

In this example, the method VisitWindow is supplied as the method to be called. The important point to grasp in this example is the use of the delegate EnumWindowsCallback . (Note the use of @delegate .) This delegate matches the signature of the method used by the EnumWindows function. (See the Platform SDK for details about EnumWindows .) You specify EnumWindowsDelegate as the type of the callback function when you declare EnumWindows . When the Enumerator program is executed, it will display the handle of every top-level window that appears on the desktop. Completed code for this example is available in the file Enumerator.jsl, in the EnumWindows project.

Calling Conventions

Before the advent of the common language runtime, one of the joys of performing cross-language method calls was the game called "Who will clean up the stack?" The rules are quite simple: The caller creates some parameters and puts them on the stack and then calls a function; the callee examines the contents of the stack and performs the requisite operations defined by the function using this information. The fun comes when the function finishes and returns to the caller. Who will clean up the stack?

In the good old days of UNIX, when programmers wrote only in C, they created interesting functions that could take variable numbers of parameters using a technique known as varargs . (Go ask your grandfather to explain.) Only the caller would know for certain what it had placed on the stack, so it was the caller's responsibility to clean up the stack when the function call completed. This is referred to as the Cdecl calling convention. Then along came a more disciplined breed of programmers who felt that varargs was a step too far and decided, for optimization reasons, to banish it and allow the callee to clean up the stack instead. This is referred to as the StdCall calling convention. To muddy the waters further, along came C++, some of whose compilers implicitly pass a reference to the current object ( this ) as the first parameter to a method, using a calling convention termed ThisCall .

As mentioned in Chapter 2, the common language runtime takes a massive leap forward by ensuring that all .NET-supported languages use the same calling convention. So normally who cleans up the stack is not an issue. However, when you call unmanaged code, it can be a very big issue indeed. If you fail to clean up the stack, or the caller and the callee both attempt to clean up the stack, the result can be some nasty exceptions (but not as nasty as things used to get before the days of the common language runtime). So, when you call an unmanaged function in a DLL, check the documentation to determine the calling convention required, and make sure you use the same. You can specify the calling convention to use with DllImportAttribute :

 /**@attributeDllImportAttribute("mylibrary.dll", CallingConvention=CallingConvention.Cdecl)**/ publicstaticnativeintMyFunction(intsomeData); 

You should use one of the values in the System.Runtime.InteropServices.CallingConvention enumeration, but don't try the FastCall option because it is not yet implemented by the common language runtime. If you're calling ordinary Windows API methods (such as those implemented in the various Windows DLLs and the Platform SDK), the safest option is to use Winapi (which is the default). Winapi uses the calling convention that's applicable to the platform ” StdCall in Win32, and Cdecl in WinCE. As mentioned, you should bear in mind that DllImportAttribute is metadata and is interpreted at runtime. If you specify Winapi , you won't need to recompile an assembly built using Windows XP if you deploy it on Windows CE.

Handling Polymorphic Parameters

C and C++ allow you to write functions whose parameters can have different meanings at different times, depending on the value of other parameters. For example, the WinHelp function in User32.dll interacts with the Windows Help system. The function is defined in the Platform SDK as follows:

 BOOLWinHelp(HWNDhWndMain, LPCTSTRlpszHelp, UINTuCommand, DWORDdwData); 

At first glance, the function appears innocuous enough. But in the documentation, you'll discover that the dwData parameter, which is loosely described as "additional data," can be a pointer to a MULTIKEYHELP structure, a pointer to a HELPWININFO structure, an integer, or a string, depending on the value supplied for the uCommand parameter! The Java language doesn't allow such functions. How, therefore, should you invoke WinHelp using P/Invoke from J#? You have two options: You can cheat unsafely or you can cheat safely.

Let's look at the unsafe cheating method first. You declare the method as shown here:

 /**@attributeDllImportAttribute("user32.dll")**/ publicstaticnativebooleanWinHelp(UInt32hWndMain, StringlpszHelp, UInt32uCommand, ObjectdwData); 

If you indicate that the fourth parameter is an Object , any object type can be marshaled. (If you want to pass an integer, you must pass it as a System.Int32 because a primitive int cannot be converted to a Object .) This is the unsafe method because not only can you pass an integer, string, MULTIKEYHELP , or HELPWININFO , you can pass anything you like, valid or not! At run time, the marshaler will examine the type of the object passed in and perform the conversion as described earlier in Table 13-1.

A safer solution is to use overloading. You define the WinHelp function four times, with a different fourth parameter each time. (You'll also need to define J# versions of the MULTIKEYHELP and HELPWININFO classes.)

 /**@attributeDllImportAttribute("user32.dll")**/ publicstaticnativebooleanWinHelp(UInt32hWndMain, StringlpszHelp, UInt32uCommand, intdwData); /**@attributeDllImportAttribute("user32.dll")**/ publicstaticnativebooleanWinHelp(UInt32hWndMain, StringlpszHelp, UInt32uCommand, StringdwData); /**@attributeDllImportAttribute("user32.dll")**/ publicstaticnativebooleanWinHelp(UInt32hWndMain, StringlpszHelp, UInt32uCommand, MULTIKEYHELPdwData); /**@attributeDllImportAttribute("user32.dll")**/ publicstaticnativebooleanWinHelp(UInt32hWndMain, StringlpszHelp, UInt32uCommand, HELPWININFOdwData); 

You have no guarantee that the developer who calls the WinHelp method will use the correct version for a given value of uCommand , but at least the J# compiler can perform some type checking and reduce the opportunity for errors.

Handling Exceptions

Unmanaged code can throw exceptions. If the unmanaged code explicitly throws an exception using structured exception handling (SEH), the common language runtime will map this to a managed System.Runtime.InteropServices.SEHException object when the unmanaged code finishes. You must be prepared to catch and handle exceptions in your J# applications whenever you invoke an unmanaged function.

Some unmanaged functions provide additional information if they detect an error, using the unmanaged methods SetLastError or SetLastErrorEx . In an unmanaged environment, you call the GetLastError function to obtain this information. In managed space, you can indicate that an unmanaged function sets the last error code by setting the SetLastError field of DllImportAttribute to true when you declare the function. If the unmanaged code sets the last error code, the common language runtime will cache the value when the function returns. The caller (in managed space) can invoke the static method GetLastWin32Error of the System.Runtime.InteropServices.Marshal class to retrieve the error code.

Security Constraints

Unmanaged code is not subject to the same verification as managed code, and it can basically do almost anything it likes. All managed code that makes use of P/Invoke, either directly or indirectly, must be granted the UnmanagedCode privilege. The UnmanagedCode privilege is one of the flags comprising the Security permission, as described in Chapter 2. By default, this privilege is granted only to assemblies loaded from the My Computer zone. This prevents untrusted code in assemblies downloaded from the Internet or even a shared network drive from calling unmanaged functions that might reformat your hard drive.

I l @ ve RuBoard


Microsoft Visual J# .NET (Core Reference)
Microsoft Visual J# .NET (Core Reference) (Pro-Developer)
ISBN: 0735615500
EAN: 2147483647
Year: 2002
Pages: 128

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