C++ DLL和C#代码之间的共享内存

mit*_*rop 1 c# c++

我目前正在开展一个截止日期很短的项目,所以我没有太多时间去理解一切.另外,我不是 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
Run Code Online (Sandbox Code Playgroud)

另外,我可以在下面给你一些代码(真的很简单!).实际上,C++公开了两种方法:StartRecognition()启动操作以从麦克风获取一些数据,然后处理它们并存储结果.GetResults()返回先前存储的结果的实例.在WrapperCallback()允许时,结果是能够进行处理,从而被称为C#一部分.调用Callback时,C#部分将要求使用该GetResults()方法获取结果.

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

要完成,问题是当C#回调调用该GetResults()方法时.尝试访问resultsForCSC#似乎是不可能的.

C++ part - header

// 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();
Run Code Online (Sandbox Code Playgroud)

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);
}
Run Code Online (Sandbox Code Playgroud)

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

我明白问题来了,因为我使用指针存储结果(char *),但我实际上不知道如何以另一种方式做到这一点.该szResults类型是char *太多,我不能改变这个!

Han*_*ant 5

是的,返回类型是问题.pinvoke marshaller必须做一些事情来释放为字符串分配的内存.合同是必须从COM堆分配需要由调用者释放的内存分配.本机代码中的CoTaskMemAlloc(),也在.NET中作为Marshal.AllocCoTaskMem()公开.

这很少结束,大多数本机代码使用malloc()或:: operator new进行分配,从C运行时库创建的堆中分配.错误的堆.所以CoTaskMemFree()调用不可避免地会失败.在Windows XP及更早版本中默默地忽略了Vista上的kaboom.

您必须停止pinvoke marshaller尝试释放内存.通过将返回值声明为IntPtr来执行此操作.并使用Marshal.PtrToStringAnsi()来恢复字符串.

你仍然有一个大问题,这个问题困扰任何试图使用这个函数的本机代码.您仍然需要释放一个字符串缓冲区.你不能从C#那里做到这一点,你不能用pinvoke正确版本的free()或:: operator delete.内存泄漏是不可避免的.你唯一能想到的就是本机代码以某种方式处理它.如果没有,那么您必须使用C++/CLI来互操作.还需要使用相同的编译器重建本机代码,以便它使用相同的共享CRT.从本机代码中难以正确使用的代码也很难说明.这是一个设计缺陷,总是允许调用者传递一个缓冲区来填充,所以从来没有一个问题谁拥有内存.