1

私は現在、締め切りが非常に短いプロジェクトに取り組んでいるので、すべてを理解する時間があまりありません。また、私はC++ 開発とメモリ管理の専門家ではありません。

だから、私がやろうとしているのは、C と C++ の両方のコードで DLL を作成することです。次に、この DLL を C# コードで呼び出したいと思います。現在、C++ と C# 間の通信は問題ありません。DLL から C# コードに文字列を転送しようとすると、問題が発生します。エラーはこれです:

System.AccessViolationException: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
   at Microsoft.Win32.Win32Native.CoTaskMemFree(IntPtr ptr)
   at System.StubHelpers.CSTRMarshaler.ClearNative(IntPtr pNative)
   at NMSPRecognitionWrapper.Program.GetResultsExt()
   at NMSPRecognitionWrapper.Program.<Main>b__0() in <my dir>\Program.cs:line 54
   at NMSPRecognitionWrapper.Program.StartRecognitionExt()
   at NMSPRecognitionWrapper.Program.Main(String[] args) in <my dir>\Program.cs:line 60

また、以下にいくつかのコードを示します (非常に単純化されています!)。実際、C++ は 2 つのメソッドを公開しています。StartRecognition()操作を起動してマイクからデータを取得し、それらを処理して結果を保存します。GetResults()以前に保存された結果のインスタンスを返します。これWrapperCallback()により、Result が処理可能になったときに C# 部分を呼び出すことができます。Callback が呼び出されると、C# 部分はGetResults()メソッドを使用して結果を取得するように要求します。

このプレゼンテーションではアーキテクチャが不適切に見えるかもしれませんが、モデルを検証するためにプロジェクト全体を説明するつもりはありません。すべてが正しいことを確認してください。

最後に、問題は C# コールバックがGetResults()メソッドを呼び出すときです。C#からアクセスしようとしてresultsForCSも無理のようです。

C++ 部分 - ヘッダー

// NMSPRecognitionLib.h

#pragma once
#include <iostream>

using namespace std;

extern "C" __declspec(dllexport) char* GetResults();
extern "C" static void DoWork();
extern "C" __declspec(dllexport) void StartRecognition();

C++ 部分 - ソース

#include "stdafx.h"
#include "NMSPRecognitionLib.h"

static char * resultsForCS;

static SUCCESS ProcessResult(NMSPCONNECTION_OBJECTS *pNmspConnectionObjects, LH_OBJECT hResult)
{
    [...]
    char* szResult;
    [...]

    resultsForCS = szResult;

    DoWork();

    [...]
    return Success;

    error:
        return Failure;
} /* End of ProcessResult */


extern "C" __declspec(dllexport) char* GetResults()
{
    return resultsForCS;
}

extern "C"
{
    typedef void (*callback_function)();
    callback_function gCBF;

    __declspec(dllexport) void WrapperCallback(callback_function callback) {
        gCBF = callback;
    }

    static void DoWork() {
        gCBF();
    }
}

extern "C" __declspec(dllexport) void StartRecognition()
{
    char* argv[] = { "path", "params" };
    entryPoint(2, argv);
}

C#部分

class Program
{
    [DllImport("NMSPRecognitionLib.dll", EntryPoint = "GetResults")]
    [return: MarshalAs(UnmanagedType.LPStr)]
    public static extern string GetResultsExt();

    public delegate void message_callback_delegate();

    [DllImport("NMSPRecognitionLib.dll", EntryPoint = "WrapperCallback")]
    public static extern void WrapperCallbackExt(message_callback_delegate callback);

    [DllImport("NMSPRecognitionLib.dll", EntryPoint = "StartRecognition")]
    public static extern void StartRecognitionExt();

    static void Main(string[] args)
    {
        WrapperCallbackExt(
            delegate()
            {
                Console.WriteLine(GetResultsExt());
            }
        );

        StartRecognitionExt();

        Console.WriteLine("\nPress any key to finish... ");
        var nothing = Console.ReadLine();
    }
}

結果を格納するためにポインターを使用しているために問題が発生することは理解していますが ( char *)、実際には別の方法でこれを行う方法がわかりません。szResultsタイプがchar *多すぎて、これを変更することはできません!

4

3 に答える 3

7

はい、戻り値の型が問題です。pinvoke マーシャラーは、文字列に割り当てられたメモリを解放するために何らかの操作を行う必要があります。契約では、呼び出し元が解放する必要があるメモリ割り当ては、COM ヒープから割り当てる必要があります。ネイティブ コードの CoTaskMemAlloc()。.NET でも Marshal.AllocCoTaskMem() として公開されます。

ほとんどのネイティブ コードは malloc() または ::operator new を使用して割り当て、C ランタイム ライブラリによって作成されたヒープから割り当てます。間違ったヒープ。したがって、必然的に CoTaskMemFree() 呼び出しは失敗します。Windows XP 以前ではサイレントに無視され、Vista 以降ではカブームです。

pinvoke マーシャラーがメモリを解放しようとするのを止める必要があります。これを行うには、戻り値を IntPtr として宣言します。Marshal.PtrToStringAnsi() を使用して文字列を復元します。

この関数を使用しようとするネイティブ コードを悩ませるような、大きな問題がまだ残っています。解放する必要がある文字列バッファがまだあります。C# からそれを行うことはできません。正しいバージョンの free() または ::operator delete をピンボークすることはできません。メモリリークは避けられません。期待できる唯一のことは、ネイティブ コードが何らかの形でそれを処理することです。そうでない場合は、C++/CLI を使用して相互運用する必要があります。同じ共有 CRT を使用するように、ネイティブ コードを同じコンパイラで再構築する必要があるという追加の要件があります。ネイティブ コードから正しく使用するのが難しいコードは、ピンボークするのも困難です。これは設計上の欠陥です。常に呼び出し元がバッファを渡して埋められるようにするので、誰がメモリを所有しているかという問題はありません。

于 2013-05-23T14:16:34.600 に答える