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

它完全运行的事实意味着它确实加载了 dll,找到了 DoCallBack() 方法的引用,并且它似乎调用了该​​方法。但什么也没有发生。所以有些事情是关闭的。

您可能想知道为什么要尝试这样做?你想知道是否有更好的方法?好吧,最终目标是创建一个“hack”,以便以与在同一域中几乎相同的性能跨 AppDomains 运行线程。

在以下链接中,您将找到迄今为止更快的跨 AppDomain 性能技术。 MS .Net AddIn 团队提供了“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

现在让我们讨论一些时间比较,看看为什么这仍然不足以满足我们的需求。正常的跨域远程处理在具有零参数的方法上每秒提供大约 10,000 次调用。使用 FastPath 选项可增加到每秒 200,000 次调用。但是,与调用具有零参数(在同一域中)的接口(interface)方法的 C# 相比,它与其他测试在同一台机器上每秒执行超过 160,000,000 次操作。

所以即使是 FastPath 技术,仍然比简单的接口(interface)方法调用慢 1,000 倍。但是为什么我们需要更好的性能?

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

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

请注意,我们不希望跨 AppDomains 通信数据,它们都是并行独立运行的。

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

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

不幸的是,每个 AppDomain 在它耗尽工作之前不能独立运行很长时间,并且需要返回到主域让不同的 AppDomain 工作..所以来自 FastPath 技术的每秒 200,000 的性能时间将导致显着的缓慢由于跨 AppDomain 调用导致整体性能下降。

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

每秒 9000 万更接近我们在 AppDomain 之间切换线程的需求。

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

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

这是构建为 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()。我想你想要
它是回调()。 — 吉姆·米歇尔

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

您首先调用非托管方法将委托(delegate)发送到非托管代码,该代码被编码为函数指针。

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

我们的测试表明,与每秒 8000 万次对接口(interface)或 Interlocked.Increment() 的简单 C# 调用的每秒 3 亿次调用相比,这可以实现每秒 9000 万次调用。

换句话说,这足够快,以至于经常发生跨 AppDomain 边界的线程转换。

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

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

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

关于.net - 将 P/Invoke(也)托管回调反向到非托管代码,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/8770611/

10-13 06:17