挑战 - 带有C++ dll 的 Ninjascript C# 接口

本文关键字:Ninjascript 接口 dll 带有 C++ 挑战 | 更新日期: 2023-09-27 17:57:12

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

这就是问题所在。 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 函数中的参数更改为 TestStruct *&testyTestStruct **testy ,注意.运算符也必须更改为 ->

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

挑战 - 带有C++ dll 的 Ninjascript C# 接口

> Huzzah! 我终于想通了。 首先,让我发布相关代码,然后我将进行解释。

首先是 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* 中,并将其存储在另一个指针变量中(以保持我的零元素引用不变)。

从那时起,我

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

没有任何东西被传回NinjaTrader,因为dll正在编辑其原始内存位置的实际值。 无需传递任何内容,从而减少了必要的 CPU 周期/内存操作......这是我的初衷。

希望这有助于陷入类似情况的人。