Программный код, выполняющийся под управлением CLR, называется управляемым кодом.
Программный код, выполняющийся вне среды выполнения CLR, называется неуправляемым кодом.
Примеры неуправляемого программного кода:
.NET появилась не на пустом месте. Вновь разрабатываемый управляемый код вынужден взаимодействовать с существующим неуправляемым программным кодом. Поэтому на платформе .NET предусмотрены различные сценарии установления взаимодействия между управляемым и неуправляемым кодами. Microsoft .NET Framework обеспечивает взаимодействие с компонентами COM, службами COM+, внешними библиотеками типов и многими службами операционной системы.
CLR скрывает имеющиеся в управляемой и неуправляемой моделях различия. Проблемы, связанные с типами данных, механизмами обработки ошибок и т. д. в управляемой и неуправляемой моделях, решаются CLR "незаметно" как для вызывающей стороны (клиента), так и для вызываемой стороны (сервера). Таким образом, организация взаимодействия между управляемым и неуправляемым кодом выглядит проще, чем могло быть...
Реализованные в .NET языки программирования позволяют создавать управляемый код, который может взаимодействовать с неуправляемыми библиотеками Win32 и компонентами на основе модели компонентных объектов Microsoft (COM).
Язык программирования C++ .NET является единственным, который позволяет создавать как управляемый, так и неуправляемый код. Это дает возможность не только использовать неуправляемый библиотечный код, но и смешивать управляемый и неуправляемый коды в одном приложении.
В приводимых ниже примерах кроме языка программирования C# будет использоваться язык C++.
Первый шаг тривиален. Создается C++ Win32 Console Project, то есть Неуправляемое Консольное Приложение на C++. Последняя версия Visual Studio .NET 2005 позволяет в широких пределах смешивать управляемый и неуправляемый коды.
// Транслятору задали опцию /clr.
// Эта опция преобразует все объявляемые в программе типы.
// От имени объекта элементарного типа (например, int) можно
// вызвать методы базового класса object.
// Но только вызываются они лишь из фрагментов управляемого
кода.
// Иначе – сообщение транслятора:
// ... managed type or function cannot be used in an unmanaged function
#include "stdafx.h"
#include <iostream>
using namespace std;
#using <mscorlib.dll> // Ядро CLR
using namespace System;
class uClass; // Предварительное неполное объявление класса.
class mClass; // Предварительное неполное объявление класса.
#pragma managed
class mClass
{
public:
uClass *ucP;
mClass *mcP;
int x;
mClass()
{
// Только в неуправляемом коде!
// cout << "Ha-Ha-Ha";
Console::WriteLine("mClass");
// Легко посмотрели значение непроинициализированной переменной.
Console::WriteLine(x.ToString());
}
~mClass()
{
Console::WriteLine("~mClass");
}
void mcF0()
{
Console::WriteLine("mcF0()");
}
mClass* mcGenerator();
};
#pragma unmanaged
class uClass
{
public:
uClass *ucP;
mClass *mcP;
int x;
uClass()
{
// Только в управляемом коде!
//Console::WriteLine("Ha-Ha-Ha");
printf("uClass\n");
}
~uClass()
{
printf("~uClass\n");
}
void ucF0()
{
cout << "ucF0()\n";
}
uClass* ucGenerator();
};
// Судя по всему, функция Управляемого
// класса может быть НеУправляемой!
mClass* mClass::mcGenerator()
{
//x.ToString();
//Console::WriteLine("Ha-Ha-Ha");
cout << "Ha-Ha-Ha from unmanaged function of managed class!" << endl;
ucP = new uClass();
ucP->ucF0();
delete ucP;
return new mClass();
}
#pragma managed
// А сделать Управляемой функцию НеУправляемого класса невозможно.
// Прагма managed для функции - члена неуправляемого класса игнорируется.
uClass* uClass::ucGenerator()
{
cout << "Ha-Ha-Ha from function of unmanaged class!" << endl;
//Console::WriteLine("Ha-Ha-Ha");
//x.ToString();
mcP = new mClass();
mcP->mcF0();
delete mcP;
return new uClass();
}
#pragma unmanaged
int _tmain(int argc, _TCHAR* argv[])
{
void *xPoint;
int x = 125;
// Только не смешивать!
//Console::WriteLine("Ha-Ha-Ha");
mClass *mc = new mClass();
mc->mcF0();
xPoint = mc->mcGenerator();
delete (mClass*)xPoint;
delete mc;
uClass *uc = new uClass();
uc->ucF0();
xPoint = uc->ucGenerator();
delete (uClass*)xPoint;
delete uc;
return 0;
}
Создание кода управляемой библиотеки тривиально. Для этого в Visual Studio предусмотрены специальные опции. Новый проект создается как библиотека классов (Class Library). Сборка автоматически получает расширение .dll.
C# основывается на парадигме объектно-ориентированного программирования, поэтому библиотека классов представляет собой все то же объявление класса.
Методы – члены класса составляют основу функциональности библиотеки. Наличие пары явно определяемых конструкторов – вынужденная мера. Это требование со стороны модуля на C++, использующего библиотеку. Там невозможно создать объект без явным образом объявленных конструкторов умолчания и копирования:
using System;
namespace CSLib00
{
public class Class1
{
// Явное объявление конструктора без параметров.
public Class1()
{
}
// Явное объявление конструктора копирования.
// Конструктор с одним параметром – ссылкой на
// объект - представитель собственного класса.
// Именно такие конструкторы называются конструкторами
// копирования.
public Class1(Class1 cKey)
{
}
// Реализация функциональности. Метод - член класса Class1 Summ.
public int Summ(int key1, int key2)
{
Console.WriteLine("this is Summ from CSLib00.dll");
return (key1 + key2);
}
}
}
Использование управляемой библиотеки в управляемом коде тривиально. Главная проблема, которая возникает при разработке приложения, использующего код управляемой библиотеки, – добавить ссылку (Add Reference) на библиотечную сборку. Для этого нужно указать месторасположение сборки в соответствующем диалоговом окне.
После этого Visual Studio копирует сборку в директорию, в которой располагается разрабатываемый код. При согласовании пространств имен (оператор using или использование полного имени с
using System;
namespace CSClient
{
class Program
{
static void Main(string[] args)
{
// Здесь используется точечная нотация.
// Явное указание принадлежности имени класса
// пространству имен CSLib00.
CSLib00.Class1 c1 = new CSLib00.Class1();
// Создали объект, затем вызываем метод!
Console.WriteLine("the res = {0}", c1.Summ(1, 2));
}
}
}
Особенности разработки неуправляемого программного кода, использующего управляемую библиотеку, состоят в следующем:
#include "stdafx.h"
#using <mscorlib.dll>
using namespace System;
using namespace CSLib00;
#pragma managed
void managedLibStarter()
{
Class1 cl1 = gcnew Class1();
Console::WriteLine("{0}",cl1.Summ(1,2));
}
#pragma unmanaged
int _tmain(int argc, _TCHAR* argv[])
{
printf("QWERTY\n");
managedLibStarter();
return 0;
}
Сначала определение.
Платформный вызов — это служба, которая позволяет управляемому программному коду вызывать неуправляемые функции, реализованные в библиотеках динамической компоновки (DLL), например, функции библиотек Win32 API.
Служба платформного вызова обеспечивает поиск и вызов экспортируемой функции и в случае необходимости обеспечивает маршалинг ее параметров (передачу параметров через границы процесса).
Платформный вызов позволяет управлять значительной частью операционной системы посредством вызова функций Win32 API и других библиотек DLL. В дополнение к Win32 API, через платформный вызов доступны многие другие интерфейсы API и DLL.
Вызов неуправляемых функций средствами платформного вызова предполагает следующие действия:
При вызове неуправляемой функции платформный вызов выполняет следующую последовательность действий:
В лучших традициях .NET определение местонахождения и загрузка DLL, а также определение местоположения адреса функции в памяти, выполняется только при первом вызове функции.
Идентификация функции DLL предполагает:
Например, идентификация функции MessageBox включает имя ( MessageBox ) и имя файла (User32.dll, User32, или user32). Программный интерфейс приложений Microsoft Windows (Win32 API) может содержать несколько версий функции. Например, версии функций, которые работают с символами и строками: для 1-байтовых символов – ANSI и для 2-байтовых символов – Unicode.
Так MessageBoxA является точкой входа для функции MessageBox версии ANSI, а MessageBoxW — точкой входа для аналогичной функции версии Unicode.
Имена функций, располагаемых в определенной библиотеке DLL, например, user32.dll, можно получить с помощью ряда инструментов в режиме командной строки. Например, для получения имен функций можно использовать вызовы
dumpbin –exports user32.dll
или
link –dump –exports user32.dll.
Эта деятельность называется упаковкой неуправляемой функции в обертку
На самом деле неуправляемые функции можно и не обертывать. Но это удобно, поскольку процесс определения функций DLL может быть трудоемким и представлять собой источник ошибок.
Для
После упаковки функции в обертку ее методы можно вызывать точно так же, как и методы любой другой статической функции. Платформный вызов обеспечивает передачу управления библиотечной функции автоматически.
Для обращения к неуправляемой функции DLL из управляемого программного кода требуется знать имя функции и имя библиотеки DLL, которая ее экспортирует.
Располагая этой информацией, можно начинать создавать управляемое определение для неуправляемой функции, реализованной в DLL.
При этом с помощью атрибутов можно управлять порядком создания, платформным вызовом функции и маршалингом данных в функцию и из нее (передачу значений параметров и получение возвращаемого значения):
using System.Runtime.InteropServices;
[DllImport("user32.dll")]
public static extern int MessageBox(int hWnd,
String text,
String caption,
uint type);
Поведение управляемого программного кода при его выполнении задается значениями полей атрибутов. Служба платформного вызова работает в соответствии с набором стандартных значений различных полей атрибутов, которые хранятся как метаданные в сборке.
В следующей таблице представлен полный набор полей атрибутов, имеющих отношение к платформному вызову. Для каждого поля в таблице имеется стандартное значение и ссылка на сведения относительно использования этих полей для определения неуправляемых функций DLL.
Управление службой платформного вызова сводится к применению полей атрибутов. Знание правил применения полей атрибутов – основа управления службой платформного вызова!
| Поле | Описание |
|---|---|
BestFitMapping |
Отключает наилучшее соответствие |
CallingConvention |
Задает соглашение о вызовах, которое должно использоваться при передаче аргументов методов. По умолчанию используется WinAPI, что соответствует __ stdcall для 32-разрядных платформ на основе процессора Intel |
CharSet |
Управляет передачей имен и задает способ маршалинга строковых аргументов в функцию. Стандартное значение — CharSet.Ansi |
EntryPoint |
Задает точку входа DLL для вызова |
ExactSpelling |
Указывает, должна ли быть изменена точка входа в соответствии с символьным набором. Стандартное значение варьируется в зависимости от языка программирования |
PreserveSig |
Указывает, должна ли управляемая подпись метода быть преобразована в неуправляемую подпись, которая возвращает значение HRESULT и для возвращаемого значения имеет дополнительный аргумент [out, retval].По умолчанию используется значение |
SetLastError |
Позволяет вызывающему объекту для определения факта ошибки при выполнении метода использовать API-функцию . В Visual Basic по умолчанию используется значение true; в C# и C++ — значение false |
ThrowOnUnmappableChar |
Управляет возникновением исключения при появлении несопоставимого символа Unicode, который преобразуется в символ ANSI " ?" |
Точка входа определяет расположение функции в DLL. В управляемом проекте исходное имя или порядковый номер точки входа функции назначения определяет эту функцию в границах взаимодействия. Можно также сопоставить точку входа с другим именем, фактически переименовывая функцию.
Для переименования функции DLL возможны следующие причины:
Для задания функции DLL по имени или порядковому номеру можно использовать поле DllImportAttribute.EntryPoint. Если имя функции в определении метода совпадает с именем точки входа в DLL, явно задавать функцию с помощью поля EntryPoint не требуется. В противном случае, чтобы указать имя или порядковый номер, следует использовать одну из следующих форм атрибута:
[DllImport("dllname", EntryPoint="Functionname")]
[DllImport("dllname", EntryPoint="#123")]
При этом порядковому номеру должен предшествовать знак ' # '.
Ниже приводится пример переименования функции. Имя функции MessageBoxA заменяется на MsgBox с помощью поля EntryPoint. Неуправляемая функция MessageBoxA, которая является точкой входа, в управляемом коде представляется под именем MsgBox:
using System.Runtime.InteropServices;
public class Win32
{
[DllImport("user32.dll", EntryPoint="MessageBoxA")]
public static extern int MsgBox(int hWnd,
String text,
String caption,
uint type);
}
Поле DllImportAttribute.CharSet играет двойную роль:
Некоторые API экспортируют две версии функций, которые принимают строковые аргументы: узкую (ANSI) и широкую (Unicode). Например, Win32 API для функции MessageBox содержит следующие имена точек входа:
MessageBoxA — обеспечивает форматирование с использованием 1-байтовых символов ANSI, на что указывает буква " A ", добавляемая в конец имени точки входа. При вызовах MessageBoxA маршалинг строк всегда выполняется в формате ANSI, который обычно применяется на платформах Windows 98 и Windows 95;MessageBoxW — обеспечивает форматирование с использованием 2-байтовых символов Unicode, на что указывает буква " W ", добавляемая в конец имени точки входа. При вызовах MessageBoxW маршалинг строк всегда выполняется в формате Unicode, который обычно применяется на платформах Windows NT, Windows 2000 и Windows XP.Поле CharSet может принимать следующие значения:
CharSet.Ansi (стандартное значение).При этом:
DllImportAttribute.ExactSpelling оказывается установленным в true, как это принято по умолчанию в Visual Basic .NET, платформный вызов ищет только указанное имя. Например, если указано MessageBox, платформный вызов ищет именно MessageBox. Если он не может найти точное посимвольное совпадение, возникает исключение при выполнении вызова функции.Когда значение поля ExactSpelling установлено в false, как это принято по умолчанию в C# и управляемых расширениях для C++, платформный вызов сначала ищет точный псевдоним ( MessageBox ), а затем добавленное имя ( MessageBoxA ), если точный псевдоним не найден.
Следует учитывать, что механизм совпадения имен в формате ANSI отличается от механизма совпадения имен в формате Unicode.
CharSet.Unicode.При этом платформный вызов обеспечивает маршалинг строк путем копирования строки из их управляемого формата в формат Unicode.
Если же при этом значение поля ExactSpelling оказывается равным true, действует принцип совпадения имен: платформный вызов ищет только указанное имя. Например, если указано MessageBox, платформный вызов ищет именно MessageBox. Если он не может найти точное посимвольное совпадение, возникает сбой.
А если значение поля ExactSpelling устанавливается в false, как это принято по умолчанию в C#, платформный вызов сначала ищет добавленное имя ( MessageBoxW ), а затем – точный псевдоним ( MessageBox ), если добавленное имя не найдено.
CharSet.Auto.При этом платформный вызов выбирает между форматами ANSI и Unicode во время выполнения в соответствии с платформой назначения.
Поле DllImportAttribute.CharSet определяет набор символов как базовый набор ANSI или Unicode. Набор символов определяет режим выполнения маршалинга строковых аргументов. Для указания набора символов применяется один из следующих вариантов атрибута:
[DllImport("dllname", CharSet=CharSet.Ansi)]
[DllImport("dllname", CharSet=CharSet.Unicode)]
[DllImport("dllname", CharSet=CharSet.Auto)]
В следующем примере показаны три управляемых определения функции MessageBox с атрибутами, задающими наборы символов. В первом определении, в котором значение поля CharSet не задано, по умолчанию принимается набор символов ANSI:
[DllImport("user32.dll")]
public static extern int MessageBoxA(int hWnd, String text,
String caption, uint type);
[DllImport("user32.dll", CharSet=CharSet.Unicode)]
public static extern int MessageBoxW(int hWnd, String text,
String caption, uint type);
[DllImport("user32.dll", CharSet=CharSet.Auto)]
public static extern int MessageBox(int hWnd, String text,
String caption, uint type);
Под ПЛАТФОРМОЙ НАЗНАЧЕНИЯ понимается платформа, которой предназначена закодированная в атрибутах информация. Естественно, это .NET.
В других платформах службы платформного вызова нет. Никакая другая платформа просто не сумеет прочитать и понять значение полей атрибутов.
Ниже демонстрируется определение и вызов функции MessageBox из библиотеки User32.dll. В качестве аргумента передается простая строка. Значение для атрибута поля DllImportAttribute.CharSet установлено в Auto. Это позволяет платформе назначения самостоятельно определять размер символов и выбирать маршалинг строк:
using System.Runtime.InteropServices;
public class Win32 {
[DllImport("user32.dll", CharSet=CharSet.Auto)]
public static extern int MessageBox(int hWnd, String text,
String caption, uint type);
}
public class HelloWorld {
public static void Main() {
Win32.MessageBox(0, "Hello World", "Platform Invoke Sample", 0);
}
}
Информация о функции Beep в хелпах по C# отсутствует. Такой функции в .NET НЕТ. Однако способы заставить C#-приложение "пропищать" все же существуют (передача функции Console.Writeline параметра с соответствующей escape-последовательностью не в счет).
Итак, функция
Beep
обеспечивает генерацию simple
BOOL Beep( DWORD dwFreq, DWORD dwDuration );
Параметры
dwFreq
Частотная характеристика звука в герцах. Диапазон значений этого параметра ограничен следующими величинами 37–32,767 ( 0x25– 0x7FFF ).
В Windows Me/98/95 функция Beep этот параметр игнорирует.
dwDuration
Продолжительность звучания в миллисекундах.
В Windows Me/98/95 функция Beep этот параметр игнорирует.
Возвращаемое значение
Здесь речь идет о неуправляемой функции, судя по всему, реализованной в C++. Так вот, при успешном выполнении функция возвращает ненулевое значение. В C++ такие значения соответствуют значению ИСТИНА.
В противном случае возвращается нулевое значение, то есть ЛОЖЬ:
using System;
using System.Threading;
using System.Runtime.InteropServices;
namespace BeepByWin32API
{
// Import a Beep() API function.
class Class1
{
// Таким образом получаем возможность использования импортируемых
// функций в приложении .NET.
[DllImport("kernel32.dll")]
private static extern bool Beep(int frec, int dur);
// Импортируется функция PlaySound().
[DllImport("winmm.dll")]
public static extern bool PlaySound(string pszSound,
int hmod,
int fdwSound);
// Константы, необходимые для использования PlaySound...
public const int SND_FILENAME = 0x00020000;
// За конкретное значение параметра я не ручаюсь. Подобрал сам.
// Играет - и ладно...
public const int SND_ASYNC = 0x0010;//0x0001;
// The main entry Point for the application.
static void Main(string[] args)
{
int i;
// Извлекаем звук посредством escape-последовательности.
Console.WriteLine("Пропищим..." + "\a\a\a\a\a\a\a\a\a\a");
Console.WriteLine("И еще...");
for (i = 0; i < 10; i++) {Console.Write("\a"); Thread.Sleep(1000);}
Console.WriteLine("Нажми Any Key для продолжения опытов...");
Console.ReadLine();
// Извлекаем звук посредством обращения к функции API Beep().
// Frequency of the sound, in hertz.
// This parameter must be in the range 37 through 32,767 (0x25 through 0x7FFF).
// Duration of the sound, in milliseconds.
Beep(800,200);
Beep(500,1000);
Beep(100,500);
Beep(1000,2000);
Beep(0x25,2000);
Beep(0x7FFF,2000);
Console.WriteLine("Нажми Any Key для продолжения опытов...");
Console.ReadLine();
for (i = 0; i < 10; i++)
{
PlaySound("Trumpet1.wav", 0, SND_FILENAME|SND_ASYNC);
}
}
}
}
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.