C++/CLI 菜鸟:System.AccessViolationException [英] C++/CLI noob: System.AccessViolationException

查看:25
本文介绍了C++/CLI 菜鸟:System.AccessViolationException的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试为我女儿的职业日制作一个很酷的演示".在 5 天内高,所以我正在尝试使用 echoprint 库 来无线执行(OTA) 音频识别.我从来没有比 C++ 中的hello world"走得更远,我正在尝试使用 C++/CLI 来包装 echoprint 代码生成库,以便我可以从 C# 调用它.这是我的头文件:

I'm trying to put together a "cool demo" for a career day at my daughter's jr. high in 5 days and so I'm trying to use the echoprint library to perform over the air (OTA) audio recognition. I've never really gone much farther than "hello world" in C++ and I am trying to use C++/CLI to wrap the echoprint codegen library so I can call it from C#. Here's my header file:

// echoprint-cli.h

#pragma once

#include "Codegen.h";

using namespace System;

namespace echoprintcli {

    public ref class CodegenCLI
    {
    public:
        String^ getCodeString(array<float>^ buffer, unsigned int samples, int start_offset);
    };
}

这是我的实现:

#include "stdafx.h"
#include <msclrmarshal_cppstd.h>
#include "echoprint-cli.h"

using namespace System;
using namespace System::Runtime::InteropServices;
using namespace msclr::interop;

namespace echoprintcli {
    String^ CodegenCLI::getCodeString(array<float>^ buffer, unsigned int samples, int start_offset){
        String^ result = String::Empty;

        if(buffer->Length > 0){
            GCHandle h = GCHandle::Alloc(buffer, System::Runtime::InteropServices::GCHandleType::Pinned);

            try{
                float* pcm = (float*)(void*)h.AddrOfPinnedObject();
                Codegen* codegen = new Codegen(pcm, samples, start_offset); //System.AccessViolationException here
                std::string code;
                try{
                    code = codegen->getCodeString();
                }finally{
                    delete codegen;
                }
                result = marshal_as<String^>(code);
            }
            finally{
                h.Free();
            }
        }
        return result;
    }
}

我正在使用 XNA 麦克风类来录制音频.它返回一个字节 [] 数组,因此我将字节转换为浮点数,然后通过我的包装器将其传递给 Codegen 类,如下所示 (C#):

I'm using the XNA Microphone class to record the audio. It returns an array of byte[], so I'm converting the bytes to float and then passing it thru my wrapper to the Codegen class like this (C#):

        var mic = Microphone.Default;
        Log(String.Format("Using '{0}' as audio input...", mic.Name));
        var buffer = new byte[mic.GetSampleSizeInBytes(TimeSpan.FromSeconds(22))];

        int bytesRead = 0;
        string fileName = String.Empty;

        try
        {
            mic.Start();
            try
            {
                Log(String.Format("{0:HH:mm:ss} Start recording audio stream...", DateTime.Now));
                while (bytesRead < buffer.Length)
                {
                    Thread.Sleep(1000);
                    var bytes = mic.GetData(buffer, bytesRead, (buffer.Length - bytesRead));
                    Log(String.Format("{0:HH:mm:ss} Saving {1} bytes to stream...", DateTime.Now, bytes));
                    bytesRead += bytes;
                }
                Log(String.Format("{0:HH:mm:ss} Finished recording audio stream...", DateTime.Now));
            }
            finally
            {
                mic.Stop();
            }

            Func<byte, float> convert = (b) => System.Convert.ToSingle(b);
            var converter = new Converter<byte, float>(convert);
            float[] pcm = Array.ConvertAll<byte, float>(buffer, converter);

            Log(String.Format("{0:HH:mm:ss} Generating audio fingerprint...", DateTime.Now));
            var codeg = new CodegenCLI();
            String code = codeg.getCodeString(pcm, (uint)pcm.Length, 0);

但是当我的 C++/CLI 方法 (getCodeString) 调用本机方法时,我得到 Sysetem.AccessViolationException.

But when my C++/CLI method (getCodeString) calls into the native method I get Sysetem.AccessViolationException.

整个源代码可作为 VS 2010 SP1 或 VS 11 解决方案在 github 上获得:

The entire source code is available as a VS 2010 SP1 or VS 11 solution on github: https://github.com/developmentalmadness/echoprint-net/tree/3c48d3783136188bfa213d3e9fd1ebea0f151bed

该 URL 应指向当前遇到问题的修订版.

That URL should point to the revision that's currently experiencing the problem.

编辑我在这里尝试了建议:AccessViolation, when call C++-DLL from C++/命令行

#include "stdafx.h"
#include <msclrmarshal_cppstd.h>
#include "echoprint-cli.h"

using namespace System;
using namespace System::Runtime::InteropServices;
using namespace msclr::interop;

namespace echoprintcli {
    String^ CodegenCLI::getCodeString(array<float>^ buffer, unsigned int samples, int start_offset){
        String^ result = String::Empty;

        IntPtr p = Marshal::AllocHGlobal(buffer->Length * sizeof(float));
        try{
            pin_ptr<float> pcm = static_cast<float*>(p.ToPointer());
            Codegen* codegen = new Codegen(pcm, samples, start_offset); // System.AccessViolationException here
            std::string code;
            try{
                code = codegen->getCodeString();
            }finally{
                delete codegen;
            }
            result = marshal_as<String^>(code);
        }
        finally{
            Marshal::FreeHGlobal(p);
        }
        return result;
    }
}

而且我仍然遇到访问冲突,但在崩溃后调试器让我进入本机代码(我不知道如何自己到达那里).它会在 ctor 内部爆炸.指针 (pcm) 的地址值为 0.0000000,但除了在此处显示源代码之外,我不知道如何自己调试代码:

And I still get the access violation, but after crashing the debugger dropped me into the native code (I have no idea how to get there myself). And it bombs inside the ctor. The pointer (pcm) has an address an a value of 0.0000000 but I can't figure out how to debug into the code myself other than to show the source here:

    Codegen::Codegen(const float* pcm, unsigned int numSamples, int start_offset) {
    if (Params::AudioStreamInput::MaxSamples < (uint)numSamples)
        throw std::runtime_error("File was too big
");

    Whitening *pWhitening = new Whitening(pcm, numSamples); //System.AccessViolationException

无法调试,我只能假设遵循堆栈两个步骤:

Without being able to debug, I can only assume to follow down the stack two steps:

    Whitening::Whitening(const float* pSamples, uint numSamples) :
    _pSamples(pSamples), _NumSamples(numSamples) {
    Init();
}

我想象它在某个地方的 Init() 方法中爆炸:

And I imagine it bombs in the Init() method somewhere:

    void Whitening::Init() {
    int i;
    _p = 40;

    _R = (float *)malloc((_p+1)*sizeof(float));
    for (i = 0; i <= _p; ++i)  { _R[i] = 0.0; }
    _R[0] = 0.001;

    _Xo = (float *)malloc((_p+1)*sizeof(float));
    for (i = 0; i < _p; ++i)  { _Xo[i] = 0.0; }

    _ai = (float *)malloc((_p+1)*sizeof(float));
    _whitened = (float*) malloc(sizeof(float)*_NumSamples);
}

推荐答案

正如在 EchoNest 论坛上承诺的那样,这是我的做法.如果您修改 codegen.dll 并提供合适的导出函数,您可以更轻松地使用 CLI.

As promised on the EchoNest forum, here is my way of doing it. You can have it easier and go without CLI if you modify codegen.dll and provide a suitable exported function.

到codegen中的main.cxx,添加如下方法:

To main.cxx in codegen, add the following method:

extern "C" __declspec(dllexport) void GetCodeStringFromPcm(const float* pcm, uint numSamples, int start_offset, BSTR* sResultString)
{
  // pcm: a buffer of floats, mono, 11025 Hz
  Codegen * pCodegen = new Codegen(pcm, numSamples, start_offset);
  string code = pCodegen->getCodeString();

  // http://stackoverflow.com/questions/2573834/c-convert-string-or-char-to-wstring-or-wchar-t
  std::wstring ws(code.size(), L' '); // Overestimate number of code points.
  ws.resize(mbstowcs(&ws[0], code.c_str(), code.size())); // Shrink to fit.

  *sResultString = SysAllocStringLen(ws.data(), ws.size());
}

现在在 C# 方面,您可以简单地执行以下操作:

Now on the C# side, you can simply do this:

/// <summary>
/// Generates audio fringerprint for usage with Echonest.
/// </summary>
/// <param name="pcm">const float*, 4 byte per float in C++</param>
[DllImport("codegen.dll")]
private static extern void GetCodeStringFromPcm(float[] pcm, uint numSamples, int start_offset, [MarshalAs(UnmanagedType.BStr)] ref string sResultString);

现在您只需要这个特殊的浮点数缓冲区作为第一个参数.您提到已经有了一个,但作为对每个拥有另一种格式音频数据的人的奖励,下面是一种将几乎所有音频文件转换为正确的浮点数缓冲区的方法.要求是 BASS.NET 音频库:

Now you only need this special buffer of floats for the first parameter. You mention already having one, but as a bonus for everyone who has audio data of another format, below is a method for converting pretty much any audio file into the correct buffer of floats. Requirement is the BASS.NET audio library:

using BassLib = Un4seen.Bass.Bass;
using BassMix = Un4seen.Bass.AddOn.Mix.BassMix;

/// <summary>
/// Creates a fingerprint code for an audio track, using the codegen.dll.
/// </summary>
public string GetCodeStringFromFile(string fileName)
{
    // Read input stream
    int streamIn = BassLib.BASS_StreamCreateFile(fileName, 0, 0, Un4seen.Bass.BASSFlag.BASS_STREAM_DECODE);
    if (streamIn == 0) return null;

    // New mixer stream that allows us to read floating point samples. EchoNest requires mono data.
    int mixerStream = BassMix.BASS_Mixer_StreamCreate(targetSampleRate, 1, Un4seen.Bass.BASSFlag.BASS_STREAM_DECODE | Un4seen.Bass.BASSFlag.BASS_SAMPLE_FLOAT);
    BassMix.BASS_Mixer_StreamAddChannel(mixerStream, streamIn, Un4seen.Bass.BASSFlag.BASS_STREAM_DECODE | Un4seen.Bass.BASSFlag.BASS_MIXER_DOWNMIX);

    long bufferSizeInBytes = BassLib.BASS_ChannelSeconds2Bytes(mixerStream, 0.1f);
    double totalSeconds = BassLib.BASS_ChannelBytes2Seconds(streamIn, BassLib.BASS_ChannelGetLength(streamIn));

    // Use progress data in whatever way you need it.
    int progress = 0;
    List<float> resultData = new List<float>();

    while (true)
    {
        float[] data = new float[bufferSizeInBytes / 4];
        int readBytes = BassLib.BASS_ChannelGetData(mixerStream, data, (int)bufferSizeInBytes);
        if (readBytes <= 0) break;

        for (int i = 0; i < readBytes / 4; i++)
        {
            resultData.Add(data[i]);
        }

        double secondsPos = BassLib.BASS_ChannelBytes2Seconds(mixerStream, BassLib.BASS_ChannelGetPosition(mixerStream));
        progress = (int)(secondsPos / totalSeconds * 100);
    }

    BassLib.BASS_StreamFree(streamIn);
    BassLib.BASS_StreamFree(mixerStream);

    // We need to pass an array of samples to C.
    float[] resultArray = resultData.ToArray();

    // Clear list to prevent occupying too much memory.
    resultData.Clear();

    // Marshaller will pass float[] just fine to C.
    string resultCodegenData = string.Empty;
    GetCodeStringFromPcm(resultArray, (uint)resultArray.Length, 0, ref resultCodegenData);

    return resultCodegenData;
}

这篇关于C++/CLI 菜鸟:System.AccessViolationException的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆