.NET应用程序调用C dll。 C代码为char数组分配内存并返回此数组作为结果。 .NET应用程序将此结果作为字符串获取。
C代码:
extern "C" __declspec(dllexport) char* __cdecl Run()
{
char* result = (char*)malloc(100 * sizeof(char));
// fill the array with data
return result;
}
C#代码:
[DllImport("Unmanaged.dll")]
private static extern string Run();
...
string result = Run();
// do something useful with the result and than leave it out of scope
它的一些测试表明垃圾收集器没有释放C代码分配的内存。
任何帮助将不胜感激。 :)
答案 0 :(得分:7)
托管字符串与char *不同。底层发生的事情是,互操作层中的封送代码会生成非托管字符串的副本,以便将其转换为托管字符串,但它无法释放该内存,因为它不知道它是如何分配的。
但是,您可以尝试分配并返回BSTR而不是char *。与传统的非托管数据类型相比,互操作层可以更好地处理自动化数据类型。
重要的原因是char *和BSTR在内存中的分配方式。
char *缓冲区使用CLR一无所知的私有分配/解除分配例程在C ++运行时的堆上分配,因此无法删除该内存。更糟糕的是,char *指向的缓冲区可以由dll代码的内部堆实现分配,或者甚至可以指向私有类中的成员变量。
另一方面,BSTR使用WIndows API SysAllocString进行分配,并由SyFreeStirng释放,并且由于CLR互操作层知道这些Windows API,因此它知道如何从非托管代码中释放BSTR。
答案 1 :(得分:7)
P / Invoke marshaller将假设返回类型的内存是使用CoTaskMemAlloc()分配的,并将调用CoTaskMemFree()来释放它。如果没有这样做,程序将失败,Vista和Win7上有例外,但在XP上默默泄漏内存。使用SysAllocString()可以使用,但您必须在[DllImport]属性中注释返回类型。如果不对Win7进行诊断,不这样做仍会导致泄漏。 BSTR 不是指向由CoTaskMemAlloc分配的内存块的指针,指向地址前面有4个字节存储字符串大小。
以下任一组合都可以正常使用:
extern "C" __declspec(dllexport)
BSTR __stdcall ReturnsAString() {
return SysAllocString(L"Hello world");
}
[DllImport(@"c:\projects\cpptemp1\debug\cpptemp1.dll")]
[return: MarshalAs(UnmanagedType.BStr)] // NOTE: required!
private static extern string ReturnsAString();
或者:
extern "C" __declspec(dllexport)
const wchar_t* __stdcall ReturnsAString() {
const wchar_t* str = L"Hello world";
wchar_t* retval = (wchar_t*)CoTaskMemAlloc((wcslen(str)+1) * sizeof(wchar_t));
wcscpy(retval, str);
return retval;
}
[DllImport(@"c:\projects\cpptemp1\debug\cpptemp1.dll", CharSet=CharSet.Auto)]
private static extern string ReturnsAString();
您应该考虑允许客户端代码传递缓冲区,以免出现内存管理问题。这看起来应该与此相似:
extern "C" __declspec(dllexport)
void __stdcall ReturnsAString(wchar_t* buffer, size_t buflen) {
wcscpy_s(buffer, buflen, L"Hello world");
}
[DllImport(@"c:\projects\cpptemp1\debug\cpptemp1.dll", CharSet=CharSet.Auto)]
private static extern void ReturnsAString(StringBuilder buffer, int buflen);
...
StringBuilder sb = new StringBuilder(256);
ReturnsAString(sb, sb.Capacity);
string s = sb.ToString();
答案 2 :(得分:6)
您无法从托管代码中释放非托管内存。您需要在C中编写一个例程,该例程在free
函数返回的指针上调用Run
并从.NET调用它。
另一个选择是在.NET中分配非托管内存,将指针传递给C函数,该函数将用数据填充它,最后释放这个指针:
IntPtr ptr = Marshal.AllocHGlobal(100 * sizeof(char));
SomeUnmanagedFunc(ptr);
Marshal.FreeHGlobal(ptr);
答案 3 :(得分:3)
另一种方法是通过P / Invoke传递托管字符串(StringBuilder实例)(作为Run
函数的参数)。
这样就不会在非管理方面进行分配。
换句话说,你会有类似的东西:
extern "C" __declspec(dllexport) void __cdecl Run(char* data)
{
// fill the array with data
// no return value (void)
}
并将其称为:
[DllImport("Unmanaged.dll", CharSet = CharSet.Ansi)]
static extern void Run(StringBuilder result);
StringBuilder result = new StringBuilder(100);
Run(result);
答案 4 :(得分:2)
我正在阅读有关PInvoke的一些问题,我在这里停留。我不知道问题是否仍然与您有关,但我决定将我的答案发布给未来的读者。
这是你对Darin Dimitrov的回答的最后评论。当分配的内存大小未知时,典型的解决方案是使用空指针调用非托管函数并在out参数中接收大小。然后,我们分配所需的空间并再次调用非托管函数。
以下示例:
//MANAGED SIDE
IntPtr ptr = IntPtr.Zero;
int size = 0;
myFunc(ptr, out size);
ptr = Marshal.AllocHGlobal(size);
myFunc(ptr, out size);
//Do your work..
Marshal.FreeHGlobal(ptr);
//UNMANEGED SIDE
int myFunc(void* dest, size_t& size){
if(dest==NULL)
//calculate de size..
size = 'resul'
return 0;
}
// create the array and copy all elements
memcopy(dest, ... , size);
//free all allocated space in unmanaged and return success
return 0;
}
答案 5 :(得分:0)
public class Memory
{
[DllImport("kernel32.dll")]
private static extern bool SetProcessWorkingSetSize(IntPtr proc, int min, int max);
//[DllImport("kernel64.dll")]
//private static extern bool SetProcessWorkingSetSize(IntPtr proc, int min, int max);
public String Errores = "";
public void Limpiar()
{
try
{
Process Mem;
Mem = Process.GetCurrentProcess();
SetProcessWorkingSetSize(Mem.Handle, -1, -1);
Mem = null;
}
catch (Exception ex)
{
Errores = ex.ToString() + " " + ex.StackTrace.ToString();
}
}
}
public class LimpiadodeMemoria
{
private Boolean Monitorear;
private Boolean Salida;
private String ElMensajeBitacoras;
private String Error;
public delegate void Errores(string Elerror);
public event Errores OnErrores;
public delegate void Bitacora(string LaBitacora);
public event Bitacora OnBitacora;
public void Iniciar()
{
Monitorear = true;
Salida = false;
ElMensajeBitacoras = "LimpiadodeMemoria - " + DateTime.Now.ToString("dd/MM/yyyy HH:mm:ss") + " Iniciada";
OnBitacora(ElMensajeBitacoras);
while (Monitorear == true)
{
Salida = false;
Memory _Memori = new Memory();
_Memori.Limpiar();
Error = _Memori.Errores;
_Memori = null;
if (Error != "")
{
OnErrores(Error);
}
Salida = true;
System.Threading.Thread.Sleep(1000);
}
}
public void Detener()
{
Monitorear = false;
while (Salida == false)
{
System.Threading.Thread.Sleep(100);
}
ElMensajeBitacoras = "LimpiadodeMemoria - " + DateTime.Now.ToString("dd/MM/yyyy HH:mm:ss") + " Detenida";
OnBitacora(ElMensajeBitacoras);
}
}
答案 6 :(得分:-1)
.NET内存必须在CLR中分配,以便GC清除。您需要添加一个函数来释放C DLL中的块。
请记住在创建内存的C DLL的同一实例中释放内存。你不能混搭。