繁体   English   中英

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

[英]Shared memory between C++ DLL and C# code

我目前正在一个期限很短的项目上工作,所以我没有太多时间来了解所有内容。 另外,我不是 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#部分。 当调用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 * ,我无法更改!

是的,返回类型是问题。 pinvoke编组人员必须执行某些操作才能释放为该字符串分配的内存。 合同规定,必须从COM堆中分配需要由调用方释放的内存分配。 CoTaskMemAlloc()以本机代码显示,在.NET中也显示为Marshal.AllocCoTaskMem()。

这很少有一个好的结局,大多数本机代码使用malloc()或:: operator new进行分配,并从C运行时库创建的堆中进行分配。 错误的堆。 因此,不可避免地,CoTaskMemFree()调用将失败。 在Windows XP和更早版本(在Vista及更高版本上为kaboom)中被无声忽略。

您必须停止尝试释放内存的pinvoke编组器。 为此,将返回值声明为IntPtr。 并使用Marshal.PtrToStringAnsi()恢复字符串。

您仍然有一个大问题,这种问题使尝试使用此功能的任何本机代码也陷入困境。 您仍然有一个需要释放的字符串缓冲区。 您无法从C#中执行此操作,也无法调用free()或:: operator delete的正确版本。 内存泄漏是不可避免的。 您唯一希望的是,本机代码会以某种方式处理它。 如果不是,则必须使用C ++ / CLI进行互操作。 另外还需要使用相同的编译器重建本机代码,以使其使用相同的共享CRT。 很难从本地代码正确使用的代码也很难确定。 这是一个设计缺陷,始终允许调用者传递要填充的缓冲区,因此,谁拥有内存就没有问题了。

看着:

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%的保证,但值得一试。

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM