2012-07-08 64 views
2

我目前正在探索C#中的DLL导出函数和P /调用。 我创建非常简单的.dll文件:从DLL调用方法时C#应用程序块

Test.h

#ifndef TEST_DLL_H 
#define TEST_DLL_H 
extern "C" __declspec(dllexport) const char * __cdecl hello(); 
extern "C" __declspec(dllexport) const char * __cdecl test();  
#endif // TEST_DLL_H 

Test.cpp的

#include <stdlib.h> 
#include "test.h" 
#include <string.h> 

const char* hello() 
{ 
    char *novi = (char *)malloc(51); 
    strcpy(novi, "Test."); 

    return novi; 
} 

const char * test() 
{ 
    return "Test."; 
} 

我编译它,并在C#项目中使用这样的:

[DllImport("test.dll", CallingConvention = CallingConvention.Cdecl)] 
    public static extern IntPtr hello(); 

    [DllImport("test.dll", CallingConvention = CallingConvention.Cdecl)] 
    public static extern string test(); 

    private void button1_Click(object sender, EventArgs e) 
    { 
     MessageBox.Show(test()); 
     IntPtr a = hello(); 
     MessageBox.Show(Marshal.PtrToStringAnsi(a)); 
    } 

但它不工作。 test()成功调用并返回正确的字符串。但hello()只是挂起程序。如果我从hello()定义中除去malloc行并返回常量,那么一切正常,所以我想现在我知道malloc存在一个问题。

此外,我已经看到,当返回类型是char *时不应该使用字符串。如果这是真的,为什么我们应该使用IntPtr?

+0

您是否尝试过调试它?为此,在C#项目属性Debug页面中,可以启用非托管代码调试。然后在C#项目中打开test.cpp,在hello中放置一个断点并从visual studio启动C#项目。 – 2012-07-09 10:39:34

回答

3

通过DLL边界返回字符串的函数很难从C或C++可靠地调用,但从C#执行时不会更好。问题在于调用者将如何释放字符串缓冲区。这需要完成hello()而不是test()。一些很难猜测的东西。 hello()函数需要使用free()函数,使用用于调用malloc()的相同分配器。这只有在DLL和调用者共享相同的CRT实现时才能工作。其中的几率很渺茫。

pinvoke编组器也释放字符串缓冲区,它必须。只有合理的选择CoTaskMemFree()才会这样做。它使用COM使用的默认分配器。这不会很好,你的C代码没有使用CoTaskMemAlloc()。这可能的结果取决于操作系统。在Vista及更高版本中,您的程序将会随着AccessViolation而死亡,这些Windows版本使用严格的堆分配器来设计用于防止行为不当的程序。在XP上,你会得到一些内存泄漏和堆损坏之间的东西,听起来像是第二种选择。

将返回值声明为IntPtr将会达到良好的效果。那么,你的程序不会崩溃,你仍然有一个内存泄漏,你不能插入。没有办法可靠地呼叫free()。或者在你的C代码中使用CoTaskMemAlloc(),这样pinvoke编组器的释放调用就可以工作。

但现实地说,就是不要这样写C代码。总是使用由调用者分配的内存,所以永远不会猜测谁拥有内存。这需要一个类似于此的功能签名:

extern "C" __declspec(dllexport) 
int hello(char* buffer, int bufferSize); 
+1

+1优秀的职位。您可能已经提到,大多数(所有?)Windows API都使用该调用方提供缓冲区方法(并独立于是否用于字符串或结构)并非巧合。 – Lucero 2012-07-08 19:55:31

+1

你的建议使用客户端分配的缓冲区有很大的意义,但我仍然看不出为什么打个招呼应该挂起。我同意你好,但是从技术上讲,这不能成为'挂'的原因。 Marshaller和'为什么'它叫CoTaskMemFree?恕我直言,这是一个'事实上'的约定,谁分配内存应该释放它而不是CLR。例如,如果hello返回一个指向静态缓冲区的指针,会发生什么? – 2012-07-09 10:15:11

+1

pinvoke编组调用者释放test()返回的字符串。但字符串没有分配在任何堆上。接下来发生的事情是完全不可预测的,但由于堆损坏而导致的挂起并不是不可能的。你的'事实上'的约定是准确的,当函数返回一个字符串时,它就不能这样工作,除非你导出一个释放它的函数。你没有这样做。自定义编组人员将被要求使用它。你没有这样做。建议的解决方案使用“事实上的”惯例,调用者分配和释放。 – 2012-07-09 13:05:22