草庐IT

c# - 在结构中编码(marshal) IntPtr[] 会导致 midiStream 函数出现错误,但将数组展开到一堆字段是可行的

coder 2024-06-04 原文

我正在尝试使用 C# 中的 Windows 多媒体 MIDI 函数。具体来说:

MMRESULT midiOutPrepareHeader(  HMIDIOUT hmo,  LPMIDIHDR lpMidiOutHdr,  UINT cbMidiOutHdr  );
MMRESULT midiOutUnprepareHeader(  HMIDIOUT hmo,  LPMIDIHDR lpMidiOutHdr,  UINT cbMidiOutHdr  );
MMRESULT midiStreamOut(  HMIDISTRM hMidiStream,  LPMIDIHDR lpMidiHdr,  UINT cbMidiHdr  );
MMRESULT midiStreamRestart(  HMIDISTRM hms  );

/* MIDI data block header */
typedef struct midihdr_tag {
    LPSTR       lpData;               /* pointer to locked data block */
    DWORD       dwBufferLength;       /* length of data in data block */
    DWORD       dwBytesRecorded;      /* used for input only */
    DWORD_PTR   dwUser;               /* for client's use */
    DWORD       dwFlags;              /* assorted flags (see defines) */
    struct midihdr_tag far *lpNext;   /* reserved for driver */
    DWORD_PTR   reserved;             /* reserved for driver */
#if (WINVER >= 0x0400)
    DWORD       dwOffset;             /* Callback offset into buffer */
    DWORD_PTR   dwReserved[8];        /* Reserved for MMSYSTEM */
#endif
} MIDIHDR, *PMIDIHDR, NEAR *NPMIDIHDR, FAR *LPMIDIHDR;

通过执行以下操作,我可以在 C 程序中成功使用这些函数:

HMIDISTRM hms;
midiStreamOpen(&hms, ...);
MIDIHDR hdr;
hdr.this = that; ...

midiStreamRestart(hms);
midiOutPrepareHeader(hms, &hdr, sizeof(MIDIHDR)); // sizeof(MIDIHDR) == 64
midiStreamOut(hms, &hdr, sizeof(MIDIHDR));
// wait for an event that is set from the midi callback when the playback has finished
WaitForSingleObject(...);
midiOutUnprepareHeader(hms, &hdr, sizeof(MIDIHDR));

上面的调用序列有效并且没有产生错误(为了便于阅读,省略了错误检查)。

为了在 C# 中使用它们,我创建了一些 P/Invoke 代码:

[DllImport("winmm.dll")]
public static extern int midiOutPrepareHeader(IntPtr handle, ref MidiHeader header, uint headerSize);
[DllImport("winmm.dll")]
public static extern int midiOutUnprepareHeader(IntPtr handle, ref MidiHeader header, uint headerSize);
[DllImport("winmm.dll")]
public static extern int midiStreamOut(IntPtr handle, ref MidiHeader header, uint headerSize);
[DllImport("winmm.dll")]
public static extern int midiStreamRestart(IntPtr handle);

[StructLayout(LayoutKind.Sequential)]
public struct MidiHeader
{
    public IntPtr Data;
    public uint BufferLength;
    public uint BytesRecorded;
    public IntPtr UserData;
    public uint Flags;
    public IntPtr Next;
    public IntPtr Reserved;
    public uint Offset;

    //[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
    //public IntPtr[] Reserved2;

    public IntPtr Reserved0;
    public IntPtr Reserved1;
    public IntPtr Reserved2;
    public IntPtr Reserved3;
    public IntPtr Reserved4;
    public IntPtr Reserved5;
    public IntPtr Reserved6;
    public IntPtr Reserved7;
}

调用顺序同C:

var hdr = new MidiHeader();
hdr.this = that;
midiStreamRestart(handle);
midiOutPrepareHeader(handle, ref header, headerSize); // headerSize == 64
midiStreamOut(handle, ref header, headerSize);
mre.WaitOne(); // wait until the midi playback has finished.
midiOutUnprepareHeader(handle, ref header, headerSize);

MIDI 输出有效并且代码没有产生错误(再次省略了错误检查)。

只要我在 MidiHeader 中用数组取消注释这两行,而是删除 Reserved0Reserved7 字段,它不会'不再工作了。发生的情况如下:

一切正常,直到并包括 midiStreamOut。我可以听到 MIDI 输出。播放长度正确。但是,播放结束时不会调用事件回调。

此时MidiHeader.Flags的值为0xe,表示流还在播放(即使回调已经收到播放已完成的消息通知)完成的)。 MidiHeader.Flags的值应该是9,表示流已经播放完毕。

midiOutUnprepareHeader 调用失败,错误代码为 0x41(“无法在媒体数据仍在播放时执行此操作。重置设备,或等待数据播放完毕玩完了。”)。请注意,按照错误消息中的建议重置设备实际上并不能解决问题(等待或多次尝试也无法解决)。

另一个正确工作的变体是,如果我在 C# 声明的签名中使用 IntPtr 而不是 ref MidiHeader,然后手动分配非托管内存,复制我的 MidiHeader 结构到该内存中,然后使用分配的内存调用函数。

此外,我尝试将传递给 headerSize 参数的大小减小到 32。因为这些字段是保留的(事实上,在以前版本的 Windows API 中不存在), 他们似乎没有什么特别的目的。然而,这并不能解决问题,即使 Windows 甚至不知道数组的存在,因此它不应该做任何事情。再次完全注释掉数组可以解决问题(即,数组和 8 个 Reserved* 字段都被注释掉,并且 headerSize 为 32)。

这向我暗示 IntPtr[] Reserved2 无法正确编码,尝试这样做会破坏其他值。为了验证这一点,我创建了一个 Platform Invoke 测试项目:

WIN32PROJECT1_API void __stdcall test_function(struct test_struct_t *s)
{
    printf("%u %u %u %u %u %u %u %u\n", s->test0, s->test1, s->test2, s->test3, s->test4, s->test5, s->test6, s->test7);
    for (int i = 0; i < sizeof(s->pointer_array) / sizeof(s->pointer_array[0]); ++i)
    {
        printf("%u ", ((uint32_t)s->pointer_array[i]) >> 16);
    }
    printf("\n");
}

typedef int32_t *test_ptr;

struct test_struct_t
{
    test_ptr test0;
    uint32_t test1;
    uint32_t test2;
    test_ptr test3;
    uint32_t test4;
    test_ptr test5;
    uint32_t test6;
    uint32_t test7;
    test_ptr pointer_array[8];
};

从 C# 中调用:

[StructLayout(LayoutKind.Sequential)]
struct TestStruct
{
    public IntPtr test0;
    public uint test1;
    public uint test2;
    public IntPtr test3;
    public uint test4;
    public IntPtr test5;
    public uint test6;
    public uint test7;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
    public IntPtr[] pointer_array;
}

[DllImport("Win32Project1.dll")]
static extern void test_function(ref TestStruct s);

static void Main(string[] args)
{
    TestStruct s = new TestStruct();
    s.test0 = IntPtr.Zero;
    s.test1 = 1;
    s.test2 = 2;
    s.test3 = IntPtr.Add(IntPtr.Zero, 3);
    s.test4 = 4;
    s.test5 = IntPtr.Add(IntPtr.Zero, 5);
    s.test6 = 6;
    s.test7 = 7;
    s.pointer_array = new IntPtr[8];
    for (int i = 0; i < s.pointer_array.Length; ++i)
    {
        s.pointer_array[i] = IntPtr.Add(IntPtr.Zero, i << 16);
    }
    test_function(ref s);

    Console.ReadLine();
}

并且输出符合预期,因此 IntPtr[] pointer_array 的编码在该程序中有效。

我知道不使用 SafeHandle 是次优的,但是,当使用它时,使用数组时 MIDI 函数的行为甚至更奇怪,所以我选择可能解决一个一个问题。

为什么使用 IntPtr[] Reserved2 会导致错误?


下面是生成完整示例的更多代码:

C 代码

/*
* example9.c
*
*  Created on: Dec 21, 2011
*      Author: David J. Rager
*       Email: djrager@fourthwoods.com
*
* This code is hereby released into the public domain per the Creative Commons
* Public Domain dedication.
*
* http://http://creativecommons.org/publicdomain/zero/1.0/
*/
#include <windows.h>
#include <mmsystem.h>
#include <stdio.h>

HANDLE event;

static void CALLBACK example9_callback(HMIDIOUT out, UINT msg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2)
{
    switch (msg)
    {
    case MOM_DONE:
        SetEvent(event);
        break;
    case MOM_POSITIONCB:
    case MOM_OPEN:
    case MOM_CLOSE:
        break;
    }
}

int main()
{
    unsigned int streambufsize = 24;
    char* streambuf = NULL;

    HMIDISTRM out;
    MIDIPROPTIMEDIV prop;
    MIDIHDR mhdr;
    unsigned int device = 0;

    streambuf = (char*)malloc(streambufsize);
    if (streambuf == NULL)
        goto error2;

    memset(streambuf, 0, streambufsize);

    if ((event = CreateEvent(0, FALSE, FALSE, 0)) == NULL)
        goto error3;

    memset(&mhdr, 0, sizeof(mhdr));
    mhdr.lpData = streambuf;
    mhdr.dwBufferLength = mhdr.dwBytesRecorded = streambufsize;
    mhdr.dwFlags = 0;

    // flags and event code
    mhdr.lpData[8] = (char)0x90;
    mhdr.lpData[9] = 63;
    mhdr.lpData[10] = 0x55;
    mhdr.lpData[11] = 0;
    // next event
    mhdr.lpData[12] = 96; // delta time?
    mhdr.lpData[20] = (char)0x80;
    mhdr.lpData[21] = 63;
    mhdr.lpData[22] = 0x55;
    mhdr.lpData[23] = 0;


    if (midiStreamOpen(&out, &device, 1, (DWORD)example9_callback, 0, CALLBACK_FUNCTION) != MMSYSERR_NOERROR)
        goto error4;

    //printf("sizeof midiheader = %d\n", sizeof(MIDIHDR));

    if (midiOutPrepareHeader((HMIDIOUT)out, &mhdr, sizeof(MIDIHDR)) != MMSYSERR_NOERROR)
        goto error5;

    if (midiStreamRestart(out) != MMSYSERR_NOERROR)
        goto error6;

    if (midiStreamOut(out, &mhdr, sizeof(MIDIHDR)) != MMSYSERR_NOERROR)
        goto error7;

    WaitForSingleObject(event, INFINITE);

error7:
    //midiOutReset((HMIDIOUT)out);

error6:
    MMRESULT blah = midiOutUnprepareHeader((HMIDIOUT)out, &mhdr, sizeof(MIDIHDR));

    printf("stuff: %d\n", blah);

error5:
    midiStreamClose(out);

error4:
    CloseHandle(event);

error3:
    free(streambuf);

error2:
    //free(tracks);

error1:
    //free(hdr);

    return(0);
}

C#代码

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;

namespace MidiOutTest
{
    class Program
    {
        [DllImport("winmm.dll")]
        public static extern int midiStreamOpen(out IntPtr handle, ref uint deviceId, uint cMidi, MidiCallback callback, IntPtr userData, uint flags);
        [DllImport("winmm.dll")]
        public static extern int midiStreamOut(IntPtr handle, ref MidiHeader header, uint headerSize);
        [DllImport("winmm.dll")]
        public static extern int midiStreamRestart(IntPtr handle);
        [DllImport("winmm.dll")]
        public static extern int midiOutPrepareHeader(IntPtr handle, ref MidiHeader header, uint headerSize);
        [DllImport("winmm.dll")]
        public static extern int midiOutUnprepareHeader(IntPtr handle, ref MidiHeader header, uint headerSize);
        [DllImport("winmm.dll", CharSet = CharSet.Unicode)]
        public static extern int midiOutGetErrorText(int mmsyserr, StringBuilder errMsg, int capacity);
        [DllImport("winmm.dll")]
        public static extern int midiStreamClose(IntPtr handle);

        public delegate void MidiCallback(IntPtr handle, uint msg, IntPtr instance, IntPtr param1, IntPtr param2);

        private static readonly ManualResetEvent mre = new ManualResetEvent(false);

        private static void TestMidiCallback(IntPtr handle, uint msg, IntPtr instance, IntPtr param1, IntPtr param2)
        {
            Debug.WriteLine(msg.ToString());
            if (msg == MOM_DONE)
            {
                Debug.WriteLine("MOM_DONE");
                mre.Set();
            }
        }

        public const uint MOM_DONE = 0x3C9;
        public const int MMSYSERR_NOERROR = 0;
        public const int MAXERRORLENGTH = 256;
        public const uint CALLBACK_FUNCTION = 0x30000;
        public const uint MidiHeaderSize = 64;

        public static void CheckMidiOutMmsyserr(int mmsyserr)
        {
            if (mmsyserr != MMSYSERR_NOERROR)
            {
                var sb = new StringBuilder(MAXERRORLENGTH);
                var errorResult = midiOutGetErrorText(mmsyserr, sb, sb.Capacity);
                if (errorResult != MMSYSERR_NOERROR)
                {
                    throw new /*Midi*/Exception("An error occurred and there was another error while attempting to retrieve the error message."/*, mmsyserr*/);
                }
                throw new /*Midi*/Exception(sb.ToString()/*, mmsyserr*/);
            }
        }

        static void Main(string[] args)
        {
            IntPtr handle;
            uint deviceId = 0;
            CheckMidiOutMmsyserr(midiStreamOpen(out handle, ref deviceId, 1, TestMidiCallback, IntPtr.Zero, CALLBACK_FUNCTION));
            try
            {
                var bytes = new byte[24];
                IntPtr buffer = Marshal.AllocHGlobal(bytes.Length);

                try
                {
                    MidiHeader header = new MidiHeader();
                    header.Data = buffer;
                    header.BufferLength = 24;
                    header.BytesRecorded = 24;
                    header.UserData = IntPtr.Zero;
                    header.Flags = 0;
                    header.Next = IntPtr.Zero;
                    header.Reserved = IntPtr.Zero;
                    header.Offset = 0;
#warning uncomment if using array
                    //header.Reserved2 = new IntPtr[8];

                    // flags and event code
                    bytes[8] = 0x90;
                    bytes[9] = 63;
                    bytes[10] = 0x55;
                    bytes[11] = 0;
                    // next event
                    bytes[12] = 96;
                    bytes[20] = 0x80;
                    bytes[21] = 63;
                    bytes[22] = 0x55;
                    bytes[23] = 0;
                    Marshal.Copy(bytes, 0, buffer, bytes.Length);

                    CheckMidiOutMmsyserr(midiStreamRestart(handle));
                    CheckMidiOutMmsyserr(midiOutPrepareHeader(handle, ref header, MidiHeaderSize));
                    CheckMidiOutMmsyserr(midiStreamOut(handle, ref header, MidiHeaderSize));
                    mre.WaitOne();
                    CheckMidiOutMmsyserr(midiOutUnprepareHeader(handle, ref header, MidiHeaderSize));
                }
                finally
                {
                    Marshal.FreeHGlobal(buffer);
                }
            }
            finally
            {
                midiStreamClose(handle);
            }
        }
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct MidiHeader
    {
        public IntPtr Data;
        public uint BufferLength;
        public uint BytesRecorded;
        public IntPtr UserData;
        public uint Flags;
        public IntPtr Next;
        public IntPtr Reserved;
        public uint Offset;
#if false
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
        public IntPtr[] Reserved2;
#else
        public IntPtr Reserved0;
        public IntPtr Reserved1;
        public IntPtr Reserved2;
        public IntPtr Reserved3;
        public IntPtr Reserved4;
        public IntPtr Reserved5;
        public IntPtr Reserved6;
        public IntPtr Reserved7;
#endif
    }
}

最佳答案

来自midiOutPrepareHeader的文档:

Before you pass a MIDI data block to a device driver, you must prepare the buffer by passing it to the midiOutPrepareHeader function. After the header has been prepared, do not modify the buffer. After the driver is done using the buffer, call the midiOutUnprepareHeader function.

您没有遵守此规定。编码器创建一个临时的 native 版本的结构,它在调用 midiOutPrepareHeader 期间存在。一旦 midiOutPrepareHeader 返回,临时 native 结构将被销毁。但是 MIDI 代码仍然有对它的引用。这就是关键点,MIDI 代码包含对您的结构的引用并且需要能够访问它。

具有单独写入字段的版本可以工作,因为该结构是 blittable 的。因此 p/invoke 编码器通过固定与 native 结构二进制兼容的托管结构来优化调用。在您调用 midiOutUnprepareHeader 之前,GC 仍有机会重新定位该结构,但您似乎还没有被它捕获。如果您坚持使用 bittable 结构,您需要固定它直到调用 midiOutUnprepareHeader

因此,底线是您需要提供一个结构,该结构在您调用 midiOutUnprepareHeader 之前一直有效。个人建议使用Marshal.AllocHGlobalMarshal.StructureToPtr,然后Marshal.FreeHGlobal一次midiOutUnprepareHeader返回。并且显然将参数从 ref MidiHeader 切换为 IntPtr

我认为我不需要向您展示任何代码,因为从您的问题中可以清楚地看出您知道如何执行这些操作。事实上,我提出的解决方案是您已经尝试并观察到的解决方案。但现在你知道为什么了!

关于c# - 在结构中编码(marshal) IntPtr[] 会导致 midiStream 函数出现错误,但将数组展开到一堆字段是可行的,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28925449/

有关c# - 在结构中编码(marshal) IntPtr[] 会导致 midiStream 函数出现错误,但将数组展开到一堆字段是可行的的更多相关文章

  1. ruby-on-rails - Rails 常用字符串(用于通知和错误信息等) - 2

    大约一年前,我决定确保每个包含非唯一文本的Flash通知都将从模块中的方法中获取文本。我这样做的最初原因是为了避免一遍又一遍地输入相同的字符串。如果我想更改措辞,我可以在一个地方轻松完成,而且一遍又一遍地重复同一件事而出现拼写错误的可能性也会降低。我最终得到的是这样的:moduleMessagesdefformat_error_messages(errors)errors.map{|attribute,message|"Error:#{attribute.to_s.titleize}#{message}."}enddeferror_message_could_not_find(obje

  2. ruby-on-rails - 迷你测试错误 : "NameError: uninitialized constant" - 2

    我遵循MichaelHartl的“RubyonRails教程:学习Web开发”,并创建了检查用户名和电子邮件长度有效性的测试(名称最多50个字符,电子邮件最多255个字符)。test/helpers/application_helper_test.rb的内容是:require'test_helper'classApplicationHelperTest在运行bundleexecraketest时,所有测试都通过了,但我看到以下消息在最后被标记为错误:ERROR["test_full_title_helper",ApplicationHelperTest,1.820016791]test

  3. ruby-on-rails - 如何在 Rails View 上显示错误消息? - 2

    我是rails的新手,想在form字段上应用验证。myviewsnew.html.erb.....模拟.rbclassSimulation{:in=>1..25,:message=>'Therowmustbebetween1and25'}end模拟Controller.rbclassSimulationsController我想检查模型类中row字段的整数范围,如果不在范围内则返回错误信息。我可以检查上面代码的范围,但无法返回错误消息提前致谢 最佳答案 关键是您使用的是模型表单,一种显示ActiveRecord模型实例属性的表单。c

  4. 使用 ACL 调用 upload_file 时出现 Ruby S3 "Access Denied"错误 - 2

    我正在尝试编写一个将文件上传到AWS并公开该文件的Ruby脚本。我做了以下事情:s3=Aws::S3::Resource.new(credentials:Aws::Credentials.new(KEY,SECRET),region:'us-west-2')obj=s3.bucket('stg-db').object('key')obj.upload_file(filename)这似乎工作正常,除了该文件不是公开可用的,而且我无法获得它的公共(public)URL。但是当我登录到S3时,我可以正常查看我的文件。为了使其公开可用,我将最后一行更改为obj.upload_file(file

  5. ruby-on-rails - 错误 : Error installing pg: ERROR: Failed to build gem native extension - 2

    我克隆了一个rails仓库,我现在正尝试捆绑安装背景:OSXElCapitanruby2.2.3p173(2015-08-18修订版51636)[x86_64-darwin15]rails-v在您的Gemfile中列出的或native可用的任何gem源中找不到gem'pg(>=0)ruby​​'。运行bundleinstall以安装缺少的gem。bundleinstallFetchinggemmetadatafromhttps://rubygems.org/............Fetchingversionmetadatafromhttps://rubygems.org/...Fe

  6. ruby - #之间? Cooper 的 *Beginning Ruby* 中的错误或异常 - 2

    在Cooper的书BeginningRuby中,第166页有一个我无法重现的示例。classSongincludeComparableattr_accessor:lengthdef(other)@lengthother.lengthenddefinitialize(song_name,length)@song_name=song_name@length=lengthendenda=Song.new('Rockaroundtheclock',143)b=Song.new('BohemianRhapsody',544)c=Song.new('MinuteWaltz',60)a.betwee

  7. ruby-on-rails - 每次我尝试部署时,我都会得到 - (gcloud.preview.app.deploy) 错误响应 : [4] DEADLINE_EXCEEDED - 2

    我是Google云的新手,我正在尝试对其进行首次部署。我的第一个部署是RubyonRails项目。我基本上是在关注thisguideinthegoogleclouddocumentation.唯一的区别是我使用的是我自己的项目,而不是他们提供的“helloworld”项目。这是我的app.yaml文件runtime:customvm:trueentrypoint:bundleexecrackup-p8080-Eproductionconfig.ruresources:cpu:0.5memory_gb:1.3disk_size_gb:10当我转到我的项目目录并运行gcloudprevie

  8. ruby-on-rails - Rails 5 Active Record 记录无效错误 - 2

    我有两个Rails模型,即Invoice和Invoice_details。一个Invoice_details属于Invoice,一个Invoice有多个Invoice_details。我无法使用accepts_nested_attributes_forinInvoice通过Invoice模型保存Invoice_details。我收到以下错误:(0.2ms)BEGIN(0.2ms)ROLLBACKCompleted422UnprocessableEntityin25ms(ActiveRecord:4.0ms)ActiveRecord::RecordInvalid(Validationfa

  9. c# - 如何在 ruby​​ 中调用 C# dll? - 2

    如何在ruby​​中调用C#dll? 最佳答案 我能想到几种可能性:为您的DLL编写(或找人编写)一个COM包装器,如果它还没有,则使用Ruby的WIN32OLE库来调用它;看看RubyCLR,其中一位作者是JohnLam,他继续在Microsoft从事IronRuby方面的工作。(估计不会再维护了,可能不支持.Net2.0以上的版本);正如其他地方已经提到的,看看使用IronRuby,如果这是您的技术选择。有一个主题是here.请注意,最后一篇文章实际上来自JohnLam(看起来像是2009年3月),他似乎很自在地断言RubyCL

  10. arrays - 这是 Ruby 中 Array.fill 方法的错误吗? - 2

    这个问题在这里已经有了答案:Arraysmisbehaving(1个回答)关闭6年前。是否应该这样,即我误解了,还是错误?a=Array.new(3,Array.new(3))a[1].fill('g')=>[["g","g","g"],["g","g","g"],["g","g","g"]]它不应该导致:=>[[nil,nil,nil],["g","g","g"],[nil,nil,nil]]

随机推荐