Правильный маршалинг структур
От: Oxy  
Дата: 10.02.10 13:31
Оценка:
Есть структура

[cсode]
typedef struct
{
LPSTR pszVersionText;
INT nVersionTextMax;
INT nVersionTextLen;
} TcVersionInfoA, *PcVersionInfoA;
[/сcode]
И есть метод из DLL в который передается эта структура

typedef int (__stdcall* TcGetVersionExA) (PcVersionInfoA VInfo);


pszVersionText это ссылка на предварительно выделенный блок памяти размером nVersionTextMax.
То есть для вызова я должен создать структуру, выделить память для pszVersionText размером nVersionTextMax и передать это все методу который заполнит эту структуру данными.
Я сделал уже рабочий вариант типа


public struct TcVersionInfoA
{
    public IntPtr pszVersionText;
    INT nVersionTextMax;
    INT nVersionTextLen;
}

[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
public struct TAllocatedString
{
    [MarshalAs(UnmanagedType.LPStr, SizeConst=250)]
    public string Str;
}

[DllImport(DLLName, EntryPoint = "cGetVersionExW@4", CallingConvention = CallingConvention.StdCall)]
private static extern int cGetVersionEx(IntPtr VInfo);

И делаю такой вызов

public VersionInfoEx GetVersionEx()
{
    TcVersionInfo verInfo = new TcVersionInfo();
    verInfo.pszVersionText = GetStructureBlockInHeap(new TAllocatedString());
    verInfo.nVersionTextMax = 250;

    IntPtr verInfoPtr = GetStructureBlockInHeap(verInfo);
    int status = 0;
    VersionInfoEx res = new VersionInfoEx();
    try
    {
        status = cGetVersionEx(verInfoPtr);
        verInfo = (TcVersionInfo) Marshal.PtrToStructure(verInfoPtr, verInfo.GetType());
        res.pszVersionText = Marshal.PtrToStringUni(verInfo.pszVersionText);
    }
    finally
    {
        Marshal.DestroyStructure(verInfoPtr, verInfo.GetType());
        Marshal.DestroyStructure(verInfo.pszVersionText, typeof(TAllocatedString));
    }
    return res;
}

private IntPtr GetStructureBlockInHeap(object structure)
{
    if (structure == null)
        return IntPtr.Zero;
    int structSizeInBytes = Marshal.SizeOf(structure.GetType());
    // выделить в куче буфер для нашей структуры
    IntPtr structurePtr = Marshal.AllocHGlobal(structSizeInBytes);
    // скопировать .NET структуру в только что выделенный unmanaged кусок памяти
    Marshal.StructureToPtr(structure, structurePtr, false);
    return structurePtr;
}

Все работает, но очень уж громоздко получилось.
Подскажите плиз как все это оформить проще? Может есть такие то контструкции которые облегчат этот код?
Re: Правильный маршалинг структур
От: _nn_ www.nemerleweb.com
Дата: 10.02.10 13:47
Оценка: +2
Здравствуйте, Oxy, Вы писали:

Oxy>Все работает, но очень уж громоздко получилось.

Oxy>Подскажите плиз как все это оформить проще? Может есть такие то контструкции которые облегчат этот код?

Самый простой вариант для маршалинга это взять C++/CLI
http://rsdn.nemerleweb.com
http://nemerleweb.com
Re: Правильный маршалинг структур
От: Fortnum  
Дата: 10.02.10 14:43
Оценка:
Здравствуйте, Oxy, Вы писали:

Oxy>То есть для вызова я должен создать структуру, выделить память для pszVersionText размером nVersionTextMax и передать это все методу который заполнит эту структуру данными.


1. Судя по приведенному вами коду, память для pszVersionText выделяете не вы, а функция, которую вы вызываете в DLL, т.к. вы (а) не инициализируете pszVersionText перед вызовом этой функции и (б) у вас в TcVersionInfoA поле pszVersionText указано как IntPtr.

2. Непонятен фокус со структурой TAllocatedString. Зачем она? Не лучше ли для освобождения памяти, выделенной функцией DLL под строку, использовать FreeHGlobal?

finally
{
    Marshal.FreeHGlobal(verInfo.pszVersionText);
    Marshal.DestroyStructure(verInfoPtr, typeof(TcVersionInfoA));
}


Да и SizeConst при UnmanagedType.LPStr роли никакой не играет.
Re[2]: Правильный маршалинг структур
От: Oxy  
Дата: 10.02.10 15:09
Оценка:
Здравствуйте, Fortnum, Вы писали:

F>Здравствуйте, Oxy, Вы писали:


Oxy>>То есть для вызова я должен создать структуру, выделить память для pszVersionText размером nVersionTextMax и передать это все методу который заполнит эту структуру данными.


F>1. Судя по приведенному вами коду, память для pszVersionText выделяете не вы, а функция, которую вы вызываете в DLL, т.к. вы (а) не инициализируете pszVersionText перед вызовом этой функции и (б) у вас в TcVersionInfoA поле pszVersionText указано как IntPtr.


Нет, вы плохо прочитали код. Память я выделил специально вот этой строчкой
verInfo.pszVersionText = GetStructureBlockInHeap(new TAllocatedString());


F>2. Непонятен фокус со структурой TAllocatedString. Зачем она? Не лучше ли для освобождения памяти, выделенной функцией DLL под строку, использовать FreeHGlobal?


Фокус прост. Я должен выделить для метода буфер под строку и передать ему указатель на этот буфер pszVersionText, а так же размер буфера verInfo.nVersionTextMax = 250; Для унимфикации я создал структуру с одним единственным строковым полем (по сути буфер для строки) и дальше не забочусь об кодировках и прочих моментах. Иначе мне пришлось бы делать кучу ручной работы, создавать буфер и прочее.

F>
F>finally
F>{
F>    Marshal.FreeHGlobal(verInfo.pszVersionText);
F>    Marshal.DestroyStructure(verInfoPtr, typeof(TcVersionInfoA));
F>}
F>


F>Да и SizeConst при UnmanagedType.LPStr роли никакой не играет.

Да вы правы. Там должно быть ByValTStr.
Re[3]: Правильный маршалинг структур
От: Fortnum  
Дата: 10.02.10 16:01
Оценка:
Здравствуйте, Oxy, Вы писали:

Oxy>Нет, вы плохо прочитали код. Память я выделил специально вот этой строчкой


Звиняюсь, не заметил.

Oxy>Фокус прост. Я должен выделить для метода буфер под строку и передать ему указатель на этот буфер pszVersionText, а так же размер буфера verInfo.nVersionTextMax = 250; Для унимфикации я создал структуру с одним единственным строковым полем (по сути буфер для строки) и дальше не забочусь об кодировках и прочих моментах. Иначе мне пришлось бы делать кучу ручной работы, создавать буфер и прочее.


Ну, не такю уж и кучу... Зато без квазиструктур. Представляете, кому-то если придется в вашем коде разбираться? А если в структуре таких строковых полей 15 штук?
public VersionInfoEx GetVersionEx()
{
    TcVersionInfo verInfo = new TcVersionInfo();
    verInfo.nVersionTextMax = 250;
    verInfo.pszVersionText = Marshal.AllocHGlobal(verInfo.nVersionTextMax * 2);
    ...
Re[4]: Правильный маршалинг структур
От: Oxy  
Дата: 10.02.10 17:27
Оценка:
Здравствуйте, Fortnum, Вы писали:

F>Здравствуйте, Oxy, Вы писали:


Oxy>>Нет, вы плохо прочитали код. Память я выделил специально вот этой строчкой


F>Звиняюсь, не заметил.


Oxy>>Фокус прост. Я должен выделить для метода буфер под строку и передать ему указатель на этот буфер pszVersionText, а так же размер буфера verInfo.nVersionTextMax = 250; Для унимфикации я создал структуру с одним единственным строковым полем (по сути буфер для строки) и дальше не забочусь об кодировках и прочих моментах. Иначе мне пришлось бы делать кучу ручной работы, создавать буфер и прочее.


F>Ну, не такю уж и кучу... Зато без квазиструктур. Представляете, кому-то если придется в вашем коде разбираться? А если в структуре таких строковых полей 15 штук?

F>
F>public VersionInfoEx GetVersionEx()
F>{
F>    TcVersionInfo verInfo = new TcVersionInfo();
F>    verInfo.nVersionTextMax = 250;
F>    verInfo.pszVersionText = Marshal.AllocHGlobal(verInfo.nVersionTextMax * 2);
F>    ...
F>

Не спорю, может и зря ввел все это. Но меня больше интересует вопрос как это сделать еще проще. В идеале что бы компилятор автоматом делал большинство работы (как это с обычными строками происходит)
Re[5]: Правильный маршалинг структур
От: Fortnum  
Дата: 10.02.10 18:05
Оценка:
Здравствуйте, Oxy, Вы писали:

Oxy>Не спорю, может и зря ввел все это. Но меня больше интересует вопрос как это сделать еще проще. В идеале что бы компилятор автоматом делал большинство работы (как это с обычными строками происходит)


Написать свой маршалер для структуры TcVersionInfoA (MarshalAs.MarshalType[Ref]).
Маршалер пишется на C#, просто весь код по преобразованию Managed-Unmanaged туда инкапсулируется, и вызов метода становится прозрачным.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.