我目前正在开展一个截止日期很短的项目,所以我没有太多时间去理解一切。另外,我不是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()
允许在结果能够处理时调用C#部分。调用Callback时,C#部分将要求使用GetResults()
方法获取结果。
我知道这个架构在这个演示文稿中可能看起来不合适,但我不想解释整个项目来验证模型,请确保一切正确。
要完成,问题是C#回调调用GetResults()
方法。从C#开始尝试访问resultsForCS
似乎是不可能的。
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();
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 *
,我无法更改此内容!
答案 0 :(得分: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。从本机代码中难以正确使用的代码也难以解决。这是一个设计缺陷,总是允许调用者传递一个缓冲区来填充,所以永远不会有谁拥有内存的问题。
答案 1 :(得分: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%保证,但值得一试。
答案 2 :(得分:-1)
我发现围绕C ++本机代码在C++/CLI中编写包装器通常更容易。 C ++ / CLI类可以直接调用和使用本机C ++,但可以从C#(和任何.Net语言)访问。根据我的经验,使用DLLImport会导致难以调试并发现错误。