C# 在C语言中,封送一个从dll返回的无符号char*函数#

C# 在C语言中,封送一个从dll返回的无符号char*函数#,c#,pinvoke,return,marshalling,C#,Pinvoke,Return,Marshalling,我在本机DLL中有以下函数头: unsigned char* Version_String() 我试图从一个C#项目中调用它,我尝试了以下调用(在这里的其他类似问题中可以找到): 我一直得到以下例外: [DllImport("BSL430.dll", CharSet=CharSet.Ansi)] [return : MarshalAs(UnmanagedType.LPStr)] public extern static string Version_String(); 试图读取或写入受保护的

我在本机DLL中有以下函数头:

unsigned char* Version_String()
我试图从一个C#项目中调用它,我尝试了以下调用(在这里的其他类似问题中可以找到):

我一直得到以下例外:

[DllImport("BSL430.dll", CharSet=CharSet.Ansi)]
[return : MarshalAs(UnmanagedType.LPStr)]
public extern static string Version_String();
试图读取或写入受保护的内存。这通常表示其他内存已损坏

下一次尝试如下,我得到了相同的异常:

[DllImport("BSL430.dll", CharSet=CharSet.Ansi)]
[return : MarshalAs(UnmanagedType.LPStr)]
public extern static string Version_String();
我似乎无法回避这个问题。任何帮助都将不胜感激

编辑:

我不能在这里给出DLL代码,因为它属于NDA,但我调用的函数如下所示:

unsigned char versionString[50];

__declspec(dllexport) unsigned char* Version_String()
{
    if(check_hardware_stuff())
    {
      strcpy((char *) versionString, "version_string_bla_bla");
      versionString[5] = stuff;
    }
    else if (other_check())
    {
      //will return empty string, that should be filled with '\0'
    }
    else
    {
      strcpy( (char *) versionString, "ERROR" );
    }
  return versionString;
}
[DllImport("BSL430.dll", CallingConvention=CallingConvention.Cdecl)]
public extern static IntPtr Version_String();
我并不特别喜欢DLL实现,但我需要“按原样”使用它。
每当我尝试调用
VersionString()
,无论我如何处理返回值,都会引发异常。

更新

看到更新的问题、各种注释和本机函数的代码后,调用
check\u hardware\u stuff()
时可能会出现异常。它很容易调试。我将用这样的函数替换您的函数:

unsigned char versionString[50];

__declspec(dllexport) unsigned char* Version_String()
{
    if(check_hardware_stuff())
    {
      strcpy((char *) versionString, "version_string_bla_bla");
      versionString[5] = stuff;
    }
    else if (other_check())
    {
      //will return empty string, that should be filled with '\0'
    }
    else
    {
      strcpy( (char *) versionString, "ERROR" );
    }
  return versionString;
}
[DllImport("BSL430.dll", CallingConvention=CallingConvention.Cdecl)]
public extern static IntPtr Version_String();
无符号字符版本字符串[50]

__declspec(dllexport) unsigned char* Version_String()
{
    strcpy(versionString, "testing");
    return versionString;
}
如果仍然失败,那么我的猜测是在DLL的
DllMain
中引发了错误。通过将上述函数放入一个普通的、不做任何其他事情的DLL来调试它

原始答案

调用约定是最明显的问题。本机代码最有可能使用
cdecl
,但p/invoke默认值为
stdcall
。将您的p/invoke签名更改为如下所示:

unsigned char versionString[50];

__declspec(dllexport) unsigned char* Version_String()
{
    if(check_hardware_stuff())
    {
      strcpy((char *) versionString, "version_string_bla_bla");
      versionString[5] = stuff;
    }
    else if (other_check())
    {
      //will return empty string, that should be filled with '\0'
    }
    else
    {
      strcpy( (char *) versionString, "ERROR" );
    }
  return versionString;
}
[DllImport("BSL430.dll", CallingConvention=CallingConvention.Cdecl)]
public extern static IntPtr Version_String();
您可以安全地省略
字符集
参数,因为这些参数都没有文本,因为您将返回值视为指针

Edit:Hans在评论中正确指出,由于没有参数,调用约定不匹配并不重要。所以这不是问题所在

调用以转换为.net字符串

string version = Marshal.PtrToStringAnsi(Version_String());
由于
PtrToStringAnsi
需要
IntPtr
参数,我建议您使用
IntPtr
作为p/invoke签名的返回类型

这一切都假定从本机函数返回的内存在本机DLL中分配和释放。如果它是堆分配的,并且您希望调用者取消分配它,那么您就有一个小问题。既然您没有访问本机DLL堆的权限,如何从C#中释放内存

简单的解决方案是使用共享COM堆来分配内存。调用
CoTaskMemAlloc
为字符串分配缓冲区。然后将返回值声明为
string
类型,p/invoke封送器将与COM分配器解除分配

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

当然,这仅适用于返回希望调用方释放的堆分配内存的情况。

可能使用了错误的方法?dll是否使用多字节字符集选项编译?使用调试器找出本机代码崩溃的位置。项目+属性,调试,勾选“启用非托管代码调试”。在C源代码文件中的函数上设置断点。@很遗憾,我使用的是Express版本,它不支持非托管代码调试。不过还是要感谢你的想法,希望我能很快切换到完整版本。谢谢你的及时回复。我尝试了代码,但仍然得到相同的异常。我看了一下DLL代码,从中可以看出字符串被全局声明为固定大小的数组:
unsigned char versionString[50]。据我所知,在这种情况下不需要进一步分配,对吗?它不能调用约定,函数不接受任何参数。在什么情况下会出现异常?调用DLL或使用返回的值执行操作时?@David调用Version_string()时会触发异常,无论我是否将返回值复制到另一个变量。@David我用DLL中的一些代码编辑了原始帖子。谢谢你的帮助!