GavinRay97 / reaper-native

A collection of ports/examples of REAPER's native C/C++ SDK to other languages
9 stars 1 forks source link

Expanding On The Code #3

Open jamsoft opened 2 years ago

jamsoft commented 2 years ago

Hi, this looks fantastic. Found it yesterday and have been tinkering. Hope you have some time to read/respond as I'm having some troubles but really want this to work and contribute.

I'll do a quick walk-through.

  1. Cloned the repo, compiled and deployed
  2. BOOM all working sweet.
  3. The end (I wish)

So I decided to take a closer look.

Looking over Reapy and Beyond.Reaper it inspired me to add in a few wrapper methods and add in UDP connectivity for 3rd party callers. This is where I'm stuck. The plugin starts up fine, prints some messages to the console and it appears the UDP socket is also created and working.

public static int ReaperPluginEntry(IntPtr hInstance, [DNNE.C99Type("struct reaper_plugin_info_t*")] reaper_plugin_info_t* rec)
{
  try
  {
    // Initialize
    _reaper = new();
    _reaper.Initialize(rec);

    _udp = new UdpSocket();
    _udp.Server("192.168.50.128", 8081);

    ShowConsoleMsg("Hello From C#");

    //InsertTrackAtIndex(1);
    //InsertMedia(@"D:\Samples\hiphop2\bigga_bass_86a_g#.wav");
    GetExePath();

    return 1;
  }
  // Handle exception
  catch (Exception e)
  {
    System.Diagnostics.Debug.WriteLine(e);
    return 0;
  }
}

As soon as I make a call, reaper gets the message, prints it to the console and then hangs requiring a task manager forced shutdown. As far as I can tell, my UDP config is correct.

I've tried monitoring the situation in WinDbg but I didn't get anything useful from it other than this:

  ModLoad: 00000000`00850000 00000000`00858000   C:\Program Files\dotnet\shared\Microsoft.NETCore.App\6.0.6\System.Threading.ThreadPool.dll
(5bb0.1c94): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
*** WARNING: Unable to verify checksum for C:\Users\jamme\AppData\Roaming\REAPER\UserPlugins\ReaperSharp.dll
*** WARNING: Unable to verify checksum for C:\Users\jamme\AppData\Roaming\REAPER\UserPlugins\reaper_csharp.dll
ReaperSharp!ReaperSharp.reaper_plugin_functions.Initialize+0x143:
00007ffc`d0f81583 488b4918        mov     rcx,qword ptr [rcx+18h] ds:00000000`00000018=????????????????

I refactored things a little, such as:

This method works.

public static void ShowConsoleMsg(string message)
{
  var msgPtr = Marshal.StringToHGlobalAnsi($"{message}\n");
  try
  {
    _reaper.ShowConsoleMsg((sbyte*)msgPtr);
  }
  finally
  {
    Marshal.FreeHGlobal(msgPtr);
  }
}

As does this method:

public static string GetExePath()
{
  var e = (IntPtr)_reaper.GetExePath();
  string? s = Marshal.PtrToStringAnsi(e);
  ShowConsoleMsg(s);
  return s;
}

This method does not work however, no ideas why but it could I guess be the fact that I'm testing it as part of the initialisation of the plugin at the moment, so may well be too early in the startup process of Reaper itself but I'm not 100% on this.

public static int InsertMedia(string path)
{
  var pathPtr = Marshal.StringToHGlobalAnsi(path);
  try
  {
    ShowConsoleMsg(path);
    ShowConsoleMsg(pathPtr.ToString());
    return _reaper.InsertMedia((sbyte*)pathPtr, 1);
  }
  finally
  {
    Marshal.FreeHGlobal(pathPtr);
  }
}

This method also fails to do anything but like I said above this may well be too early in the startup.

public static void InsertTrackAtIndex(int i
{
     _reaper.InsertTrackAtIndex(i, 1);
 }

Strangely, reapy seems to use UDP Dgram listening on 0.0.0.0 which is odd. That address will just blow up .NET. I've tried listening on 127.0.0.1 and my host machine IP. Always the same result. The UDP class is really simple:

    public class UdpSocket
    {
        public Socket _socket;
        private const int bufSize = 8 * 1024;
        private State state = new State();
        private EndPoint epFrom = new IPEndPoint(IPAddress.Any, 0);
        private AsyncCallback recv = null;

        public class State
        {
            public byte[] buffer = new byte[bufSize];
        }

        public void Server(string address, int port)
        {
            _socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
            //_socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.ReuseAddress, true);
            _socket.Bind(new IPEndPoint(IPAddress.Parse(address), port));
            Receive();            
        }

        public void Client(string address, int port)
        {
            _socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
            _socket.Connect(IPAddress.Parse(address), port);
            Receive();            
        }

        public void Send(string text)
        {
            byte[] data = Encoding.ASCII.GetBytes(text);
            _socket.BeginSend(data, 0, data.Length, SocketFlags.None, (ar) =>
            {
                State so = (State)ar.AsyncState;
                int bytes = _socket.EndSend(ar);
                Console.WriteLine("SEND: {0}, {1}", bytes, text);
            }, state);
        }

        private void Receive()
        {            
            _socket.BeginReceiveFrom(state.buffer, 0, bufSize, SocketFlags.None, ref epFrom, recv = (ar) =>
            {
                try
                {
                    State so = (State)ar.AsyncState;
                    int bytes = _socket.EndReceiveFrom(ar, ref epFrom);
                    _socket.BeginReceiveFrom(so.buffer, 0, bufSize, SocketFlags.None, ref epFrom, recv, so);

                    MyReaperPlugin.ShowConsoleMsg($"RECV: {epFrom.ToString()}: {bytes}, {Encoding.ASCII.GetString(so.buffer, 0, bytes)}");

                    //Console.WriteLine("RECV: {0}: {1}, {2}", epFrom.ToString(), bytes, Encoding.ASCII.GetString(so.buffer, 0, bytes));
                }
                catch(Exception ex)
                {
                    MyReaperPlugin.ShowConsoleMsg($"{ex.Message}");
                }

            }, state);
        }
    }

The client is equally simple, just a console app doing this:

_udpClient = new UdpClient(8081);
_udpClient.Connect("192.168.50.128", 8081);
Byte[] sendBytes = Encoding.ASCII.GetBytes("TEST Message!");
_udpClient.Send(sendBytes, sendBytes.Length);

Would love to get this working and I'm sure it's a simple oversight on my part but I'm fast running out of ideas and can't really pick up anything useful from the other Reaper Extensions as it seems this setup is in agreement with theirs, save for a few platform differences.

I'm sure it's something to do with how UDP is setup, Reaper shows the TEST Message! string, so it seems logical that it's whatever happens immediately after that message is printed is the issue.

Any pointers would be really appreciated.

jamsoft commented 2 years ago

Just found this in the Event Viewer.

<Event xmlns="http://schemas.microsoft.com/win/2004/08/events/event">
<System>
  <Provider Name="Application Hang" /> 
  <EventID Qualifiers="0">1002</EventID> 
  <Version>0</Version> 
  <Level>2</Level> 
  <Task>101</Task> 
  <Opcode>0</Opcode> 
  <Keywords>0x80000000000000</Keywords> 
  <TimeCreated SystemTime="2022-07-10T12:50:25.1334325Z" /> 
  <EventRecordID>3841122</EventRecordID> 
  <Correlation /> 
  <Execution ProcessID="0" ThreadID="0" /> 
  <Channel>Application</Channel> 
  <Computer>GlowyMcGlowface</Computer> 
  <Security /> 
  </System>
<EventData>
  <Data>reaper.exe</Data> 
  <Data>6.6.3.0</Data> 
  <Data>63e4</Data> 
  <Data>01d8945b67d0dd7b</Data> 
  <Data>5</Data> 
  <Data>C:\Program Files\REAPER (x64)\reaper.exe</Data> 
  <Data>60f108d1-8e96-4950-9c13-70f5c608fb69</Data> 
  <Data /> 
  <Data /> 
  <Data>Unknown</Data> 
  <Binary>55006E006B006E006F0077006E0000000000</Binary> 
  </EventData>
  </Event>
jamsoft commented 2 years ago

Also, attaching VS Debugger to a running instance of Reaper allows me to breakpoint on the execution and it all seems to working as far as I can tell. Just something going on that Reaper doesn't like but I'm completely out of ideas now to be honest.

jamsoft commented 2 years ago

More event data, I can't see anything pertinent in here though:

Version=1
EventType=AppHangB1
EventTime=133019310206605551
ReportType=3
Consent=1
UploadTime=133019310222206580
ReportStatus=268435456
ReportIdentifier=794bfbae-95fc-4b68-89b4-b4e541657844
IntegratorReportIdentifier=60f108d1-8e96-4950-9c13-70f5c608fb69
Wow64Host=34404
NsAppName=reaper.exe
OriginalFilename=reaper.exe
AppSessionGuid=000063e4-0001-0482-7bdd-d0675b94d801
TargetAppId=W:00066016c0205f41a2309dfd9fef8507341300000904!00006c96028bd147b617037b1fa8c256855eeb58dcc2!reaper.exe
TargetAppVer=2022//07//03:21:29:13!e8e229!reaper.exe
BootId=4294967295
TargetAsId=2844
UserImpactVector=269484304
IsFatal=1
EtwNonCollectReason=1
Response.BucketId=de216398a4c9a6c807eda2d6148e3fbe
Response.BucketTable=5
Response.LegacyBucketId=1724213272704335806
Response.type=4
Sig[0].Name=Application Name
Sig[0].Value=reaper.exe
Sig[1].Name=Application Version
Sig[1].Value=6.6.3.0
Sig[2].Name=Application Timestamp
Sig[2].Value=62c20a29
Sig[3].Name=Hang Signature
Sig[3].Value=5115
Sig[4].Name=Hang Type
Sig[4].Value=134217728
DynamicSig[1].Name=OS Version
DynamicSig[1].Value=10.0.19044.2.0.0.256.48
DynamicSig[2].Name=Locale ID
DynamicSig[2].Value=2057
DynamicSig[22].Name=Additional Hang Signature 1
DynamicSig[22].Value=511566d5c003373d2663daf15c34a154
DynamicSig[23].Name=Additional Hang Signature 2
DynamicSig[23].Value=dd29
DynamicSig[24].Name=Additional Hang Signature 3
DynamicSig[24].Value=dd2935d3d54d99721fbe7c50a06b4a48
DynamicSig[25].Name=Additional Hang Signature 4
DynamicSig[25].Value=5115
DynamicSig[26].Name=Additional Hang Signature 5
DynamicSig[26].Value=511566d5c003373d2663daf15c34a154
DynamicSig[27].Name=Additional Hang Signature 6
DynamicSig[27].Value=dd29
DynamicSig[28].Name=Additional Hang Signature 7
DynamicSig[28].Value=dd2935d3d54d99721fbe7c50a06b4a48
UI[3]=REAPER is not responding
UI[4]=If you close the program, you might lose information.
UI[5]=Close the program
UI[6]=Close the program
UI[7]=Close the program
LoadedModule[0]=C:\Program Files\REAPER (x64)\reaper.exe
... SNIPPED
LoadedModule[148]=C:\Users\jamme\AppData\Roaming\REAPER\UserPlugins\reaper_csharp.dll
LoadedModule[149]=C:\Program Files\dotnet\host\fxr\6.0.6\hostfxr.dll
LoadedModule[150]=C:\Program Files\dotnet\shared\Microsoft.NETCore.App\6.0.6\hostpolicy.dll
LoadedModule[151]=C:\Program Files\dotnet\shared\Microsoft.NETCore.App\6.0.6\coreclr.dll
LoadedModule[152]=C:\Program Files\dotnet\shared\Microsoft.NETCore.App\6.0.6\System.Private.CoreLib.dll
LoadedModule[153]=C:\Program Files\dotnet\shared\Microsoft.NETCore.App\6.0.6\clrjit.dll
LoadedModule[154]=C:\WINDOWS\SYSTEM32\icu.dll
LoadedModule[155]=C:\Users\jamme\AppData\Roaming\REAPER\UserPlugins\ReaperSharp.dll
LoadedModule[156]=C:\Program Files\dotnet\shared\Microsoft.NETCore.App\6.0.6\System.Runtime.dll
LoadedModule[157]=C:\Program Files\dotnet\shared\Microsoft.NETCore.App\6.0.6\System.Runtime.InteropServices.dll
LoadedModule[158]=C:\Program Files\dotnet\shared\Microsoft.NETCore.App\6.0.6\System.Runtime.CompilerServices.Unsafe.dll
LoadedModule[159]=C:\Program Files\dotnet\shared\Microsoft.NETCore.App\6.0.6\System.Net.Primitives.dll
LoadedModule[160]=C:\Program Files\dotnet\shared\Microsoft.NETCore.App\6.0.6\System.Memory.dll
LoadedModule[161]=C:\Program Files\dotnet\shared\Microsoft.NETCore.App\6.0.6\System.Net.Sockets.dll
LoadedModule[162]=C:\Program Files\dotnet\shared\Microsoft.NETCore.App\6.0.6\Microsoft.Win32.Primitives.dll
LoadedModule[163]=C:\Program Files\dotnet\shared\Microsoft.NETCore.App\6.0.6\System.Diagnostics.Tracing.dll
LoadedModule[164]=C:\WINDOWS\system32\mswsock.dll
LoadedModule[165]=C:\WINDOWS\system32\wshunix.dll
LoadedModule[166]=C:\Program Files\dotnet\shared\Microsoft.NETCore.App\6.0.6\System.Threading.dll
LoadedModule[167]=C:\Program Files\dotnet\shared\Microsoft.NETCore.App\6.0.6\System.Threading.Overlapped.dll
...SNIPPED
FriendlyEventName=Stopped responding and was closed
ConsentKey=AppHangXProcB1
AppName=REAPER
AppPath=C:\Program Files\REAPER (x64)\reaper.exe
ReportDescription=A problem caused this program to stop interacting with Windows.
NsPartner=windows
NsGroup=windows8
ApplicationIdentity=7BDB38C149AC1CE8DD0FEAC701E2C68F
MetadataHash=344491907
jamsoft commented 2 years ago

It's almost like the plugin code isn't letting go of the main thread or something.

jamsoft commented 2 years ago

LOL, I hope all this isn't putting you off ...

P R O G R E S S!

I've hardened up my code a bit.

UDP listening is now started explicitly on a thread:

Thread t = new Thread(() => DoReceiveAsync(_socket, cancelToken));
t.Start();

And listening with:

private async void DoReceiveAsync(Socket udpSocket, CancellationToken cancelToken)
{
    byte[] buffer = GC.AllocateArray<byte>(length: 65527, pinned: true);
    Memory<byte> bufferMem = buffer.AsMemory();
    while (!cancelToken.IsCancellationRequested)
    {
        try
        {
            var result = await udpSocket.ReceiveFromAsync(bufferMem, SocketFlags.None, _remoteEndpoint);

            var recvPacket = bufferMem.Slice(0, result.ReceivedBytes);

            //MyReaperPlugin.ShowConsoleMsg($"RECV: {udpSocket.RemoteEndPoint}, {Encoding.ASCII.GetString(recvPacket.ToArray(), 0, recvPacket.Length)}");
            var filePath = Encoding.ASCII.GetString(recvPacket.ToArray(), 0, recvPacket.Length);

            MyReaperPlugin.InsertMedia(filePath);
        }
        catch (SocketException)
        {
            // Socket exception means we are finished.
            break;
        }
    }
}

Now I thought it was the while() loop causing havoc with the threading but no. I'm a little confused actually. You can see that I'm now not calling the ShowConsoleMsg. I can repeatedly call this with file paths and they are being inserted into Reaper!!!!!

BUT

If I dare call, ShowConsoleMsg outside of the Initialize method, the whole thing implodes and hangs Reaper. I just don't understand that at all, the insert media method handles the string in exactly the same way and works, repeatedly. I can insert media all day long but the console method blows up. Odd.

Any ideas?

toby11 commented 9 months ago

Hi did you work out what was causing the issue? I am thinking of creating a c# plugin for reaper but wanted to see how far you got first!