将字段作为参数传递与将方法调用作为参数传递有何不同?
How does passing field as argument differ from passing method call as argument?
我正在一个带有打印机的智能设备上编写 .Net CE 应用程序。我在 StringBuilder
对象中收集我的数据,然后尝试打印它。这是我的打印方式
var receipt = new StringBuilder();
// ...
Printer.getInstance().print(receipt.ToString(), (int) Printer.TextAlign.Left, 0, 24, false);
Printer
class 是从 DLL
导入的。应用程序在 print
行引发非托管异常并崩溃。但是当我将代码更改为此
var receipt = new StringBuilder();
// ...
var str = receipt.ToString();
Printer.getInstance().print(str, (int) Printer.TextAlign.Left, 0, 24, false);
一切正常。 StringBuilder
的评价怎么可能影响流量?
这是我的 Printer.dll
的方法(反编译)
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();
}
这是它从另一个 DLL
调用的方法。不幸的是,DotPeek
不会反编译它。
[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();
编辑:感谢 Kevin Gosse,我发现问题只存在于 Debug
模式。所以我现在的问题是,调试模式评估与正常执行有何不同。虽然我知道这可能是题外话,但如果有人可以分享相关文档,我会很高兴。
在发布模式下,您的代码的两个版本是相同的。
在调试模式下,存在细微差别,因为 str
的生命周期将延长到当前方法结束(用于调试目的)。
所以这段代码处于调试模式:
var receipt = new StringBuilder();
// ...
var str = receipt.ToString();
Printer.getInstance().print(str, (int) Printer.TextAlign.Left, 0, 24, false);
在发布模式下等同于:
var receipt = new StringBuilder();
// ...
var str = receipt.ToString();
Printer.getInstance().print(str, (int) Printer.TextAlign.Left, 0, 24, false);
GC.KeepAlive(str);
当您的字符串被提供给本机代码时,GC 不再跟踪它。所以可能会被采集,导致原生部分出错。
理论上,当使用常见类型(如字符串)时,编组器会自动保护您免受此类情况的影响,如下所述:https://docs.microsoft.com/en-us/dotnet/framework/interop/copying-and-pinning?redirectedfrom=MSDN。这是另一个难题,但 .net compact framework 可能具有不同的编组器并且不会自动保护您。不幸的是,很难找到关于该主题的具体文档。
当您反编译该方法时,让我感到惊讶的是本机调用实际上接收的不是原始字符串,而是 text.TrimEnd()
。这意味着原始值的生命周期不应有任何影响(因为本机代码接收到不同的字符串)。然而,结果却是.TrimEnd
returns the original string when there's nothing to trim。当您添加 space 时,即使使用延长字符串生命周期的代码版本,它也开始崩溃。那是因为现在你正在延长错误字符串的生命周期(因为 TrimEnd()
将 return 一个不同的实例,而这就是本机代码将使用的实例)。
我相信能在 Release 中运行的代码纯属运气。也许它只是改变了垃圾收集器的时间,而你没有 运行 进入那个特定的问题,但它可能会在未来引起问题。出于谨慎,我建议您:
- Trim 字符串 在 调用
Printer.getInstance().print
之前
- 在调用
print
后调用 GC.KeepAlive
修剪后的字符串
- 如果您想更加安全,可以固定字符串而不是调用
GC.KeepAlive
我希望我能提供的不仅仅是理论,但我相信您正在 运行 了解 .net 紧凑框架的特性。如果在该主题上有更多经验的人阅读本文并可以提供更多信息,将不胜感激。
我正在一个带有打印机的智能设备上编写 .Net CE 应用程序。我在 StringBuilder
对象中收集我的数据,然后尝试打印它。这是我的打印方式
var receipt = new StringBuilder();
// ...
Printer.getInstance().print(receipt.ToString(), (int) Printer.TextAlign.Left, 0, 24, false);
Printer
class 是从 DLL
导入的。应用程序在 print
行引发非托管异常并崩溃。但是当我将代码更改为此
var receipt = new StringBuilder();
// ...
var str = receipt.ToString();
Printer.getInstance().print(str, (int) Printer.TextAlign.Left, 0, 24, false);
一切正常。 StringBuilder
的评价怎么可能影响流量?
这是我的 Printer.dll
的方法(反编译)
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();
}
这是它从另一个 DLL
调用的方法。不幸的是,DotPeek
不会反编译它。
[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();
编辑:感谢 Kevin Gosse,我发现问题只存在于 Debug
模式。所以我现在的问题是,调试模式评估与正常执行有何不同。虽然我知道这可能是题外话,但如果有人可以分享相关文档,我会很高兴。
在发布模式下,您的代码的两个版本是相同的。
在调试模式下,存在细微差别,因为 str
的生命周期将延长到当前方法结束(用于调试目的)。
所以这段代码处于调试模式:
var receipt = new StringBuilder();
// ...
var str = receipt.ToString();
Printer.getInstance().print(str, (int) Printer.TextAlign.Left, 0, 24, false);
在发布模式下等同于:
var receipt = new StringBuilder();
// ...
var str = receipt.ToString();
Printer.getInstance().print(str, (int) Printer.TextAlign.Left, 0, 24, false);
GC.KeepAlive(str);
当您的字符串被提供给本机代码时,GC 不再跟踪它。所以可能会被采集,导致原生部分出错。
理论上,当使用常见类型(如字符串)时,编组器会自动保护您免受此类情况的影响,如下所述:https://docs.microsoft.com/en-us/dotnet/framework/interop/copying-and-pinning?redirectedfrom=MSDN。这是另一个难题,但 .net compact framework 可能具有不同的编组器并且不会自动保护您。不幸的是,很难找到关于该主题的具体文档。
当您反编译该方法时,让我感到惊讶的是本机调用实际上接收的不是原始字符串,而是 text.TrimEnd()
。这意味着原始值的生命周期不应有任何影响(因为本机代码接收到不同的字符串)。然而,结果却是.TrimEnd
returns the original string when there's nothing to trim。当您添加 space 时,即使使用延长字符串生命周期的代码版本,它也开始崩溃。那是因为现在你正在延长错误字符串的生命周期(因为 TrimEnd()
将 return 一个不同的实例,而这就是本机代码将使用的实例)。
我相信能在 Release 中运行的代码纯属运气。也许它只是改变了垃圾收集器的时间,而你没有 运行 进入那个特定的问题,但它可能会在未来引起问题。出于谨慎,我建议您:
- Trim 字符串 在 调用
Printer.getInstance().print
之前
- 在调用
print
后调用 - 如果您想更加安全,可以固定字符串而不是调用
GC.KeepAlive
GC.KeepAlive
修剪后的字符串
我希望我能提供的不仅仅是理论,但我相信您正在 运行 了解 .net 紧凑框架的特性。如果在该主题上有更多经验的人阅读本文并可以提供更多信息,将不胜感激。