• Привет, Гость!
  • Войти
  • Регистрация
  • Записи
  • Форумы
  • Люди
  • Файлы
  • Работа
  • Технологии
  • Все
  • Новости
  • События
  • Статьи
  • Блоги

Экспорт функции из .Net dll или пишем managed функцию для rundll32

Экспорт функции из .Net dll или пишем managed функцию для rundll32

RaveNoX
26.02.2010 23:34

Суть вопроса

В своей работе, я несколько раз сталкивался с необходимостью экспорта функции из managed dll так, как это делалось в C++. Как правило, такая необходимость возникает при написании плагинов к unmanaged программам.

Поиск информации по данной тематике, как правило приводит к 2 вариантам:
  • Из managed dll функции экспортировать нельзя.
  • Можно, если создать обёртку в виде unmanaged dll и из неё вызывать managed по средством COM.
Первый вариант, понятное дело, не устраивает. Второй же, требует знаний по написанию unmanaged dll и работы их них с COM, что несколько повышает сложность решения.

После более детального поиска информации по данному направлению, оказалось, что есть ещё и третий вариант. Данный вариант позволят делать именно то, что требуется. Именно его я и хотел бы описать.

Что реализуем

Пример будет реализован в виде функции managed dll которую мы можем вызвать при помощи rundll32.exe. Функция должна будет выполнить простое действие - вывести MessageBox с текстом "Hello <аргумент переданный rundll32>".

Rundll32 

Функция, которая вызывается rundll32.exe имеет чётко оговоренный синтакс, который описана в MSDN, выдержка:

VOID CALLBACK InstallHinfSection(
  __in  HWND hwnd,
  __in  HINSTANCE ModuleHandle,
  __in  PCTSTR CmdLineBuffer,
  __in  INT nCmdShow
);
В данном примере нас интересует только параметр CmdLineBuffer, в который будет попадать аргумент, переданный при вызове rundll32.

Managed Dll

Итак, на основе данной информации реализуем следующий managed класс:
using System;
using System.Windows.Forms;

namespace HelloLib
{
    public class HelloClass
    {
        public static void Hello(IntPtr hwnd, IntPtr moduleHandle, string cmdLineBuffer, int nCmdShow)
        {
            MessageBox.Show(cmdLineBuffer);
        }
    }
}

Компилируем.

Немного магии

Наконец, мы добрались до того, ради чего собственно и писалась эта статья.
Для дальнейших манипуляций нам потребуются две утилиты: ilasm (входит в поставку .Net Framework) и ildasm (входит в поставку PlatformSDK), версии должны подходить к .Net 2.0 и выше. Самый удобный вариант их использования - Visual Studio Command Promt.

Делай раз

Дезассемблируем нашу Dll, для чего переходим в директорию с ней и выполняем следующую комманду: ildasm /OUT=HelloLib.il HelloLib.dll На выходе получаем 2 файла: HelloLib.il и HelloLib.res, нас интересует первый.
Открываем его в текстовом редакторе и видим ilasm листинг нашей dll.

Делай два

Ищем в листинге следующий кусок:
// =============== CLASS MEMBERS DECLARATION ===================

.class public auto ansi beforefieldinit HelloLib.HelloClass
       extends [mscorlib]System.Object
{
  .method public hidebysig static void  Hello(native int hwnd,
                                              native int moduleHandle,
                                              string cmdLineBuffer,
                                              int32 nCmdShow) cil managed
  {
    // Code size       8 (0x8)
    .maxstack  8
    IL_0000:  ldarg.2
    IL_0001:  call       valuetype [System.Windows.Forms]System.Windows.Forms.DialogResult [System.Windows.Forms]System.Windows.Forms.MessageBox::Show(string)
    IL_0006:  pop
    IL_0007:  ret
  } // end of method HelloClass::Hello

  .method public hidebysig specialname rtspecialname 
          instance void  .ctor() cil managed
  {
    // Code size       7 (0x7)
    .maxstack  8
    IL_0000:  ldarg.0
    IL_0001:  call       instance void [mscorlib]System.Object::.ctor()
    IL_0006:  ret
  } // end of method HelloClass::.ctor

} // end of class HelloLib.HelloClass


// =============================================================

Это и есть наш класс, нас интересует код нашего метода:
.method public hidebysig static void  Hello(native int hwnd,
                                              native int moduleHandle,
                                              string cmdLineBuffer,
                                              int32 nCmdShow) cil managed
  {
    // Code size       8 (0x8)
    .maxstack  8
    IL_0000:  ldarg.2
    IL_0001:  call       valuetype [System.Windows.Forms]System.Windows.Forms.DialogResult [System.Windows.Forms]System.Windows.Forms.MessageBox::Show(string)
    IL_0006:  pop
    IL_0007:  ret
  } // end of method HelloClass::Hello

Его нужно изменить следующим образом:
.method public hidebysig static void  Hello(native int hwnd,
                                              native int moduleHandle,
                                              string cmdLineBuffer,
                                              int32 nCmdShow) cil managed
  {
	.export[1] // <<-- Добавили
    // Code size       8 (0x8)
    .maxstack  8
    IL_0000:  ldarg.2
    IL_0001:  call       valuetype [System.Windows.Forms]System.Windows.Forms.DialogResult [System.Windows.Forms]System.Windows.Forms.MessageBox::Show(string)
    IL_0006:  pop
    IL_0007:  ret
  } // end of method HelloClass::H
А именно добавили ".export[1]" в начало метода.
Сохраняем файл.

Делай три


Осталось собрать нашу библиотеку обратно, делаем это с помощью ilasm:
ilasm /DLL /OPTIMIZE /RESOURCE=HelloLib.res HelloLib.ilНа выходе получаем нашу Dll, запускаем её при помощи rundll32 и радуемся результату:



Немного теории и особенностей

Теория

Итак, мы добавляем в тело метода следующее: .export[N]В данном коде N - это порядок функции в таблице экспорта, то есть если нам нужно экспортировать две функции, у первой N=1, у второй N=2. Если у двух разных функций значения будут одинаковыми, то экспортироваться будет только последняя.

При добавлении данного параметра ilasm делает следующие изменения:
  • Меняет тип сборки на mixed (содержащую managed и unmanaged код).
  • Создаёт таблицу экспорта и заносит в неё все экспортированные функции.

Экспорт с другим именем

Есть так же возможность экспорта функции с другим именем, при этом её имя для managed кода сохраняется прежним делается заменой ".export[N]" на:.export[N] as <NewFunctioName>

Calling convention

Меняем calling convention функции, приводя код объявления к следующему виду:
.method public hidebysig static void modopt([mscorlib]System.Runtime.CompilerServices.CallConvThiscall) Hello(native int hwnd,
                                              native int moduleHandle,
                                              string cmdLineBuffer,
                                              int32 nCmdShow) cil managed
То есть добавляем перед именем метода следующее: modopt([mscorlib]System.Runtime.CompilerServices.CallConvThiscall)В данном случае calling convention меняется на Thiscall. Другие возможные варианты:
System.Runtime.CompilerServices.CallConvCdecl
System.Runtime.CompilerServices.CallConvFastcall
System.Runtime.CompilerServices.CallConvStdcall
System.Runtime.CompilerServices.CallConvThiscall

Особенности

При использовании данного метода возможны проблемы со сборкой мусора, выполняемой при закрытии приложения, в которое подгружена данная dll, что может приводить к исключениям, иногда ручной вызов Dispose объектов спасает ситуацию.

В заключение

Аналогичным образом можно экспортировать и другие функции, что позволяет писать плагины к umanaged программам.


P.S. Первая статья на сайте, отзывы и предложения приветствуются
.

RaveNoX
26.02.2010 23:34
Комментариев:6 RSS Просмотров:2213
Теги: c#, .net, fun, winapi, rundll32
ztaz
27.02.2010 8:27
Интересный вариант =)
Ссылка
rystsov-denis
27.02.2010 14:40
Как раз недавно наткнулся еще на один вариант, правда он связан с mono - Embedding Mono
Ссылка
Alexsashka
27.02.2010 21:50
Извините, может я не совсем понял, таким способом можно вызывать только функции через rundll32.exe или мы можем скомилировать mixed сборку с unmanaged кодом и вызывать функции напрямую?
Ссылка
RaveNoX
27.02.2010 23:41
Код в данном случае не unmanaged,а managed, просто добавляется возможность вызова static метода из unmanaged кода, динамически подгружая библиотеку и не используя COM.
rundll32.exe здесь приведён как частный случай.
Ссылка
n_sidoranov
28.02.2010 21:29
Уважаемый RaveNoX, Ваша методика конечно интересная, но она по принципу "сначала сами создаем себе сложности, а потом героически их преодолеваем". Все это хорошо для одной функции из пяти строчек. А для реального примера библиотеки из 15 классов каждый по 30 методов Ваши игры с дизассемблером затянутся очень надолго. К тому же вероятность ошибок высока. И еще я не понял чем сложна методика взаимодействия через COM. Она описана во многих книгах и совсем не сложна в реализации. Поэтому не надо делать сложно, когда можно сделать просто.
Ссылка
RaveNoX
28.02.2010 22:19
На самом деле данная статья служит лишь для того, чтобы показать возможность такой реализации, это теория. В ilasm библиотеки из 15 классов и 30 методов никто не полезет. Для работы с такими библиотеками, спокойно можно создать аттрибут, который потом написанная за полчаса программа будет искать в ilasm коде и добавлять .export в тело метода, после чего вероятность ошибки сводится к нулю.
По поводу COM и unmanaged, там ничего сложного действительно нет, но для его использования нужно как минимум на неплохом уровне знать C++. Плюс тащить библиотеку на unmanaged только как обёртку не всегда имеет смысл.
Просто / сложно тут понятие относительно и при наличии программы, которая добавляет нужное в ilasm и которая вызывается в postbuild можно ещё поспорить какой из вариантов будет проще. Кроме того это просто ещё один вариант, а что использовать в том или ином проекте - решать вам.
Ссылка

Arthur Kraev

RaveNoX Системный + VOIP администратор / Программист .Net + Mono
Особенности программирования под .Net, Mono. Различные нестандартные приёмы.
  • Блог

Облако тегов

.net c# fun rundll32 winapi
Строишь сложные системы? Хостинг от Parking.Ru

Записи

Популярные
  • k0stya > Система контроля версий для базы данных
  • Oxozle > Руководство по отладке многопоточных приложений в Visual Studio 2010
  • Jeje > Тюнинг производительно­сти для ASP.NET. Часть 1
  • Jeje > Razor - новый движок представлений в ASP.NET
  • Oxozle > Профилирование приложений в Visual Studio 2010
  • Jeje > NerdDinner. Шаг 1: Новый проект
  • Oxozle > Visual Studio 2010 Productivity Power Tools
  • sergeypopov > Ссылки к докладу «Расширяем Visual Studio 2010»
  • Jeje > NerdDinner. Шаг 2: Создание базы данных
  • mezastel > Паттерн-мэтчинг на языке C#
Все популярные записи
Обсуждаемые
  • k0stya > Система контроля версий для базы данных
  • Oxozle > Руководство по отладке многопоточных приложений в Visual Studio 2010
  • XaocCPS > Срочно! Вышел Razor View Engine и открыто имя нового проекта WebMatrix
  • mvcdev > Локализация ASP.NET приложений. Стиль кодирования
  • XaocCPS > Pivot-коллекция (silverlight) для навигации по статьям журнала MSDN Magazine
  • Jeje > NerdDinner. Шаг 2: Создание базы данных
  • Sharomank > Расширение Regex Tester для Visual Studio 2010
  • XaocCPS > Анонсирован SQL Server Compact Edition 4
  • Oxozle > Visual Studio 2010 Productivity Power Tools
  • XaocCPS > Выпущена библиотека ADO.NET Entity Framework Feature CTP 4
Все обсуждаемые записи

Блоги

Новые
  • Stanislav Gornakov [MVP]> Stanislav Gornakov
  • k0stya> k0stya
  • ][tiger> Just do IT - просто дует
  • Oxozle> KLUBS
  • mvcdev> WebDev
  • VitaliyP> PanarinV
  • Tamifist> Tamifist
  • kir> KLypkan
  • sadomovalex> Alexey Sadomov
  • noetic> Систематизация автоматизации
Обсуждаемые
  • mihailik> Олег Михайлик
  • ceo> Нотатник Вiктора Шатохiна [MSFT]
  • gaidar> Gaidar Magdanurov
  • MikhailChernomo­rdikov> Mikhail Chernomordikov [MSFT]
  • Alexander Lozhechkin [MSFT]> Alexander Lozhechkin
  • agladkik> Andrey Gladkikh: Microsoft Dynamics
  • sergun> Sergey Zwezdin
  • beerbong> Bong Blog
  • sos> Dmitry Soshnikov [MSFT]
  • not-a-kernel-gu­y> Зеркало: Not a kernel guy
О сайте   Свяжитесь с нами   Версия для печати
Работает на 1С-Битрикс: Управление сайтом ASP.NET  |  Хостинг на Parking.Ru