Сложный маршаллинг массива структур DirectX с C ++ на C #

Цель: преобразовать массив структур C ++ (указатель на?) в C #.

C ++: CreateVertexDeclaration ()

   HRESULT CreateVertexDeclaration(
     [in]           const D3DVERTEXELEMENT9 *pVertexElements,
     [out, retval]  IDirect3DVertexDeclaration9 **ppDecl
   );

C #:

Я использую this для определения структуры D3DVERTEXELEMENT9. SharpDX - это управляемая библиотека DirectX, сгенерированная непосредственно из заголовков DirectX SDK C ++, поэтому она предположительно вполне совместим с COM-взаимодействием. На самом деле у меня уже есть 10 других хуков, работающих отлично, но это первый с указателем на массив структур.

На всякий случай, если определение структуры SharpDX не работает, я также попытался заменить его своим собственным определением:

[StructLayout(LayoutKind.Sequential, Pack = 1)]
    public class D3DVERTEXELEMENT9
    {
        public short Stream;
        public short Offset;
        public DeclarationType Type;      // Enum
        public DeclarationMethod Method;  // Enum
        public DeclarationUsage Usage;    // Enum
        public byte UsageIndex;
    }

Я пробовал следующие сигнатуры методов C #:

Примечание: использование IntPtr devicePointer в качестве первого параметра не должно быть проблемой. У меня есть еще 10 хуков, которые успешно работают.

Примечание. Это перехватчики Direct3D API. Итак, моя программа передает данные, которые мне нужно маршалировать из неуправляемой среды C ++ в управляемую среду C #.

1:

CreateVertexDeclaration(IntPtr devicePointer, D3DVERTEXELEMENT9[] vertexElements, out IntPtr vertexDeclaration)

Результат: в массиве один элемент, но его значения по умолчанию (не имеет смысла). Это означает, что short Stream, short Offset, Type, Method, Usage и UsageIndex равны 0 (перечисления принимают свое первое значение).

2:

CreateVertexDeclaration(IntPtr devicePointer, ref D3DVERTEXELEMENT9[] vertexElements, out IntPtr vertexDeclaration)

Результат: сам массив равен нулю.

3:

CreateVertexDeclaration(IntPtr devicePointer, [In, Out] D3DVERTEXELEMENT9[] vertexElements, out IntPtr vertexDeclaration)

То же, что и 1. Никакой выгоды.

4:

CreateVertexDeclaration(IntPtr devicePointer, D3DVERTEXELEMENT9 vertexElements, out IntPtr vertexDeclaration)

То же, что и 1, ничего не меняет. Я получаю структуру по умолчанию. То же, если бы я позвонил new D3DVERTEXELEMENT9().

5:

CreateVertexDeclaration(IntPtr devicePointer, ref D3DVERTEXELEMENT9 vertexElements, out IntPtr vertexDeclaration)

Я забыл результат, но ничего не вышло. Я думаю, что это на самом деле разбило крючки.

6:

CreateVertexDeclaration(IntPtr devicePointer, IntPtr vertexElements, out IntPtr vertexDeclaration)

Я думаю, что либо это # ​​6, либо # 1 / # 2 должно быть правильным. Я наверное испортил реализацию этого метода?

Я пробовал использовать с ним этот код:

var vertex = (D3DVERTEXELEMENT9)Marshal.PtrToStructure(vertexElements, typeof(D3DVERTEXELEMENT9));

Но не работает! Это то же самое, что и №1. Моя упорядоченная структура указателя на структуру - это структура по умолчанию, такая же, как если бы я вызвал new D3DVERTEXELEMENT9().

7:

CreateVertexDeclaration(IntPtr devicePointer, ref IntPtr vertexElements, out IntPtr vertexDeclaration)

Ноль или ноль; недействительный.


Для метода №6 (с использованием IntPtr) я попытался просмотреть область памяти в Visual Studio. Следующие 50 байтов или около того были нулевыми. Может быть, в поле из 50 нулей был один 2 байт. Но это было примерно 49 байтов нулей, начиная с IntPtr, предоставленного методу.

Разве это не проблема? Разве это не означает, что указанный указатель уже неверен? Я решил, что у меня неправильная подпись метода. Проблема в том, что я пробовал всевозможные возможные комбинации, и они либо приводят к сбою программы, либо дают мне массив по умолчанию, такой же, как если бы я вызвал new D3DVERTEXELEMENT9().

Вопрос: как правильно маршалировать pVertexElements массив структур с C ++ на C # и почему мои текущие подписи неверны?

Дополнительное примечание. В C ++ длина этого конкретного массива определяется как с суффиксом D3DDECL_END. Я понятия не имею, как мне получить соответствующую длину массива в C # без какого-либо переданного параметра длины.


Другие рабочие хуки:

C ++:

BeginScene(LPDIRECT3DDEVICE9 pDevice)
BeginStateBlock(LPDIRECT3DDEVICE9 pDevice)
Clear(LPDIRECT3DDEVICE9 pDevice, DWORD Count,CONST D3DRECT* pRects,DWORD Flags,D3DCOLOR Color,float Z,DWORD Stencil)
ColorFill(LPDIRECT3DDEVICE9 pDevice, IDirect3DSurface9* pSurface,CONST RECT* pRect,D3DCOLOR color)
CreateAdditionalSwapChain(LPDIRECT3DDEVICE9 pDevice, D3DPRESENT_PARAMETERS* pPresentationParameters,IDirect3DSwapChain9** pSwapChain)
CreateCubeTexture(LPDIRECT3DDEVICE9 pDevice, UINT EdgeLength,UINT Levels,DWORD Usage,D3DFORMAT Format,D3DPOOL Pool,IDirect3DCubeTexture9** ppCubeTexture,HANDLE* pSharedHandle)

...

C #:

Примечание: я использую перечисления и структуры SharpDX для всех этих делегатов, и они отлично работают. Все они также начинаются с IntPtr devicePointer.

BeginSceneDelegate(IntPtr devicePointer);
BeginStateBlocKDelegate(IntPtr devicePointer);
ClearDelegate(IntPtr devicePointer, int count, IntPtr rects, ClearFlags flags, ColorBGRA color, float z, int stencil);
ColorFillDelegate(IntPtr devicePointer, IntPtr surface, IntPtr rect, ColorBGRA color);
CreateAdditionalSwapChainDelegate(IntPtr devicePointer, [In, Out] PresentParameters presentParameters, out SwapChain swapChain);
CreateCubeTextureDelegate(IntPtr devicePointer, int edgeLength, int levels, Usage usage, Format format, Pool pool, out IntPtr cubeTexture, IntPtr sharedHandle);
...

Журнал переданных параметров для других хуков:

DLL injection suceeded.
Setting up Direct3D 9 hooks...
Activating Direct3D 9 hooks...
CreateDepthStencilSurface(IntPtr devicePointer: 147414976, Int32 width: 1346, Int32 height: 827, Format format: D24S8, MultisampleType multiSampleType: None, Int32 multiSampleQuality: 0, Boolean discard: False, IntPtr& surface: (out), IntPtr sharedHandle: 0)
CreateDepthStencilSurface(IntPtr devicePointer: 147414976, Int32 width: 1346, Int32 height: 827, Format format: D24S8, MultisampleType multiSampleType: None, Int32 multiSampleQuality: 0, Boolean discard: False, IntPtr& surface: (out), IntPtr sharedHandle: 0)
Clear(IntPtr devicePointer: 147414976, Int32 count: 0, IntPtr rects: (Empty), ClearFlags flags: Target, ColorBGRA color: A:0 R:0 G:0 B:0, Single z: 1, Int32 stencil: 0)
Clear(IntPtr devicePointer: 147414976, Int32 count: 0, IntPtr rects: (Empty), ClearFlags flags: Target, ColorBGRA color: A:0 R:0 G:0 B:0, Single z: 1, Int32 stencil: 0)
BeginScene(IntPtr devicePointer: 147414976)
Clear(IntPtr devicePointer: 147414976, Int32 count: 0, IntPtr rects: (Empty), ClearFlags flags: Target, ColorBGRA color: A:0 R:0 G:0 B:0, Single z: 1, Int32 stencil: 0)
Clear(IntPtr devicePointer: 147414976, Int32 count: 0, IntPtr rects: (Empty), ClearFlags flags: Target, ColorBGRA color: A:0 R:0 G:0 B:0, Single z: 1, Int32 stencil: 0)
Clear(IntPtr devicePointer: 147414976, Int32 count: 0, IntPtr rects: (Empty), ClearFlags flags: ZBuffer, ColorBGRA color: A:0 R:0 G:0 B:0, Single z: 1, Int32 stencil: 0)
BeginScene(IntPtr devicePointer: 147414976)

Сигнатура метода, наиболее похожая на этот CreateVertexDeclaration(), кажется, Clear(). Вот моя реализация Clear():

private Result Clear(IntPtr devicePointer, int count, IntPtr rects, ClearFlags flags, ColorBGRA color, float z, int stencil)
        {
            try
            {
                var structSize = Marshal.SizeOf(typeof (Rectangle));
                var structs = new Rectangle[count];
                for (int i = 0; i < count; i++)
                {
                    structs[i] = (Rectangle) Marshal.PtrToStructure(rects, typeof (Rectangle));
                }
                // Seems to work fine, not sure why it doesn't work for CreateVertexDeclaration

                var rectangles = structs;
                Log.LogMethodSignatureTypesAndValues(devicePointer, count, rectangles.PrintTypesNamesValues(), flags,
                                                     color, z, stencil);
                GetOrCreateDevice(devicePointer);
                if (rectangles.Length == 0)
                    Device.Clear(flags, color, z, stencil);
                else
                    Device.Clear(flags, color, z, stencil, rectangles);
            }
            catch (Exception ex)
            {
                Log.Warn(ex.ToString());
            }

            return Result.Ok;
        }

person Jason    schedule 15.11.2012    source источник


Ответы (1)


Во-первых, объявление в SharpDX использует Pack = 2 (а не Pack = 1):

    [StructLayout(LayoutKind.Sequential, Pack = 2 )]
    public  partial struct VertexElement {  
     ...
    }

Также, если вы хотите быть уверенным в маршалинге, предпочитайте использовать unsafe, а не Marshal.StructureToPtr или любой другой метод маршалинга, когда это возможно (когда структура отображается непосредственно на эквивалент C #). Вы избежите проблем с производительностью Marshal и будете уверены в компоновке памяти (при условии, что структура была правильно объявлена ​​в C #)

public unsafe static void CreateVertexDeclaration(IntPtr devicePointer, IntPtr vertexElementsPtr, out IntPtr vertexDeclaration)
{
    var vertexElements = (VertexElement*)vertexElementsPtr;

    // Access to all VertexElement (last element is specified by Cmacro D3DDECL_END() in d3d9types.h)
    ...
}
person xoofx    schedule 16.11.2012
comment
Эй, я на самом деле ПРОСТО решил это! Моя ошибка заключалась в том, что я не пошел дальше с правильной реализацией №6. Мне пришлось самому восстановить массив, продолжить чтение дополнительных объектов VertexElement из памяти, даже если они ВИДЕЛИ, что все они нули (они не были), а затем проверить, было ли последнее чтение VertexElement == VertexElement.EndVertexDeclaration (отмечает конец массива). Спасибо за вашу библиотеку SharpDX. Я не могу себе представить, чтобы мне самому пришлось писать все эти COM-интерфейсы. - person Jason; 16.11.2012
comment
просто случайный вопрос: почему API SlimDX так похожи на API SharpDX, хотя оба они были созданы с использованием принципиально разных средств (SlimDX написан на управляемом C ++, а SharpDX - это чистый C #, сгенерированный из заголовков DirectX SDK)? - person Jason; 16.11.2012
comment
Поскольку SlimDX был довольно популярен и хорошо организован, SharpDX старался быть совместимым с ним. Чтобы сгенерировать код из заголовков DirectX SDK, SharpDX использует несколько рукописных файлов сопоставления xml, которые описывают весь процесс сопоставления (который может быть сложным: переименование функции, добавление перечислений, преобразование некоторых определений в перечисление, определение параметра метода как возвращаемое значение ... и т. д.). Например, файл сопоставления для D3D9 - это код. .google.com / p / sharpdx / source / browse / Source /. - person xoofx; 16.11.2012