1

Alright, here is the deal. I am trying to interface a C++ dll with an indicator written for the NinjaTrader platform (which is written in ninjascript...essentially C# with some platform specific code additions). TO make my dll work the way it is intended, I need to be able to pass a struct array from the indicator to the dll. In the indicator code, I am passing the struct array via ref. In the dll, I am trying to accept the struct array as a pointer. This allows me to edit the contents in the dll, without trying to figure out a way to pass a horde of information back to NinjaTrader. Basically, the dll receives the struct array pointer, which gives it access to the contents directly. Then, when the dll function returns a bool true flag to Ninja, it accesses the struct array and renders the information to the chart. Sounds simple right? I thought so too.

Here is the problem. NinjaTrader does not allow unsafe code. So, when I try to pass the struct array to the dll, and receive it as a pointer, it immediately crashes the platform. If I receive the struct array as a pointer to a ref (*&), then it works, but....once control is passed back to Ninja, all the edits done in the struct array are non-existent.

So, to speed up this process, I have created a very brief indicator, and dll code set which demonstrates what I am trying to do. Here is the ninja indicator code:

[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 );
}

And now the C++ dll code:

#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);
}

Now, please keep in mind, this code I have pasted in above WILL cause NinjaTrader to crash the moment you place the indicator on a chart and it becomes active. The only way I have been able to make it NOT crash is to change the arg in the C++ TestFunk function to either TestStruct *&testy or TestStruct **testy, noting that the . operators will have to be changed to -> also.

Now that I have said all that, does anyone know how to get around this limitation and get access to the actual pointer, so the dll can edit the actual values stored in the struct array, which will be reflected inside NinjaTrader....but not crash?

4

1 回答 1

2

嘘!我终于想通了。先贴一下相关代码,然后再解释。

首先是 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;
}

首先,我必须将struct数组声明为static, using new,为其在堆上提供一个固定的内存位置。然后我将它作为 a 传递ref给我的 C++ dll。

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

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

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

希望这可以帮助陷入类似情况的人。

于 2015-06-24T19:44:54.097 回答