【问题标题】:Crash in x64 version of .net application using p/Invoke使用 p/Invoke 在 x64 版本的 .net 应用程序中崩溃
【发布时间】:2012-10-18 00:04:18
【问题描述】:

所以,我正在尝试以 64 位模式运行我的 .NET 应用程序。我依赖于可以为 x64 编译的 dll 并且它可以正确加载。我可以调用一些调用,但是当我尝试调用特定调用时,应用程序会完全崩溃。没有错误或任何东西......

错误发生在两个地方之一;要么在这里:

#include "socket.h"
int SenderAddrSize = sizeof(SOCKADDR_IN);
SOCKADDR_IN CSocket::SenderAddr;

...

char* CSocket::tcpip()
{
if(sockid<0)return NULL;
if(getpeername(sockid, (SOCKADDR *)&SenderAddr, &SenderAddrSize) == SOCKET_ERROR)return NULL;
return inet_ntoa(SenderAddr.sin_addr);
}

...

或这里:

#include "buffer.h"
char CBuffer::retval[20001];
CBuffer::CBuffer()
{
BuffSize = 30;
data = (char*)malloc(BuffSize);
count = 0;
readpos = 0;
writepos = 0;
}

...

您可以在我的 GitHub HERE 中找到完整的代码,分别在 socket.cppbuffer.cpp 中。 代码的入口点和调用上述方法的对象都位于main.cpp中

p/invoke 调用如下:

对于套接字:

[DllImport("SockLib", CallingConvention = CallingConvention.Cdecl, EntryPoint = "tcpip")]
public static extern String tcpip(Double socket);

对于缓冲区:

 [DllImport("SockLib", CallingConvention = CallingConvention.Cdecl, EntryPoint = "createbuffer")]
 public static extern Double createbuffer();

代码崩溃的任何想法。这在 x86 中就像一个魅力,只是在 x64 中没有......

另外,作为一个附带问题...如果我将一个 int 写入在 x86 中运行的客户端上的缓冲区并在 x64 服务器上接收它(反之亦然),该 int 是 4 字节还是 8 字节?关键字 int 是否会因平台特定大小而改变,还是我必须指定 int64 才能获得 8 字节整数?这适用于上述较大问题的 C++ 源代码部分。

【问题讨论】:

  • 双插座?双缓冲?为什么20001特别?不要单独编码。使用 System.Net 取得成功。
  • 我不确定...恐怕我没有编写此代码(我只是将其移植到 Linux 和 Android)...我有非常具体的理由在系统上使用它.Net,其中大部分都围绕没有适当网络实现(或非常原始/限制性实现)的支持框架......
  • 永远不要使用字符串作为返回类型。使用 IntPtr 获取使用 msdn.microsoft.com/en-us/library/7b620dhe.aspx 的字符串。至于 int 大小 int 在任何 .net 平台上都是 Int32,所以我建议在 c++ 端使用 stdint.h 和 int32_t。

标签: c# dll crash pinvoke 64-bit


【解决方案1】:

不要在 p/invoke 调用中使用 string 作为返回值。这将假定内存是使用CoTaskMemAlloc 分配的,因此将导致编组器在返回的指针上调用CoTaskMemFree。这会导致运行时错误。

一种选择是,您可以更改本机代码以使用CoTaskMemAlloc 将其分配的内存返回给C#。如果你这样做了,那么你可以继续在你的 p/invoke 返回值中使用string

替代方法是像现在一样使用malloc,在C# 端使用IntPtr,并从您的本机代码中导出一个释放器。

要将IntPtr 转换为字符串,请使用Marshal.PtrToStringAnsi()。完成后,您可以将指针传回您的本机 DLL,以便它可以在其上调用 free()

看起来像这样:

[DllImport("SockLib", CallingConvention=CallingConvention.Cdecl)]
public static extern IntPtr tcpip(double socket);

你这样称呼它:

IntPtr strPtr = tcpip(socket);
string str = Marshal.PtrToStringAnsi(strPtr);

您需要为每个返回字符串的 p/invoke 调用执行类似的操作。

您是否需要释放这些指针并不是 100% 清楚的。这取决于您是否在 C++ 代码中分配了内存。例如,tcpip 映射到对inet_ntoa 的调用。这将返回一个指向套接字库拥有的缓冲区的指针。所以你的代码不能试图释放它。但是,如果您返回使用mallocnew[] 分配的内存,那么您也必须释放它。


其他一些观察:

  • 您正在返回在 C++ 代码中声明为 int 的套接字,但在 C 包装器中将它们键入为 double。不要那样做。一直使用int
  • C# 类型 int 为 32 个字节。对于 64 位整数,请使用 long。未签名的版本是uintulong。对于机器字长的类型,请使用IntPtrUIntPtr

【讨论】:

  • 好的,我理解了其中的一半……你能用更新奇的方式解释一下吗……什么类和我在哪里包装?我确实使用 C 命名约定来避免 C++ 重整问题。我也使用 Cdecl (如果重要的话)。你能给我一个使用我的代码的例子吗?
  • 你的 C++ 代码是一堆类。你不能调用带有 p/invoke 的类的方法。
  • 那么解决方法是什么?我有一个调用类的 main.cpp……我想我只是对类以外的东西感到困惑……
  • 啊,你已经包好了。您为什么不在问题中显示该代码?!
  • 那个包装器在我看来是合理的。我可以看到的主要问题是如何返回字符串。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-03-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多