Tải bản đầy đủ - 0 (trang)
12-1. Call a Function in an Unmanaged DLL

12-1. Call a Function in an Unmanaged DLL

Tải bản đầy đủ - 0trang

CHAPTER 12 ■ UNMANAGED CODE INTEROPERABILITY



Solution

Declare a method in your C# code that you will use to access the unmanaged function. Declare this

method as both extern and static, and apply the attribute System.Runtime.InteropServices.

DllImportAttribute to specify the DLL file and the name of the unmanaged function.



How It Works

To use a native function contained in an external library, all you need to do is declare a method with the

appropriate signature—the Common Language Runtime (CLR) automatically handles the rest, including

loading the DLL into memory when the function is called and marshaling the parameters from .NET

data types to native data types. The .NET service that supports this cross-platform execution is named

PInvoke (Platform Invoke), and the process is usually seamless. Occasionally, you will need to do a little

more work, such as when you need to support in-memory structures, callbacks, or mutable strings.

PInvoke is often used to access functionality in the Win32 API, particularly Win32 features that are

not present in the set of managed classes that make up the .NET Framework. Three core libraries make

up the Win32 API:





Kernel32.dll includes operating system–specific functionality such as process

loading, context switching, and file and memory I/O.







User32.dll includes functionality for manipulating windows, menus, dialog

boxes, icons, and so on.







GDI32.dll includes graphical capabilities for drawing directly on windows, menus,

and control surfaces, as well as for printing.



As an example, consider the Win32 API functions used for writing and reading INI files, such as

GetPrivateProfileString and WritePrivateProfileString, in Kernel32.dll. The .NET Framework does

not include any classes that wrap this functionality. However, you can import these functions using the

attribute DllImportAttribute, like this:

[DllImport("kernel32.DLL", EntryPoint="WritePrivateProfileString")]

private static extern bool WritePrivateProfileString(string lpAppName,

string lpKeyName, string lpString, string lpFileName);

The arguments specified in the signature of the WritePrivateProfileString method must match the

DLL method or a runtime error will occur when you attempt to invoke it. Remember that you do not

define any method body, because the declaration refers to a method in the DLL. The EntryPoint portion

of the attribute DllImportAttribute is optional in this example. You do not need to specify the

EntryPoint when the declared function name matches the function name in the external library.



The Code

The following is an example of using some Win32 API functions to get INI file information. It declares the

unmanaged functions used and exposes public methods to call them. (Other Win32 API functions for

getting INI file information not shown in this example include those that retrieve all the sections in an

INI file.) The code first displays the current value of a key in the INI file, modifies it, retrieves the new

value, and then writes the default value.



598



www.it-ebooks.info



CHAPTER 10 ■ UNMANAGED CODE INTEROPERABILITY



using

using

using

using



System;

System.Runtime.InteropServices;

System.Text;

System.IO;



namespace Apress.VisualCSharpRecipes.Chapter12

{

class Recipe12_01

{

// Declare the unmanaged functions.

[DllImport("kernel32.dll", EntryPoint = "GetPrivateProfileString")]

private static extern int GetPrivateProfileString(string lpAppName,

string lpKeyName, string lpDefault, StringBuilder lpReturnedString,

int nSize, string lpFileName);

[DllImport("kernel32.dll", EntryPoint = "WritePrivateProfileString")]

private static extern bool WritePrivateProfileString(string lpAppName,

string lpKeyName, string lpString, string lpFileName);

static void Main(string[] args)

{

// Must use full path or Windows will try to write the INI file

// to the Windows folder, causing issues on Vista and Windows 7.

string iniFileName = Path.Combine(Directory.GetCurrentDirectory(),

"Recipe12-01.ini");

string message = "Value of LastAccess in [SampleSection] is: {0}";

// Write a new value to the INI file.

WriteIniValue("SampleSection", "LastAccess",

DateTime.Now.ToString(), iniFileName);

// Obtain the value contained in the INI file.

string val = GetIniValue("SampleSection", "LastAccess", iniFileName);

Console.WriteLine(message, val ?? "???");

// Wait to continue.

Console.WriteLine(Environment.NewLine);

Console.WriteLine("Press Enter to continue the example.");

Console.ReadLine();

// Update the INI file.

WriteIniValue("SampleSection", "LastAccess",

DateTime.Now.ToString(), iniFileName);

// Obtain the new value.

val = GetIniValue("SampleSection", "LastAccess", iniFileName);

Console.WriteLine(message, val ?? "???");



599



www.it-ebooks.info



CHAPTER 12 ■ UNMANAGED CODE INTEROPERABILITY



// Wait to continue.

Console.WriteLine(Environment.NewLine);

Console.WriteLine("Main method complete. Press Enter.");

Console.ReadLine();

}

public static string GetIniValue(string section, string key,

string filename)

{

int chars = 256;

StringBuilder buffer = new StringBuilder(chars);

string sDefault = "";

if (GetPrivateProfileString(section, key, sDefault,

buffer, chars, filename) != 0)

{

return buffer.ToString();

}

else

{

// Look at the last Win32 error.

int err = Marshal.GetLastWin32Error();

return null;

}

}

public static bool WriteIniValue(string section, string key,

string value, string filename)

{

return WritePrivateProfileString(section, key, value, filename);

}

}

}



■ Note The GetPrivateProfileString method is declared with one StringBuilder parameter

(lpReturnedString). This is because this string must be mutable; when the call completes, it will contain the

returned INI file information. Whenever you need a mutable string, you must substitute StringBuilder in place of

the String class. Often, you will need to create the StringBuilder object with a character buffer of a set size,

and then pass the size of the buffer to the function as another parameter. You can specify the number of

characters in the StringBuilder constructor. See recipe 2-1 for more information about using the

StringBuilder class.



600



www.it-ebooks.info



CHAPTER 10 ■ UNMANAGED CODE INTEROPERABILITY



12-2. Get the Handle for a Control, Window, or File

Problem

You need to call an unmanaged function that requires the handle for a control, a window, or a file.



Solution

Many classes, including all Control-derived classes and the FileStream class, return the handle of the

unmanaged Windows object they are wrapping as an IntPtr through a property named Handle. Other

classes also provide similar information; for example, the System.Diagnostics.Process class provides a

Process.MainWindowHandle property in addition to the Handle property.



How It Works

The .NET Framework does not hide underlying details such as the operating system handles used for

controls and windows. Although you usually will not use this information, you can retrieve it if you need

to call an unmanaged function that requires it. Many Microsoft Windows API functions, for example,

require control or window handles.



The Code

As an example, consider the Windows-based application shown in Figure 12-1. It consists of a single

window that always stays on top of all other windows regardless of focus. (This behavior is enforced by

setting the Form.TopMost property to true.) The form also includes a timer that periodically calls the

unmanaged GetForegroundWindow and GetWindowText WinAPI functions to determine which window is

currently active.

One additional detail in this example is that the code also uses the Form.Handle property to get the

handle of the main application form. It then compares with the handle of the active form to test if the

current application has focus. The following is the complete code for this form:

using

using

using

using



System;

System.Windows.Forms;

System.Runtime.InteropServices;

System.Text;



namespace Apress.VisualCSharpRecipes.Chapter12

{

public partial class ActiveWindowInfo : Form

{

public ActiveWindowInfo()

{

InitializeComponent();

}



601



www.it-ebooks.info



CHAPTER 12 ■ UNMANAGED CODE INTEROPERABILITY



// Declare external functions.

[DllImport("user32.dll")]

private static extern IntPtr GetForegroundWindow();

[DllImport("user32.dll")]

private static extern int GetWindowText(IntPtr hWnd,

StringBuilder text, int count);

private void tmrRefresh_Tick(object sender, EventArgs e)

{

int chars = 256;

StringBuilder buff = new StringBuilder(chars);

// Obtain the handle of the active window.

IntPtr handle = GetForegroundWindow();

// Update the controls.

if (GetWindowText(handle, buff, chars) > 0)

{

lblCaption.Text = buff.ToString();

lblHandle.Text = handle.ToString();

if (handle == this.Handle)

{

lblCurrent.Text = "True";

}

else

{

lblCurrent.Text = "False";

}

}

}

}

}



■ Caution The Windows Forms infrastructure manages window handles for forms and controls transparently.

Changing some of their properties can force the CLR to create a new native window behind the scenes, and the

new window gets assigned a different handle. For that reason, you should always retrieve the handle before you

use it (rather than storing it in a member variable for a long period of time).



602



www.it-ebooks.info



Tài liệu bạn tìm kiếm đã sẵn sàng tải về

12-1. Call a Function in an Unmanaged DLL

Tải bản đầy đủ ngay(0 tr)

×