【问题标题】:Unmanaged Function Pointer causing SEGFAULT on return返回时导致 SEGFAULT 的非托管函数指针
【发布时间】:2019-12-08 01:21:06
【问题描述】:

我在C中有以下方法

void SSL_CTX_set_cookie_verify_cb(SSL_CTX *ctx,
                                  int (*cb) (SSL *ssl,
                                             const unsigned char *cookie,
                                             unsigned int cookie_len))
{
    ctx->app_verify_cookie_cb = cb;
}

我希望在 C# 中调用它。我正在尝试这样:

[DllImport(SSLDLLNAME, CallingConvention = CallingConvention.Cdecl)]
public static extern void SSL_CTX_set_cookie_verify_cb(IntPtr ctx, VerifyCookieDelegate callback);

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate int VerifyCookieDelegate(IntPtr ssl, [MarshalAs(UnmanagedType.LPStr)] string cookie, int cookie_len);

不幸的是,当回调运行时,返回后,进程死于SIGSEGV(segfault)

    public int VerifyCookieThunk(IntPtr sslPtr, IntPtr cookie, int cookieLength)
    {
        if (!TryGetInstance(sslPtr, out Ssl ssl))
            return Native.FAIL;

        var c = new byte[cookieLength];

        Marshal.Copy(cookie, c, 0, cookieLength);

        if (_verifyCookieCallback(ssl, c))
            return 1;

        return 0;
    }

我觉得奇怪的是,当回调返回时会发生段错误。所以我想也许我还需要保留对返回值的引用。于是我做了一个类变量(类不是GCd):

private int verifyCookieResult;

现在在回调中我设置并返回verifyCookieResult。仍然是 SEGFAULT。

您可能认为本机代码中存在错误,但实际上没有。这个相同的功能在不同的上下文中工作。

我写代码设置回调函数的方式有问题吗?

【问题讨论】:

  • 有没有可能你已经在 32 位模式下编译了你的 C 代码,而你在 64 位模式下运行 C#?您确定两者都以相同的模式编译/运行吗?
  • “它在不同的上下文中工作”与“无错误”不同。作为一个常见问题:未管理的代码是针对 x32 或 x64(有时是 x16)编译的。同时,MSIL 与位无关,因此相同的代码可以以 x32 或 x64 运行——如果我们获得运行时,甚至可以以 x16 或 x128 运行。这是由运行时根据项目的目标设置决定的。这里的不匹配导致了无数此类问题。
  • cookie 的类型不匹配 - 你在一个地方有 string,在另一个地方有 IntPtr
  • 还要确保您保留对VerifyCookieDelegate 实例的引用,否则VerifyCookieDelegate 可能会在C 端调用它之前在.NET 端进行GC。这不会导致您当前的问题,但这是一个常见的问题。

标签: c# pinvoke marshalling unmanaged


【解决方案1】:

假设您在 Windows 下运行。

您可能与调用约定不匹配。 正如我从您的示例中看到的那样,它设置为CallingConvention.Cdecl

来自Wikipedia

调用约定描述了被调用代码的接口:

  • 原子(标量)参数或单个部分的顺序 一个复杂的参数,被分配了
  • 如何传递参数(推送 在堆栈上,放置在寄存器中,或两者兼而有之)
  • 被调用函数必须为调用者保留哪些寄存器(也称为: 被调用者保存的寄存器或非易失性寄存器)
  • 为函数调用准备堆栈以及在函数调用之后恢复的任务是如何完成的 在调用者和被调用者之间划分

来自MSDN

__stdcall 调用约定用于调用 Win32 API 函数。

尝试将您的代码更改为 std 调用。

CallingConvention.StdCall

【讨论】:

  • 调用约定不匹配确实可能是问题所在,但 OP 不应盲目“尝试更改”它。他只需要确保本机 (C) 和托管 (.NET) 世界之间的一致性。除非他在 C 中指定 stdcall(这不是默认值,而且他可能不在 Windows 下运行),否则它会神奇地工作的可能性很小。
  • DllImport 表示他是在Windows下运行的。
猜你喜欢
  • 2012-07-03
  • 1970-01-01
  • 1970-01-01
  • 2011-01-21
  • 2019-01-08
  • 2011-10-03
  • 2020-04-14
  • 2014-09-30
  • 1970-01-01
相关资源
最近更新 更多