I am writing a .Net CE application on a Smart Device that has a printer on it. I collect my data in a StringBuilder object, and then try to print it. Here's how I print it
var receipt = new StringBuilder();
// ...
Printer.getInstance().print(receipt.ToString(), (int) Printer.TextAlign.Left, 0, 24, false);
Printer class is imported from a DLL. The application throws an unmanaged exception on the print line and crashes. But when I change my code to this
var receipt = new StringBuilder();
// ...
var str = receipt.ToString();
Printer.getInstance().print(str, (int) Printer.TextAlign.Left, 0, 24, false);
everything works fine. How is it even possible for the evalution of the StringBuilder affect the flow?
Here's a the methods from my Printer.dll (decompiled)
public int print(string text, int textAlign, int fontWeight, int fontSize, bool endLineFeed)
{
open();
Int32 prnReturn;
Printer.Prn_SetLang(1);
Printer.PRN_SetFont((byte) fontSize, (byte) fontSize, 0);
Printer.PRN_SetAlign(textAlign);
Printer.PRN_SetBold(fontWeight);
Prn_String(text.TrimEnd());
prnReturn = PRN_PrintAndWaitComplete();
if(endLineFeed)
printEndingLineFeed();
close();
return prnReturn;
}
public void open()
{
PRN_Open();
}
public void close()
{
PRN_Close();
}
private void printEndingLineFeed()
{
open();
//lineFeed(ENDING_LINE_FEED);
PRN_FeedLine(ENDING_LINE_FEED);
close();
}
and here are the methods that it calls from another DLL. Unfortunately, DotPeek doesn't decompile this.
[DllImport(PrinterDllName, SetLastError = true, EntryPoint = "PRN_FeedLine")]
public static extern Int32 PRN_FeedLine(Int32 pszData);
[DllImport(PrinterDllName, SetLastError = true, EntryPoint = "PRN_PrintAndWaitComplete")]
public static extern Int32 PRN_PrintAndWaitComplete();
Edit: Thanks to Kevin Gosse, I found out that the problem is only there in Debug mode. So my question now is, how does the debug mode evaluation differ from normal execution. Although I do understand that this might be off-topic, I'd be glad if someone could share a relevant piece of documentation.
In release mode, both versions of your code are identical.
In debug mode, there is a subtle difference as the lifetime of
strwill be extended until the end of the current method (for debugging purpose).So this code in debug mode:
Is equivalent to this in release mode:
When your string is given to the native code, it's not tracked by the GC anymore. Therefore, it could be collected which will cause errors in the native part.
In theory, the marshaller automatically protects you from such situations when using common types (such as string), as described here: https://learn.microsoft.com/en-us/dotnet/framework/interop/copying-and-pinning?redirectedfrom=MSDN. That's another puzzle, but it's possible that .net compact framework has a different marshaller and doesn't protect you automatically. Unfortunately it's hard to find specific documentation on the subject.
What surprised me when you decompiled the method is how the native call doesn't actually receive the original string but
text.TrimEnd(). It means that the lifetime of the original value shouldn't have any impact (since the native code receives a different string). However, it turns out that.TrimEndreturns the original string when there's nothing to trim. When you added a space, it started crashing even with the version of the code that extends the lifetime of the string. That's because now you're extending the lifetime of the wrong string (sinceTrimEnd()will return a different instance, and that's the one that will be used by the native code).I believe that the code working in Release is pure luck. Maybe it just changes the timing of the garbage collector and you don't run into that specific issue, but it could cause problems in the future. Out of caution, I would suggest you to:
Printer.getInstance().printGC.KeepAliveon the trimmed string after the call toprintGC.KeepAliveI wish I could give more than theories, but I believe you're running into specificities of .net compact framework. If somebody with more experience on the subject reads this and could give more information, that would be much appreciated.