question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

SteamNetworkingMessage_t crashes on Release() / SteamNetworkingMessage_t usage

See original GitHub issue

I am fully aware that there is an issue about this problem here, and I am fully aware that it regards the development of the library rather than the usage, which is what I need. After reading it multiple times I still can’t understand exactly how am I supposed to clear messages, since Release() method still crashes Unity on version 14.0.0. My code looks like this:

MessageCount = SteamNetworkingSockets.ReceiveMessagesOnPollGroup(PollGroup, PointerBuffer, 32);
for (int i = 0; i < MessageCount; ++i)
{
SteamNetworkingMessage_t NewMessage = Marshal.PtrToStructure<SteamNetworkingMessage_t>(PointerBuffer[i]);
Marshal.Copy(NewMessage.m_pData, Buffer, 0, NewMessage.m_cbSize);
FillSize = NewMessage.m_cbSize;

// Something happens here like parsing/serialization etc

NewMessage.Release();
}

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Comments:7 (2 by maintainers)

github_iconTop GitHub Comments

5reactions
SingleAccretioncommented, Jan 15, 2021

@rlabrecque So, as you’ve correctly identified, the crash is the direct consequence of the fact that the wrong pointer is being passed to the native function. SteamAPI_SteamNetworkingMessage_t_Release(IntPtr self) expects the argument to be a pointer to this, &message (it is an instance method in C++). Instead, the current implementation of SteamNetworkingMessage_t.Release passes m_pfnFreeData, which is presumably a copy-paste bug as I’ve found at least one other library doing it. m_pfnFreeData is actually meant for the reverse scenario: you would construct a message with a custom allocator (say, GC, new and pinned GCHandle in C#) and assign this field to a custom deallocator function (something like GCHandle.Release for that managed array), then pass that message back to native which would take care of calling that deallocation function as appropriate.

The fix might be as simple as just doing something like this in Release:

public void Release()
{
    fixed (SteamNetworkingMessage_t* thisPtr = &this)
    {
        NativeMethods.SteamAPI_SteamNetworkingMessage_t_Release((IntPtr)thisPtr);
    }
}

However, it is not 100% bulletproof as the native side may be expecting the exact same pointer that it passed to you via, e. g., ReceiveMessagesOnPollGroup or similar methods (full disclosure: I am commenting here after helping @AG4W figure this out, so I am not familiar with the library or the Steam API). You also cannot exactly “carry” this pointer in SteamNetworkingMessage_t, as it must be field-for-field compatible with the native version, as your users using Marshal.PtrToStructure are already relying on this (you might be able to play tricks with appending fields to the back or assigning the pointer to m_pfnFreeData, but I would not recommend this approach due its questionable reliability and maintainability characteristics). In my opinion, your best course of action to unblock users right now would be to:

  1. Expose the API releasing the pointer to the message. Something like this:
public class SteamNetworkingMessage_t
{
+    public static void ReleaseMessage(IntPtr pointerToMessage);
}
  1. Obsolete SteamNetworkingMessage_t.Release and point users to the new API in the obsoletion message.
  2. Hide SteamNetworkingMessage_t.Release via the use of [EditorBrowsable(EditorBrowsableState.Never)] attribute (optional, might not be worth it).

Going on a bit of a tangent here: the library seems to provide a managed wrapper over the Steam API, but it appears to be inconsistent it its design. In places, it exposes very low-level C-like primitives, as is the case here, where users have to manually deduce that they need to treat IntPtrs as SteamNetworkingMessage_t* and dereference them accordingly, while at the same time hiding the raw imports. You might consider either exposing the raw imports (preferably with blittable signatures instead of using expensive marshalling features like arrays with [Out, In] attributes) so that users who are already operating on that low level can consume them while not dealing with the complexities of packaging, platform and bitness differences, and/or exposing a higher level API that would use SafeHandles and such for messages, friendly names and signatures, etc.

1reaction
rlabrecquecommented, Nov 19, 2020

I think what we may want to be doing here is:

MessageCount = SteamNetworkingSockets.ReceiveMessagesOnPollGroup(PollGroup, PointerBuffer, 32);
for (int i = 0; i < MessageCount; ++i)
{
    SteamNetworkingMessage_t NewMessage = Marshal.PtrToStructure<SteamNetworkingMessage_t>(PointerBuffer[i]);
    NewMessage.m_pfnFreeData = PointerBuffer[i]; // This line ?
    Marshal.Copy(NewMessage.m_pData, Buffer, 0, NewMessage.m_cbSize);
    FillSize = NewMessage.m_cbSize;

    // Something happens here like parsing/serialization etc

    NewMessage.Release();
}

That’s what they do over here: https://github.com/nxrighthere/ValveSockets-CSharp/blob/656d236e381cb9b0c20c12795f1c9907e25cc62a/ValveSockets.cs#L648

One thing we could possibly do relatively easy is have a constructor that takes an IntPtr, and handles this stuff like so:

MessageCount = SteamNetworkingSockets.ReceiveMessagesOnPollGroup(PollGroup, PointerBuffer, 32);
for (int i = 0; i < MessageCount; ++i)
{
    SteamNetworkingMessage_t NewMessage = SteamNetworkingMessage_t(PointerBuffer[i]);
    NewMessage.SetData(Buffer); // Like: Marshal.Copy(NewMessage.m_pData, Buffer, 0, NewMessage.m_cbSize);

    // Something happens here like parsing/serialization etc

    NewMessage.Release();
}

We possibly want to make SteamNetworkingMessage_t IDisposable too?

I haven’t written “client” code using these myself yet, so let me know what you would like and we can make that happen. 👍

The ValveSockets-CSharp library does some clever performance stuff with pooling, and I’d like that to continue to be possible with Steamworks.NET as well.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Program only crashes as release build -- how to debug?
One technique to at least narrow down the problem is to use MessageBox() to display quick statements stating what part of the program...
Read more >
Works fine on DEBUG but crashes when running ...
Any time a MAUI/Xamarin app works fine in debug mode and crashes in release, the first thing to check is the linker and/or...
Read more >
Crashes | App quality
An Android app crashes whenever there's an unexpected exit caused by an unhandled exception or signal. An app that is written using Java...
Read more >
Android App suddenly crashes when compiling in Release ...
Hi My App was working fine when build to release mode, and both when downloading from Google play store or from a Android...
Read more >
Troubleshoot crash or freeze in Photoshop
Unable to open Photoshop? Photoshop crashes or freezes on launch? We're here to help! Fix common crash issues in 6 simple steps.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found