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

1.4k views Asked by At

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?

1

There are 1 answers

0
MehZhure On BEST ANSWER

Huzzah! I finally figured it. First, let me post the relevant code, then I will explain.

First the 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 );
    }
}

And the C++ code:

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

First, I had to declare the struct array as static, using new, giving it a fixed memory location on the heap. Then I pass it as a ref to my C++ dll.

Inside the dll, the arg is captured as a void* data type. Next, I typecast the arg into a `TestStruct* and store it in another pointer variable (to keep my zero element reference intact).

From that point, I have a pointer that references element zero, which I can use to edit the values at element zero in the struct array. To gain access to the following elements, all I needed to do was increment the pointer. Once I did that, I had access to element one in the array.

Nothing was passed back to NinjaTrader because the dll was editing the actual values in their original memory locations. No need to pass anything back, thus attaining the reduced cpu cycles/memory operations necessary...which was my original intent.

Hopefully this helps someone stuck in a similar situation.