Export managed to unmanaged

То есть нужно написать библиотеку с экспортируемыми функциями на c# (понятно, что есть управляемый c++ для этого, но... предположим, что он уже подзабыт). Не вдаваясь в теорию, кратко, это нужно делать так:

  • Компилируем c# код - получаем библиотеку DLL
  • Из DLL получаем код на промежуточном языке (то есть IL)
  • В IL коде объявляем таблицу экспортируемых функций (VT) и помечаем их
  • Компилируем IL

С первым шагом вопросов нет, единственное, функции на экспорт должны быть статическими, область видимости значения не имеет, можно использовать атрибуты типа MarshalAs... Компилируем для x86 (для определенности, хотя для других платформ не проверял)

IL дамп получаем командой

ildasm /OUT:MyDLL.il MyDLL.dll
При этом еще будет создан файл ресурсов MyDLL.res, который в дальнейшем можно игнорировать. Собственно вся магия будет в файле MyDLL.il. Находим строку
.corflags 0x00000001
Эта строка говорит, что сборка содержит только код IL, и загрузчик будет игнорировать секцию .reloc, то есть, проще говоря, экспортируемые функции не смогут быть правильно загружены (COMIMAGE_FLAGS_ILONLY). Изменяем эту строку на
.corflags 0x00000002
(COMIMAGE_FLAGS_32BITSREQUIRED). А так загрузчик сможет прочитать .reloc и экспортировать функции. Дальше - таблица экспортируемых функций. Сразу после строки '.corflags 0x00000001' добавляем строки
.vtfixup [1] int32 fromunmanaged at VT_01
.data VT_01 = int32(0)
Это означает: объявляем таблицу экспортируемых адресов (.vtfixup), с одним элементом ([1]), для вызова из неуправляемого кода (fromunmanaged), с меткою VT_01. Таких таблиц может быть много и число элементов (слотов) в одной таблице может быть больше одного. Вторая строка показывает какими типами заполнять таблицу, реально таблица будет заполняться во время выполнения; подозреваю, что для 64-битной библиотеки должно быть .data VT_01 = int64(0). Теперь нужно показать какие методы будут экспортироваться. Находим в IL текст нужной функции и в его начало добавляем
.method public hidebysig static int32
test(string arg) cil managed
{
//добавляем...
    .vtentry 1 : 1
//это означает какой слот в какой таблице будет представлять адрес этой функции
//.vtentry  :
    .export [1] as test_export
//под каким именем и с каким порядковым номером (ordinal) экспортируется
//.export [ordinal] as export_name
Компилируем IL
ilasm /OUT:MyDLL.dll MyDLL.il /DLL
Это все. Товарищ Robert Giesecke написал компонент для автоматизации этого нелегкого труда.