【问题标题】:Difficult Marshalling DirectX array of structs from C++ to C#从 C++ 到 C# 的困难编组 DirectX 结构数组
【发布时间】:2012-11-03 19:57:25
【问题描述】:

目标:将 C++(指向?)的结构数组元组到 C#。

C++: CreateVertexDeclaration()

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

C#:

我使用this 来定义D3DVERTEXELEMENT9 结构。 SharpDX 是一个托管的 DirectX 库generated directly from the DirectX SDK C++ headers,因此它应该与 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 Streamshort OffsetTypeMethodUsageUsageIndex 都是 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 字节。但从提供给方法的 IntPtr 开始,几乎是 49 个字节的零。

这不是问题吗?这是否意味着提供的指针已经不正确?所以我认为这意味着我有错误的方法签名。问题是我一直在尝试各种可能的组合,它们要么使程序崩溃,要么给我一个默认数组,就像我调用 new D3DVERTEXELEMENT9() 一样。

问题:将pVertexElements 结构数组从 C++ 正确编组到 C# 的解决方案是什么,为什么我当前的签名不正确?

附加说明:在 C++ 中,此特定数组的长度由 suffixing a 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;
        }

【问题讨论】:

    标签: c# c++ marshalling unmarshalling sharpdx


    【解决方案1】:

    首先,SharpDX 中的声明使用 Pack = 2(而不是 Pack = 1):

    [StructLayout(LayoutKind.Sequential, Pack = 2)] 公共部分结构顶点元素{ ... }

    此外,如果您想确定封送处理,请尽可能使用 unsafe,而不是 Marshal.StructureToPtr 或任何其他封送处理方法(当结构直接映射到 C# 等效项时)。您将避免 Marshal 性能问题,并且您将确定内存布局(假设该结构已在 c# 中正确声明)

    public unsafe static void CreateVertexDeclaration(IntPtr devicePointer, IntPtr vertexElementsPtr, out IntPtr vertexDeclaration) { var vertexElements = (VertexElement*)vertexElementsPtr; // 访问所有 VertexElement(最后一个元素由 d3d9types.h 中的 Cmacro D3DDECL_END() 指定) ... }

    【讨论】:

    • 嘿,我刚刚解决了!我的错误在于没有进一步使用正确的#6 实现。我必须自己重建数组,继续从内存中读取 more VertexElement 对象,即使它们看起来全为零(它们不是),然后检查最后读取的 VertexElement 是否为 == VertexElement.EndVertexDeclaration (标记数组的结尾)。感谢您的 SharpDX 库。我无法想象必须自己编写所有这些 COM 接口。
    • 只是一个随机的问题:为什么 SlimDX 的 API 与 SharpDX 的 API 如此相似,尽管两者是从根本上不同的方式构建的(SlimDX 是用托​​管 C++ 编写的,而 SharpDX 是从 DirectX SDK 生成的纯 C#标题)?
    • 由于 SlimDX 非常流行且组织良好,SharpDX 试图与之兼容。为了从 DirectX SDK Headers 生成代码,SharpDX 使用了几个手写的 xml 映射文件来描述整个映射过程(可能很复杂:函数重命名,添加枚举,将一些定义转换为枚举,将方法参数定义为返回值...等)。例如,D3D9 的映射文件是code.google.com/p/sharpdx/source/browse/Source/…
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-12-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多