1

我目前正在做一个截止日期很短的项目,所以我没有太多时间来了解所有内容。另外,我不是C++ 开发和内存管理方面的专家。

所以,我想做的是用 C 和 C++ 代码创建一个 DLL。然后,我想在 C# 代码中调用这个 DLL。目前,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++ 公开了两种方法:StartRecognition()启动操作以从麦克风获取一些数据,然后处理它们并存储结果。GetResults()返回先前存储的结果的实例。允许在WrapperCallback()Result 能够处理时调用 C# 部分。C# 部分,当 Callback 被调用时,将要求使用该GetResults()方法获取结果。

我知道架构在这个演示中可能看起来真的不合适,但我不想解释整个项目来验证模型,请确保一切都是正确的。

最后,问题在于 C# 回调何时调用该GetResults()方法。尝试resultsForCS从 C# 访问似乎是不可能的。

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 marshaller 必须做一些事情来释放为字符串分配的内存。约定是调用者需要释放的内存分配必须从 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 回答
4

看着:

at Microsoft.Win32.Win32Native.CoTaskMemFree(IntPtr ptr)
at System.StubHelpers.CSTRMarshaler.ClearNative(IntPtr pNative)
at NMSPRecognitionWrapper.Program.GetResultsExt()

我可以看到您的回调被调用,但运行时尝试释放一些内存。我认为它假设您的指针将指向 com 内存。尝试自己转换字符串,很简单!

[DllImport("NMSPRecognitionLib.dll", EntryPoint = "GetResults")]
public static extern IntPtr GetResultsExt();

[...]

string result = Marshal.PtrToStringAnsi(GetResultsExt())

没有 100% 的保证,但值得一试。

于 2013-05-23T14:16:05.650 回答
-1

我发现在C++/CLI中围绕 C++ 本机代码编写包装器通常更容易。C++/CLI 类可以直接调用和使用本机 C++,但可以从 C#(和任何 .Net 语言)访问。根据我的经验,使用 DLLImport 会导致难以调试和查找错误。

于 2013-05-23T14:15:59.597 回答