挑战 - Ninjascript C# 接口与 C++ dll

Challenge - Ninjascript C# interface with a C++ dll

好的,这就是交易。我正在尝试将 C++ dll 与为 NinjaTrader 平台编写的指标(用 ninjascript 编写...本质上是 C# 并添加了一些特定于平台的代码)连接起来。为了使我的 dll 按预期方式工作,我需要能够将结构数组从指标传递到 dll。在指标代码中,我通过 ref 传递结构数组。在 dll 中,我试图接受结构数组作为指针。这使我能够编辑 dll 中的内容,而无需尝试找出将大量信息传回 NinjaTrader 的方法。基本上,dll 接收结构数组指针,这使它可以直接访问内容。然后,当 dll 函数 returns 一个 bool true 标志给 Ninja 时,它访问结构数组并将信息呈现给图表。听起来很简单吧?我也是这么想的

问题来了。 NinjaTrader 不允许不安全的代码。因此,当我尝试将结构数组传递给 dll 并将其作为指针接收时,它会立即使平台崩溃。如果我收到结构数组作为指向 ref (*&) 的指针,那么它可以工作,但是....一旦控制权传回 Ninja,在结构数组中完成的所有编辑都不存在。

因此,为了加快这个过程,我创建了一个非常简短的指标和 dll 代码集来演示我正在尝试做什么。 这是忍者指标代码:

[StructLayout(LayoutKind.Sequential)]
public struct TestStruct
{
    public int x, y;    
}
TestStruct[] test = new TestStruct[2];

protected override void OnBarUpdate()
{
    if(CurrentBar < Count - 2) {return;}
    test[0].x = 10;
    test[0].y = 2;
    test[1].x = 0;
    test[1].y = 0;

    Print(GetDLL.TestFunk(ref test));
    Print("X0: " + test[0].x.ToString() + "  Y0: " + test[0].y.ToString());
    Print("X1: " + test[1].x.ToString() + "  Y1: " + test[1].y.ToString());
}

class GetDLL
{
    GetDLL() {}
    ~GetDLL() {}
    [DllImport("testdll.dll", CallingConvention = CallingConvention.StdCall, EntryPoint = "TestFunk")]
    public static extern int TestFunk(
            [In,MarshalAs(UnmanagedType.LPArray)] ref TestStruct[] test );
}

现在是 C++ dll 代码:

#define WIN32_LEAN_AND_MEAN
#include "stdafx.h"
#include <windows.h>
#include <stdlib.h>
#include <stdio.h>

struct TestStruct
{
   int x, y;
};

extern "C" __declspec(dllexport) int __stdcall TestFunk( TestStruct *testy )
{
   testy[1].x = 20;
   testy[1].y = 9;
   int one = testy[1].x;
   int two = testy[1].y;

   return (one + two);
}

现在,请记住,我在上面粘贴的这段代码将导致 NinjaTrader 在您将指标放在图表上并激活时崩溃。我能够让它不崩溃的唯一方法是将 C++ TestFunk 函数中的 arg 更改为 TestStruct *&testyTestStruct **testy,注意必须更改 . 运算符到 -> 也。

既然我已经说了这么多,有没有人知道如何绕过这个限制并访问实际的指针,这样 dll 就可以编辑存储在结构数组中的实际值,这将反映在 NinjaTrader 中....但没有崩溃?

万岁!我终于想通了。先让我post相关代码,然后我会解释。

首先是 C#/Ninjascript

public class TestIndicator : Indicator
{    
    [StructLayout(LayoutKind.Sequential)]
    public struct TestStruct { public int x, y; }
    static TestStruct[] testy = new TestStruct[2];

    protected override void OnBarUpdate()
    {
        if(CurrentBar < Count - 2) {return;}

        GetDLL.TestFunk( ref testy[0] );
        Print("X0: " + testy[0].x.ToString() + "  Y0: " + testy[0].y.ToString());
        Print("X1: " + testy[1].x.ToString() + "  Y1: " + testy[1].y.ToString());
    }

    class GetDLL
    {
        GetDLL() {}
        ~GetDLL() {}
        [DllImport("testdll.dll", CallingConvention = CallingConvention.StdCall, EntryPoint = "TestFunk")]
        public static extern void TestFunk( ref TestStruct testy );
    }
}

C++ 代码:

struct TestStruct { int x, y; };

extern "C" __declspec(dllexport) void __stdcall TestFunk( void *t)
{
    TestStruct* ptE = (TestStruct*)t;
    ptE->x = 10; ptE->y = 2;
    ptE++;
    ptE->x = 20; ptE->y = 9;
}

首先,我必须使用 newstruct 数组声明为 static,为其在堆上指定一个固定的内存位置。然后我将它作为 ref 传递给我的 C++ dll。

在 dll 内部,arg 被捕获为 void* 数据类型。接下来,我将 arg 类型转换为 `TestStruct* 并将其存储在另一个指针变量中(以保持我的零元素引用完好无损)。

从那时起,我有了一个引用元素零的指针,我可以使用它来编辑结构数组中元素零处的值。要访问以下元素,我需要做的就是增加指针。一旦我这样做了,我就可以访问数组中的第一个元素。

没有任何内容传回 NinjaTrader,因为 dll 正在编辑原始内存位置中的实际值。不需要传回任何东西,从而减少了必要的 cpu cycles/memory 操作...这是我的初衷。

希望这对遇到类似情况的人有所帮助。