With any new technology, it's best to start out small and work your way up. You're now going to expand on the project you've been working on this hour. As mentioned earlier, the first step is to use the DLLImport attribute. When applied to a function definition, the DLLImport attribute specifies which dynamic link library (DLL) the function you are referencing is contained within. In addition, there are several optional parameters for the attribute which control how that function is defined within your source code.
The first function you are going to use is the WIN32 API function MessageBox. Before you can create its signature, however, you need to know which DLL that function is contained within. There are two ways you can do this. The first way is to use the Dependency Walker tool, depends.exe. Using this tool, however, is cumbersome and slow because it involves manually opening a system DLL such as user32.dll or kernel32.dll and looking through the large list of exports to see whether the function you need is there. If it isn't, you must try a different DLL. Dependency Walker is a great tool, just not for this procedure. Figure 22.3 shows Dependency Walker with the MessageBox function highlighted.
The easiest way to find which DLL contains the function you need is to use the MSDN documentation itself. Click Help, Index in the main menu. In the Look For field within the Index dialog, enter MessageBox function and click that phrase within the index result list. This will open the help documentation associated with the WIN32 API function MessageBox. Scroll down to the bottom of the document and you'll see a line that says the function is implemented in user32.lib. Because there is a user32.dll file, you can infer that user32.lib is linked into that library and therefore the MessageBox function is contained in user32.dll.
The first parameter for the DllImport attribute is the name of the DLL that contains the function you are interested in. Following that are a number of optional parameter attributes you can apply. These optional attribute parameters include the following:
EntryPoint. Specifies which entry point function to call. This allows you to specify a function by ordinal rather than function name.
CharSet. This controls how strings are marshaled to the DLL function. Some functions are designed to accept either Unicode or ANSI strings and will mangle the name to show that distinction. By using the CharSet attribute, you can use the original function name.
ExactSpelling. This is used to change the behavior of the CharSet parameter. ExactSpelling indicates that the name of the entry point is the same name as the method definition that follows the DllImport attribute. If this parameter is true and the CharSet parameter is to Ansi, then a letter A will be appended to the function name. Likewise, if the CharSet parameter is set to Unicode, the letter W will be appended. The default value for this parameter is false.
CallingConvention. This is used to coordinate P/Invoke and the DLL function that is being called by specifying the calling convention of the function. The default is WINAPI (__stdcall) and will rarely need to change.
PreserveSig. This is used when invoking functions that return an HRESULT. If PreserveSig is true, a regular 32-bit integer will be returned. If it is false, however, any HRESULTs that are failure codes will throw an exception.
SetLastError. Use the SetLastError parameter if you plan on using the function Marshal::GetLastWin32Error. This is equivalent to calling the WIN32 API function GetLastError. The default for this parameter is false within Visual C++ .NET.
After using the DLLImport attribute, you must then create a function signature for the function you wish to call. However, because you are working within managed code, the parameters to the function signature should be types that can be marshaled successfully from the managed data type to the unmanaged data type. Table 22.1 lists unmanaged data types and their equivalent managed data types.
|Unmanaged Windows Type||Unmanaged C Language Type||Managed Class Name|
|LPSTR||char*||System.String or System.StringBuilder|
|LPCSTR||Const char*||System.String or System.StringBuilder|
|LPWSTR||wchar_t*||System.String or System.StringBuilder|
|LPCWSTR||Const wchar_t*||System.String or System.StringBuilder|
The process of creating a function signature is similar to declaring any other function. However, because the function is not implemented in your assembly, the extern keyword must precede the actual definition. Before you add the code to define the MessageBox function contained within user32.dll, import the System::Runtime::InteropServices namespace. Next, define the MessageBox function by first creating the DllImport attribute and then creating the function definition with the equivalent managed data types. This is done as follows:
[DllImport("user32.dll", ExactSpelling=true, CharSet=CharSet::Ansi)] extern int MessageBoxA(void* hWnd, String* pText, String* pCaption, unsigned int uType);
Within the SayHello function contained in the CSystemTime class, call the MessageBoxA function you just defined:
MessageBoxA( 0, "Hello World", "CSystemTime", 0 );
Compile and run your application. If everything has worked correctly, you will see a message box similar to the one shown in Figure 22.4.