Во-первых, мы рассмотрим объявление на С# простой функции из DLL. Мы используем пример, который для .NET
PInvoke
быстро становится каноническим, — Win32 "MessageBox". Затем мы перейдем к более сложным вопросам, связанным с преобразованием параметров.
Как вы узнали из главы 8, атрибуты служат для предоставления информации периода разработки о типе С#. В период же выполнения эту информацию позволяет получить отражение. В С# атрибут
Dlllmport
позволяет указать компилятору функцию DLL, которую будет вызывать приложение:
[ОШтроЛ(имя_с/1[)]
модификатор ^доступа
static extern
возвр_знанение функциями (парам!, парам2,...);
Как видите, чтобы импортировать функцию из DLL, нужно лишь прикрепить атрибут
Dlllmport
(передавая его конструктору имя DLL) к функции DLL, которую вы хотите вызвать. Заметьте: для определяемой вами функции нужно применять модификаторы
static
и
extern.
Вот пример
MessageBox
— он показывает, как легко использовать
PInvoke:
using System; using System.Runtime.InteropServices;
class PInvokelApp {
[Dlllmport("user32.dll")] static extern int MessageBoxA(int hWnd,
string msg, string caption, int type);
public static void MainQ
{
MessageBoxA(0,
"Hello, World!",
"This is called from a C# app!", 0); } }
В результате запуска этого приложения, как и ожидалось, будет показано окно с сообщением "Hello, World!".
Оператор
using
указывает пространство имен
System.Runtime.InteropServices,
которое определяет атрибут
Dlllmport.
Затем я определил метод
MessageBoxA,
вызываемый в
Main.
Но если вы хотите назвать свой внутренний метод на С# не так, как называется функция DLL? Это позволяет сделать один из именованных параметров атрибута
Dlllmport.
В приведенной ниже программе происходит следующее. Я сообщаю компилятору, что хочу назвать свой метод
MessageBoxA.
Поскольку в атрибуте
Dlllmport
я не указал имя функции DLL, компилятор предполагает, что оба имени одинаковы.
[Dlllmport("user32.dll")]
static extern int MessageBoxA(int hWnd,
string msg, string caption, int type);
Чтобы понять, как изменить этот стандартный алгоритм, рассмотрим пример. На этот раз используется внутреннее имя
MsgBox,
но все равно вызывается функция DLL
MessageBoxA.
using System;
using System.Runtime.InteropServices;
class PInvoke2App {
[Dlllmport("user32.dll", EntryPoint="MessageBoxA")] static extern int MsgBox(int hWnd,
string msg,
string caption,
int type);
public static void Main() {
MsgBox(0,
"Hello, World!",
"This is called from a C# app!", 0); > }
Как видите, чтобы получить возможность называть внутренний эквивалент внешней функции DLL, как мне вздумается, я должен лишь задать именованный параметр
Entry Point
атрибута
Dlllmport.
Последнее, что мы рассмотрим, прежде чем перейти к преобразованию параметров, — параметр
CharSet.
Он позволяет задавать набор символов, используемый файлом DLL. Обычно при написании С++-при-ложений вы не задаете явно использование
MessageBoxA
или
Message-BoxW —
благодаря
pragma,
компилятор уже знает, какой набор символов вы применяете — ANSI или Unicode. Поэтому при вызове
MessageBox
на C++ компилятор определяет, какой набор символов использовать. Сходным образом на С# вы можете задать целевой набор символов для атрибута
Dlllmport,
который будет указывать, какую версию
MessageBox
вызвать. В следующем примере все равно будет вызвана функция
MessageBoxA,
так как
Dlllmport
через его именованный параметр
CharSet
передается соответствующее значение.
using System;
using System.Runtime.InteropServices;
class PInvokeSApp {
// При задании CharSet.Ansi будет вызвана MessageBoxA. // При задании CharSet.Unicode будет вызвана HessageBoxW. [Dlllmport("user32.dll", CharSet=CharSet.Ansi)] static extern int MessageBox(int hWnd,
string msg, string caption, int type);
public static void MainQ {
MessageBox(0,
"Hello, World!",
"This is called from a C# app!", 0); } }
Преимущество параметра
CharSet
в том, что я могу установить в приложении переменную, которая будет контролировать вызов той или иной версии функций (ANSI или Unicode). Мне не придется менять весь код при переходе от одной версии к другой.