?

Log in

No account? Create an account

Предыдущий пост | Следующий пост

Сложный DllImport в .NET (C#)

  • Feb. 29th, 2008 at 10:01 PM
karuchiferu
Понадобилось сегодня заимпортить в .NET проект несколько функций из своей DLLки, написанной на С. До этого мне приходилось импортить только некоторые функции Win32API, типа SendMessage или MessageBeep, так что проблем не возникало. А вот в той функции, которую мне понадобилось заимпортить - передается структура, содержащая 39 различных полей, 2 из которых - указатели на callback функции, в которые в свою очередь передаются другие структуры еще по 8 разных параметров :) В общем, столкнулся с некоторыми проблемами с определениями полей структуры, решение которых хочу описать ниже. И себе будет полезно, чтобы не забыть, и еще может кому пригодится.

Рассмотрим следующий пример (не ищите глубокого смысла в типах параметров, я просто собрал все в кучу, чтобы можно было потом посмотреть и понять, как нужно подогнать код под вашу конкретную задачу).

Предположим, в DLL у нас объявлена функция UnmanagedFunction, имеющая следующее определение:

int WINAPI UnmanagedFunction (BunchOfParameters * params);

Как видно, функция принимает 1 параметр - структуру BunchOfParameters, которая определяется следующим образом:

struct BunchOfParameters  {        
    OUT WCHAR * pszPointerToWideStringBuffer; // указатель на буфер где-то в памяти
    IN WCHAR szWideStringBuffer[128]; // буфер, хранящийся в самой структуре
    IN CallbackFunc lpfnCallbackFunc; // указатель на callback функцию
    IN const WCHAR * pszPointerToWideStringConstant; // указатель на строку константу
    IN int justAnInteger; // просто целое для примера
    IN float justAFloat; // просто 4х байтовое число с плавающей точкой
};

CallbackFunc здесь - обычная callback функция, например такая:

typedef int (CALLBACK * CallbackFunc) (BunchOfParameters * aBunchOfParameters);

Для усложнения задачи предположим, что callback функция тоже будет принимать параметр структуру - BunchOfParameters. Ниже я покажу, как обращаться с этим параметром.

Итак, атрибут для импорта вышеуказанной функции в .NET будет выглядит следующим образом:

[DllImport("SomeCoolLibrary.dll", EntryPoint = "UnmanagedFunction")]
private static extern int UnmanagedFunction(IntPtr aBunchOfParameters);

Перед тем, как определить структуру BunchOfParameters, определим сперва delegate, который будет использоваться для нашей callback функции:

public delegate int CallbackFunc (IntPtr aBunchOfParametersPtr);

Эквивалент нашей структуры в .NET определяется вот так:

[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
public struct BunchOfParameters  
{    
    // указатель на буфер где-то в памяти
    public IntPtr pszPointerToWideStringBuffer;
    // буфер, хранящийся в самой структуре;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst=128)] public string szWideStringBuffer;
    // указатель на callback функцию
    [MarshalAs(UnmanagedType.FunctionPtr)] public CallbackFunc lpfnCallbackFunc;
    // указатель на строку константу (просто чтобы прочитать)
    [MarshalAs(UnmanagedType.LPWStr)]
    public string pszPointerToWideStringConstant;
    // просто целое для примера
    public Int32 justAnInteger; 
    // просто 4х байтовое число с плавающей точкой
    public Single justAFloat; 
}

Атрибут StructLayout определяет, что поля внутри структуры должны располагаться в памяти именно в том порядке, в котором определены (LayoutKind.Sequential), а кодировка строк внутри структуры - Unicode, т.е. 2 байта на символ (CharSet=CharSet.Unicode).

Теперь о вызове самой функции. Перед вызовом UnmanagedFunction нужно подготовить структуру BunchOfParameters:

// создать структуру в .NET:
BunchOfParameters aBunchOfParameters = new BunchOfParameters();
aBunchOfParameters.lpfnCallbackFunc = OurCallbackFunction;
aBunchOfParameters.szWideStringBuffer= "Hello World 1";
aBunchOfParameters.pszPointerToWideStringConstant = "Hello World 2";
aBunchOfParameters.justAnInteger= 123;
aBunchOfParameters.justAFloat= 456.7f;
// получить реальный её размер в байтах
int structSizeInBytes = Marshal.SizeOf(typeof(BunchOfParameters));
// выделить в куче буфер для нашей структуры
IntPtr aBunchOfParametersPtr = Marshal.AllocHGlobal(structSizeInBytes);
// скопировать .NET структуру в только что выделенный unmanaged кусок памяти
Marshal.StructureToPtr(aBunchOfParameters, aBunchOfParametersPtr, false);
// вызвать нашу функцию в DLL, передав указатель на структуру
int result = UnmanagedFunction(aBunchOfParametersPtr);
// освободить кусок памяти в куче
Marshal.FreeHGlobal(aBunchOfParametersPtr);

Вуаля :) Как видно, строки можно просто присваивать, не забочась о длине и кодировке. Круто :)

Теперь еще посмотрим на callback функцию OurCallbackFunction. Здесь, чтобы преобразовать указатель на структуру в саму структуру, нужно проделать те же самые операции в обратном порядке:

public static Int32 OurCallbackFunction(IntPtr aBunchOfParametersPtr)
{
    BunchOfParameters aBunchOfParameters=
        (BunchOfParameters) Marshal.PtrToStructure(aBunchOfParametersPtr, typeof(BunchOfParameters));

    string sNETString = Marshal.PtrToStringUni(aBunchOfParameters.pszPointerToWideStringBuffer);
    sNETString = "Change it somehow\0";

    // теперь скопируем строку в буфер, на который указывает указатель pszPointerToWideStringBuffer
    Marshal.Copy(Encoding.Unicode.GetBytes(sNETString), 0,
        aBunchOfParameters.pszPointerToWideStringBuffer, (sNETString.Length) * 2);

    return 0;
}


Вот такие дела.. К сожалению нет времени расписывать все до мелочей, вроде и так все ясно. Если нет - рад вашим комментариям :)

Comments

Без заголовка - (Anonymous) - Feb. 29th, 2008 04:23 pm (UTC)
funbit wrote:
Feb. 29th, 2008 04:29 pm (UTC)
ага, завтра еще напишу какие могут быть заморочки с этим delegate'ом, когда он вызывается из unmanaged кода :) я в шоке был
Без заголовка - (Anonymous) - Feb. 29th, 2008 04:36 pm (UTC)
funbit wrote:
Feb. 29th, 2008 04:43 pm (UTC)
ну.. я тоже за простоту и ясность, но поддерживать старые библиотеки еще долго придется :)
some41 wrote:
Feb. 29th, 2008 10:04 pm (UTC)
сдается мне "внутреннего терминатора" у строки .нет нету
funbit wrote:
Mar. 1st, 2008 03:28 am (UTC)
да, скорее всего так и есть, надо почитать будет на досуге про внутренности всех типов
(Anonymous) wrote:
Aug. 16th, 2008 09:32 am (UTC)
Как сделать DllImport ?
Помогите пожалуйста,как сделать DllImport на следущую функцию? Очень нужна помощь!

DA_OI_EXPORT_API
da_oi_return_type
da_oi_sequence_register( unsigned int const n,
da_oi_image_info const* images_ptr,
unsigned int base_index,
bool use_a_mask,
da_oi_image_info const* mask_image_ptr,
da_oi_crop_style crop_style,
da_oi_results_info ** results,
da_oi_image_info ** mapped_images );
(Anonymous) wrote:
Apr. 7th, 2009 02:15 pm (UTC)
Еще сцылко
Запостю ссылку сюда просто так, чтобы некоторым попавшим сюда было проще разобраться на примерах. Не сносите каммент, плиз. :)
http://www.ddj.com/windows/184406285
ivinsky wrote:
Aug. 8th, 2009 03:37 am (UTC)
Достаточно подробное описание по теме можно найти в книги Tony Northrup - Microsoft.Press.MCTS.Self-Paced.Training.Kit.Exam.70-536.Microsoft.NET.Framework.Application.Development.Foundation.2nd.edition, 13 глава. Все описано просто и понятно. Книжка http://content.mail.ru/u/ms.rar
Русская версия http://files.nnov.ru/?F=111928438886049
andykras wrote:
Jul. 8th, 2010 06:26 pm (UTC)
а мне книжка как-то не понравилась. да и про Interop там не очень интересно расписано. например про маршалинг типов данных написано что мол просто берите и юзайте MarshalAs... это все-равно что ничего не сказать. а тут могут быть тонкости и весьма занятные. "Очень удобно то, что этот атрибут полностью поддерживается технологией Microsoft IntelliSense..." звучит как реклама)))

Latest Month

January 2019
S M T W T F S
  12345
6789101112
13141516171819
20212223242526
2728293031  
Powered by LiveJournal.com
Designed by chasethestars

Improved by [info]funbit