简体   繁体   English

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

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

I am currently working on a project with really short deadline, so I don't have much time to understand everything. 我目前正在一个期限很短的项目上工作,所以我没有太多时间来了解所有内容。 Also, I am not an expert in C++ development and memory management. 另外,我不是 C ++开发和内存管理方面的专家。

So, what I am trying to do is to create a DLL in with both C and C++ code. 所以,我想做的是用C和C ++代码创建一个DLL。 Then, I would like to call this DLL in a C# code. 然后,我想用C#代码调用此DLL。 Currently, the communication between C++ and C# is OK. 当前,C ++和C#之间的通信正常。 The problem comes up when I try to transfer a string from the DLL to the C# code. 当我尝试将字符串从DLL传输到C#代码时出现问题。 The error is this one : 错误是这个:

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

Also, I can give you some piece of code below (really simplified !). 另外,我可以在下面提供一些代码(确实简化了!)。 Actually, the C++ expose two methods : StartRecognition() launch operations to get some data from microphone, then process them and store the results. 实际上,C ++公开了两种方法: StartRecognition()启动操作以从麦克风获取一些数据,然后对其进行处理并存储结果。 GetResults() return an instance of the results previously stored. GetResults()返回先前存储的结果的实例。 The WrapperCallback() allows the C# part to be called when a Result is able for processing. WrapperCallback()允许在能够处理Result时调用C#部分。 The C# part, when the Callback is called, will ask to get the results using the GetResults() method. 当调用Callback时,C#部分将要求使用GetResults()方法获取结果。

I know the architecture may seem really inappropriate in this presentation, but I don't want to explain the whole project to validate the model, please be sure everything is correct. 我知道此演示文稿中的体系结构似乎确实不合适,但是我不想解释整个项目以验证模型,请确保一切正确。

To finish, the problem is when the C# callback call the GetResults() method. 最后,问题在于C#回调调用GetResults()方法时。 Trying to access to the resultsForCS seems to be impossible from the C#. 从C#中似乎无法访问resultsForCS

C++ part - header 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++ part - sources 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# part 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();
    }
}

I understand that the problem comes because I am using a pointer to store the results ( char * ), but I actually don't know how to do this in another way. 我知道出现问题是因为我正在使用指针来存储结果( char * ),但实际上我不知道如何以其他方式执行此操作。 The szResults type is char * too and I can't change this ! szResults类型也为char * ,我无法更改!

Yes, the return type is the problem. 是的,返回类型是问题。 The pinvoke marshaller must do something to release the memory that was allocated for the string. pinvoke编组人员必须执行某些操作才能释放为该字符串分配的内存。 The contract is that memory allocations that need to be released by the caller must be allocated from the COM heap. 合同规定,必须从COM堆中分配需要由调用方释放的内存分配。 CoTaskMemAlloc() in native code, also exposed in .NET as Marshal.AllocCoTaskMem(). CoTaskMemAlloc()以本机代码显示,在.NET中也显示为Marshal.AllocCoTaskMem()。

This rarely comes to a good end, most native code allocates with malloc() or ::operator new, allocating from a heap that's created by the C runtime library. 这很少有一个好的结局,大多数本机代码使用malloc()或:: operator new进行分配,并从C运行时库创建的堆中进行分配。 The wrong heap. 错误的堆。 So inevitably the CoTaskMemFree() call will fail. 因此,不可避免地,CoTaskMemFree()调用将失败。 Ignored silently in Windows XP and earlier, a kaboom on Vista and up. 在Windows XP和更早版本(在Vista及更高版本上为kaboom)中被无声忽略。

You must stop the pinvoke marshaller from trying to release the memory. 您必须停止尝试释放内存的pinvoke编组器。 Do so by declaring the return value as IntPtr. 为此,将返回值声明为IntPtr。 And use Marshal.PtrToStringAnsi() to recover the string. 并使用Marshal.PtrToStringAnsi()恢复字符串。

You still have a Big Problem, the kind of problem that bedevils any native code that tries to use this function as well. 您仍然有一个大问题,这种问题使尝试使用此功能的任何本机代码也陷入困境。 You still have a string buffer that needs to be released. 您仍然有一个需要释放的字符串缓冲区。 You cannot do that from C#, you can't pinvoke the correct version of free() or ::operator delete. 您无法从C#中执行此操作,也无法调用free()或:: operator delete的正确版本。 A memory leak is inevitable. 内存泄漏是不可避免的。 The only thing you can hope for is that the native code takes care of it, somehow. 您唯一希望的是,本机代码会以某种方式处理它。 If it doesn't then you must use C++/CLI to interop with it. 如果不是,则必须使用C ++ / CLI进行互操作。 With the additional requirement that the native code needs to be rebuilt with the same compiler so that it uses the same shared CRT. 另外还需要使用相同的编译器重建本机代码,以使其使用相同的共享CRT。 Code that's difficult to use correctly from native code is also hard to pinvoke. 很难从本地代码正确使用的代码也很难确定。 That's a design flaw, always allow the caller to pass a buffer to be filled in so there's never a question who owns the memory. 这是一个设计缺陷,始终允许调用者传递要填充的缓冲区,因此,谁拥有内存就没有问题了。

Looking at: 看着:

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

I can see that your callback is called, but the runtime tries to free some memory. 我可以看到调用了您的回调,但是运行时尝试释放一些内存。 I think it assumes your pointer would be to com memory. 我认为它假设您的指针将指向com内存。 Try converting the string yourself, it is easy! 尝试自己转换字符串,这很容易!

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

[...]

string result = Marshal.PtrToStringAnsi(GetResultsExt())

No 100% guarantee, but worth a try. 没有100%的保证,但值得一试。

I have found that it is usually easier to write a wrapper in C++/CLI around the C++ native code. 我发现使用C ++本机代码在C ++ / CLI中编写包装程序通常更容易。 A C++/CLI class can directly call and use native C++, but is accessible from C# (and any .Net language). C ++ / CLI类可以直接调用和使用本机C ++,但是可以从C#(和任何.Net语言)访问。 In my experience, using DLLImport as you do leads to hard to debug and find errors. 以我的经验,在使用DLLImport时会导致难以调试和发现错误。

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

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