反向P/Invoke(同时)对非托管代码的托管回调



包含的C#单元测试和C代码文件试图将托管回调传递给非托管代码。代码实际运行,但计数变量从不递增。所以测试失败了。

它运行的事实意味着它确实加载了dll,找到了DoCallBack()方法的引用,并且似乎调用了该方法。但什么也没发生。所以出了问题。

你可能想知道为什么要这么做?你想知道是否有更好的方法?好吧,最终目标是创建一个"破解",以便在AppDomains之间以与在同一域中几乎相同的性能运行线程。

在下面的链接中,您将发现迄今为止更快的跨AppDomain性能技术。.Net插件团队提供了"FastPath",它比简单的远程处理性能提高了很多。我们在.Net 3.5上运行了他们的示例,在将他们的AddIn合同放入GAC后,它运行得非常快。

http://blogs.msdn.com/b/clraddins/archive/2008/02/22/add-in-performance-what-can-you-expect-as-you-cross-an-isolation-boundary-and-how-to-make-it-better-jesse-kaplan.aspx

现在,让我们讨论一些时间分摊,看看为什么这仍然不够快,无法满足我们的需求。普通的跨域远程处理在一个没有参数的方法上每秒提供大约10000次调用。FastPath选项可增加到每秒200000次呼叫。但与C#调用零参数接口方法(在同一域中)相比,它在与其他测试相同的机器上每秒执行超过16000000次操作。

因此,即使是FastPath技术仍然比简单的接口方法调用慢1000倍。但为什么我们需要更好的表现?

或者,性能要求是消除CPU绑定应用程序的所有软件瓶颈,该应用程序使用多核和分布式技术在几分钟内处理数十亿元组的信息。

但一个新的功能要求是能够提供插件或插件架构,以便在不停止系统其余部分的情况下加载或卸载组件。在.Net上有效地做到这一点的唯一方法是使用单独的AppDomains。

请注意,我们不想在AppDomains之间进行数据通信,它们都是并行独立运行的。

但就线程而言,在数百个AppDomain中的每个AppDomain中运行一个单独的线程是非常低效的。如果是这样的话,它们会争夺CPU,并导致上下文切换带来的巨大性能损失。

因此,这里的计划是有一个主域或主域,它有一个线程池,轮流调用每个有工作要做的AppDomain,并让它工作一段时间。因此,这意味着协作多线程(以避免上下文切换)。因此,AppDomains将返回以允许主AppDomain移动到其他AppDomain。

不幸的是,每个AppDomain在耗尽工作之前不能独立运行很长时间,需要返回到主域以让不同的AppDomain工作。。因此,FastPath技术每秒200000次的性能时间将由于跨AppDomain调用而导致整体性能显著下降。

与下面的PInvoke相比,我们测量了StopWatch在与其他测试相同的机器上每秒产生超过90000000次呼叫的时间,即9000万次呼叫。因此,希望到那时,将P/Invoking反向到不同的AppDomain中,它仍将允许每秒数百万次操作。

每秒9000万与我们在AppDomains之间切换线程的需求非常接近。

好的。现在回到这个单元测试。这个简单的单元测试的目的是首先获得从非托管代码到托管代码的简单回调。。。。之后,下一步将创建一个单独的AppDomain,并获得对其的委托回调,然后传递给非托管代码以测试跨域回调性能。

我们知道这一切都是可能的,我们在网上看到了讨论和例子。。。虽然下面的代码看起来很简单。。。它只是没有按预期工作。

以下是作为DLL构建的非托管代码,不包含/CLR命令行选项:

#include <windows.h>
typedef void (CALLBACK *PFN_MYCALLBACK)();
int count = 0;
extern "C" {
    __declspec(dllexport) void __stdcall DoSomeStuff() {
        ++count;
    }
}
extern "C" {
    __declspec(dllexport) void __stdcall DoCallBack(PFN_MYCALLBACK callback) {
        PFN_MYCALLBACK();
    }
}

这是C#单元测试代码。

using System.Runtime.InteropServices;
using System.Security;
using NUnit.Framework;
namespace TickZoom.Callback
{
    [SuppressUnmanagedCodeSecurity]
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    public delegate void MyCallback();
    [TestFixture]
    class CrossAppDomainTesting
    {
        public int count;
        [Test]
        public void TestCallback()
        {
            NativeMethods.DoCallBack(
                delegate()
                {
                    ++count;
                });
            Assert.AreEqual(1, count);
        }
        [Test]
        public void TestDate()
        {
            NativeMethods.DoSomeStuff();
        }
    }
    public static class NativeMethods
    {
        [SuppressUnmanagedCodeSecurity]
        [DllImport("CrossAppDomain.dll")]
        public static extern void DoSomeStuff();
        [SuppressUnmanagedCodeSecurity]
        [DllImport("CrossAppDomain.dll")]
        public static extern void DoCallBack(MyCallback callback);
    }
}

帖子的第一位评论者解决了编码问题。我等着他把它贴出来,作为对他的肯定。如果他这样做了,那么我会的。

这只是DoCallback函数中的一个拼写错误吗?您有PFN_MYCALLBACK()。我想你想要它将被回调()。–Jim Mischel

此外,计时的结果是,从一个AppDomain调用到另一个AppDomain的最快方式如下:

首先调用一个非托管方法,将委托发送到非托管代码,该代码将封送至函数指针。

从那时起,您可以调用不带任何参数的非托管代码,但可以重用函数指针来调用其他AppDomain。

我们的测试表明,它的工作速度为每秒9000万次调用,而在接口或Interlocked.Increment()上进行简单的C#调用的速度为每秒3亿次,即每秒8000万次。

换言之,这足够快,可以经常在AppDomain边界之间转换线程。

注意:有几件事需要小心。如果你保留指向卸载的AppDomains的指针,然后尝试调用它们,你会得到一个关于收集的委托的异常。原因是CLR提供给您的函数指针不仅仅是代码中的函数指针。相反,它是一个指向一段"thunking"代码的指针,该代码首先检查委托是否仍然存在,并为从非托管代码到托管代码的转换做一些内务处理。

我们的计划是为每个AppDomain分配一个整数句柄。然后,非托管代码将获得要放入数组中的"句柄"和函数指针。

当我们卸载AppDomain时,我们还会通知非托管代码删除该句柄的函数指针。我们将在已释放的列表中保留已释放的"句柄",以便在创建的下一个AppDomain中重复使用。

相关内容

  • 没有找到相关文章

最新更新