用 C# 解析和运行 JavaScript 代码
最编程
2024-04-23 10:35:11
...
如果想在C#编程中解析并运行JavaScript代码,常见的方式有两种:
- 利用COM组件“Microsoft Script Control”,可参见:C#使用技巧之调用JS脚本方法一
- 利用JScript.net,可参见:C#使用技巧之调用JS脚本方法二 或 从命令行编译 JScript 代码
这两种方式都能达到在C#中执行JavaScript代码的效果,不过各有利弊。 方法一对程序运行环境有要求,程序只能以x86为目标平台。方法二实际是将JavaScript代码编译成为了.NET程序集,对JavaScript代码有严格的格式要求,但是可以在任意平台发布(支持跨平台)。 除此之外,其实还有另一种方法来实现这个功能,兼容x86和x64平台,不过仅限于在Windows下使用。而且对JavaScript代码没有严格的格式要求。
Windows Script Engines
Microsoft® Windows® Script Interfaces introduce a new way for an application to add scripting and OLE Automation capabilities. With the advent of the interfaces, hosts can call upon disparate scripting engines from multiple sources and vendors to perform scripting between components. The implementation of the script itself—language, syntax, persistent format, execution model, and so on—is left to the script vendor. The designers have taken care to allow hosts that rely on Windows Script to use arbitrary language back ends.
以上是微软对Windows Script Engines的官方解释,详细信息可以参看:Microsoft Windows Script Interfaces - Introduction。简单的说,Windows Script Engines 提供了一种新的方式,让我们可以为应用程序添加脚本功能。 所以我们只需要将Windows Script Engines进行包装即可。
调用示例
1、直接执行表达式:
Console.WriteLine(ScriptEngine.Eval("jscript", "1+2/3"));
将会输出:1.66666666666667
2、调用有参数的方法:
using (ScriptEngine engine = new ScriptEngine("jscript")) { ParsedScript parsed = engine.Parse("function MyFunc(x){return 1+2+x}"); Console.WriteLine(parsed.CallMethod("MyFunc", 3)); }
将会输出:6
3、调用可选参数NameItem方法:
using (ScriptEngine engine = new ScriptEngine("jscript")) { ParsedScript parsed = engine.Parse("function MyFunc(x){return 1+2+x+My.Num}"); MyItem item = new MyItem(); item.Num = 4; engine.SetNamedItem("My", item); Console.WriteLine(parsed.CallMethod("MyFunc", 3)); } [ComVisible(true)] // Script engines are COM components. public class MyItem { public int Num { get; set; } }
将会输出:10
如果宿主服务中安装有IE9+的浏览器,那么我们可以调用更快的JavaScript引擎"chakra":
using (ScriptEngine engine = new ScriptEngine("{16d51579-a30b-4c8b-a276-0ff4dc41e755}")) { // continue with chakra now }
ScriptEngine.cs
上面只是部分的调用示例,但是应该可以满足80%的业务需求。上面的代码依赖于ScriptEngine类,完整的ScriptEngine.cs代码如下:
/// <summary> /// Represents a Windows Script Engine such as JScript, VBScript, etc. /// </summary> public sealed class ScriptEngine : IDisposable { /// <summary> /// The name of the function used for simple evaluation. /// </summary> public const string MethodName = "EvalMethod"; /// <summary> /// The default scripting language name. /// </summary> public const string DefaultLanguage = JavaScriptLanguage; /// <summary> /// The JavaScript or jscript scripting language name. /// </summary> public const string JavaScriptLanguage = "javascript"; /// <summary> /// The javascript or jscript scripting language name. /// </summary> public const string VBScriptLanguage = "vbscript"; /// <summary> /// The chakra javascript engine CLSID. The value is {16d51579-a30b-4c8b-a276-0ff4dc41e755}. /// </summary> public const string ChakraClsid = "{16d51579-a30b-4c8b-a276-0ff4dc41e755}"; private IActiveScript _engine; private IActiveScriptParse32 _parse32; private IActiveScriptParse64 _parse64; internal ScriptSite Site; private Version _version; private string _name; [Guid("BB1A2AE1-A4F9-11cf-8F20-00805F2CD064"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] private interface IActiveScript { [PreserveSig] int SetScriptSite(IActiveScriptSite pass); [PreserveSig] int GetScriptSite(Guid riid, out IntPtr site); [PreserveSig] int SetScriptState(ScriptState state); [PreserveSig] int GetScriptState(out ScriptState scriptState); [PreserveSig] int Close(); [PreserveSig] int AddNamedItem(string name, ScriptItem flags); [PreserveSig] int AddTypeLib(Guid typeLib, uint major, uint minor, uint flags); [PreserveSig] int GetScriptDispatch(string itemName, out IntPtr dispatch); [PreserveSig] int GetCurrentScriptThreadID(out uint thread); [PreserveSig] int GetScriptThreadID(uint win32ThreadId, out uint thread); [PreserveSig] int GetScriptThreadState(uint thread, out ScriptThreadState state); [PreserveSig] int InterruptScriptThread(uint thread, out System.Runtime.InteropServices.ComTypes.EXCEPINFO exceptionInfo, uint flags); [PreserveSig] int Clone(out IActiveScript script); } [Guid("4954E0D0-FBC7-11D1-8410-006008C3FBFC"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] private interface IActiveScriptProperty { [PreserveSig] int GetProperty(int dwProperty, IntPtr pvarIndex, out object pvarValue); [PreserveSig] int SetProperty(int dwProperty, IntPtr pvarIndex, ref object pvarValue); } [Guid("DB01A1E3-A42B-11cf-8F20-00805F2CD064"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] private interface IActiveScriptSite { [PreserveSig] int GetLCID(out int lcid); [PreserveSig] int GetItemInfo(string name, ScriptInfo returnMask, out IntPtr item, IntPtr typeInfo); [PreserveSig] int GetDocVersionString(out string version); [PreserveSig] int OnScriptTerminate(object result, System.Runtime.InteropServices.ComTypes.EXCEPINFO exceptionInfo); [PreserveSig] int OnStateChange(ScriptState scriptState); [PreserveSig] int OnScriptError(IActiveScriptError scriptError); [PreserveSig] int OnEnterScript(); [PreserveSig] int OnLeaveScript(); } [Guid("EAE1BA61-A4ED-11cf-8F20-00805F2CD064"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] private interface IActiveScriptError { [PreserveSig] int GetExceptionInfo(out System.Runtime.InteropServices.ComTypes.EXCEPINFO exceptionInfo); [PreserveSig] int GetSourcePosition(out uint sourceContext, out int lineNumber, out int characterPosition); [PreserveSig] int GetSourceLineText(out string sourceLine); } [Guid("BB1A2AE2-A4F9-11cf-8F20-00805F2CD064"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] private interface IActiveScriptParse32 { [PreserveSig] int InitNew(); [PreserveSig] int AddScriptlet(string defaultName, string code, string itemName, string subItemName, string eventName, string delimiter, IntPtr sourceContextCookie, uint startingLineNumber, ScriptText flags, out string name, out System.Runtime.InteropServices.ComTypes.EXCEPINFO exceptionInfo); [PreserveSig] int ParseScriptText(string code, string itemName, IntPtr context, string delimiter, int sourceContextCookie, uint startingLineNumber, ScriptText flags, out object result, out System.Runtime.InteropServices.ComTypes.EXCEPINFO exceptionInfo); } [Guid("C7EF7658-E1EE-480E-97EA-D52CB4D76D17"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] private interface IActiveScriptParse64 { [PreserveSig] int InitNew(); [PreserveSig] int AddScriptlet(string defaultName, string code, string itemName, string subItemName, string eventName, string delimiter, IntPtr sourceContextCookie, uint startingLineNumber, ScriptText flags, out string name, out System.Runtime.InteropServices.ComTypes.EXCEPINFO exceptionInfo); [PreserveSig] int ParseScriptText(string code, string itemName, IntPtr context, string delimiter, long sourceContextCookie, uint startingLineNumber, ScriptText flags, out object result, out System.Runtime.InteropServices.ComTypes.EXCEPINFO exceptionInfo); } [Flags] private enum ScriptText { None = 0, //DelayExecution = 1, //IsVisible = 2, IsExpression = 32, IsPersistent = 64, //HostManageSource = 128 } [Flags] private enum ScriptInfo { //None = 0, //IUnknown = 1, ITypeInfo = 2 } [Flags] private enum ScriptItem { //None = 0, IsVisible = 2, IsSource = 4, //GlobalMembers = 8, //IsPersistent = 64, //CodeOnly = 512, //NoCode = 1024 } private enum ScriptThreadState { //NotInScript = 0, //Running = 1 } private enum ScriptState { Uninitialized = 0, Started = 1, Connected = 2, Disconnected = 3, Closed = 4, Initialized = 5 } private const int TYPE_E_ELEMENTNOTFOUND = unchecked((int)(0x8002802B)); private const int E_NOTIMPL = -2147467263; /// <summary> /// Determines if a script engine with the input name exists. /// </summary> /// <param name="language">The language.</param> /// <returns>true if the engine exists; false otherwise.</returns> public static Version GetVersion(string language) { if (language == null) throw new ArgumentNullException("language"); Type engine; Guid clsid; if (Guid.TryParse(language, out clsid)) { engine = Type.GetTypeFromCLSID(clsid, false); } else { engine = Type.GetTypeFromProgID(language, false); } if (engine == null) return null; IActiveScript scriptEngine = Activator.CreateInstance(engine) as IActiveScript; if (scriptEngine == null) return null; IActiveScriptProperty scriptProperty = scriptEngine as IActiveScriptProperty; if (scriptProperty == null) return new Version(1, 0, 0, 0); int major = GetProperty(scriptProperty, SCRIPTPROP_MAJORVERSION, 0); int minor = GetProperty(scriptProperty, SCRIPTPROP_MINORVERSION, 0); int revision = GetProperty(scriptProperty, SCRIPTPROP_BUILDNUMBER, 0); Version version = new Version(major, minor, Environment.OSVersion.Version.Build, revision); Marshal.ReleaseComObject(scriptProperty); Marshal.ReleaseComObject(scriptEngine); return version; } private static T GetProperty<T>(IActiveScriptProperty prop, int index, T defaultValue) { object value; if (prop.GetProperty(index, IntPtr.Zero, out value) != 0) return defaultValue; try { return (T)Convert.ChangeType(value, typeof(T)); } catch { return defaultValue; } } /// <summary> /// Initializes a new instance of the <see cref="ScriptEngine"/> class. /// </summary> /// <param name="language">The scripting language. Standard Windows Script engines names are 'jscript' or 'vbscript'.</param> public ScriptEngine(string language) { if (language == null) throw new ArgumentNullException("language"); Type engine; Guid clsid; if (Guid.TryParse(language, out clsid)) { engine = Type.GetTypeFromCLSID(clsid, true); } else { engine = Type.GetTypeFromProgID(language, true); } _engine = Activator.CreateInstance(engine) as IActiveScript; if (_engine == null) throw new ArgumentException(language + " is not an Windows Script Engine", "language"); Site = new ScriptSite(); _engine.SetScriptSite(Site); // support 32-bit & 64-bit process if (IntPtr.Size == 4) { _parse32 = (IActiveScriptParse32)_engine; _parse32.InitNew(); } else { _parse64 = (IActiveScriptParse64)_engine; _parse64.InitNew(); } } private const int SCRIPTPROP_NAME = 0x00000000; private const int SCRIPTPROP_MAJORVERSION = 0x00000001; private const int SCRIPTPROP_MINORVERSION = 0x00000002; private const int SCRIPTPROP_BUILDNUMBER = 0x00000003; /// <summary> /// Gets the engine version. /// </summary> /// <value> /// The version. /// </value> public Version Version { get { if (_version == null) { int major = GetProperty(SCRIPTPROP_MAJORVERSION, 0); int minor = GetProperty(SCRIPTPROP_MINORVERSION, 0); int revision = GetProperty(SCRIPTPROP_BUILDNUMBER, 0); _version = new Version(major, minor, Environment.OSVersion.Version.Build, revision); } return _version; } } /// <summary> /// Gets the engine name. /// </summary> /// <value> /// The name. /// </value> public string Name { get { if (_name == null) { _name = GetProperty(SCRIPTPROP_NAME, string.Empty); } return _name; } } /// <summary> /// Gets a script engine property. /// </summary> /// <typeparam name="T">The expected property type.</typeparam> /// <param name="index">The property index.</param> /// <param name="defaultValue">The default value if not found.</param> /// <returns>The value of the property or the default value.</returns> public T GetProperty<T>(int index, T defaultValue) { object value; if (!TryGetProperty(index, out value)) return defaultValue; try { return (T)Convert.ChangeType(value, typeof(T)); } catch { return defaultValue; } } /// <summary> /// Gets a script engine property. /// </summary> /// <param name="index">The property index.</param> /// <param name="value">The value.</param> /// <returns>true if the property was successfully got; false otherwise.</returns> public bool TryGetProperty(int index, out object value) { value = null; IActiveScriptProperty property = _engine as IActiveScriptProperty; if (property == null) return false; return property.GetProperty(index, IntPtr.Zero, out value) == 0; } /// <summary> /// Sets a script engine property. /// </summary> /// <param name="index">The property index.</param> /// <param name="value">The value.</param> /// <returns>true if the property was successfully set; false otherwise.</returns> public bool SetProperty(int index, object value) { IActiveScriptProperty property = _engine as IActiveScriptProperty; if (property == null) return false; return property.SetProperty(index, IntPtr.Zero, ref value) == 0; } /// <summary> /// Adds the name of a root-level item to the scripting engine's name space. /// </summary> /// <param name="name">The name. May not be null.</param> /// <param name="value">The value. It must be a ComVisible object.</param> public void SetNamedItem(string name, object value) { if (name == null) throw new ArgumentNullException("name"); _engine.AddNamedItem(name, ScriptItem.IsVisible | ScriptItem.IsSource); Site.NamedItems[name] = value; } internal class ScriptSite : IActiveScriptSite { internal ScriptException LastException; internal Dictionary<string, object> NamedItems = new Dictionary<string, object>(); int IActiveScriptSite.GetLCID(out int lcid) { lcid = Thread.CurrentThread.CurrentCulture.LCID; return 0; } int IActiveScriptSite.GetItemInfo(string name, ScriptInfo returnMask, out IntPtr item, IntPtr typeInfo) { item = IntPtr.Zero; if ((returnMask & ScriptInfo.ITypeInfo) == ScriptInfo.ITypeInfo) return E_NOTIMPL; object value; if (!NamedItems.TryGetValue(name, out value)) return TYPE_E_ELEMENTNOTFOUND; item = Marshal.GetIUnknownForObject(value); return 0; } int IActiveScriptSite.GetDocVersionString(out string version) { version = null; return 0; } int IActiveScriptSite.OnScriptTerminate(object result, System.Runtime.InteropServices.ComTypes.EXCEPINFO exceptionInfo) { return 0; } int IActiveScriptSite.OnStateChange(ScriptState scriptState) { return 0; } int IActiveScriptSite.OnScriptError(IActiveScriptError scriptError) { string sourceLine = null; try { scriptError.GetSourceLineText(out sourceLine); } catch { // happens sometimes... } uint sourceContext; int lineNumber; int characterPosition; scriptError.GetSourcePosition(out sourceContext, out lineNumber, out characterPosition); lineNumber++; characterPosition++; System.Runtime.InteropServices.ComTypes.EXCEPINFO exceptionInfo; scriptError.GetExceptionInfo(out exceptionInfo); string message; if (!string.IsNullOrEmpty(sourceLine)) { message = "Script exception: {1}. Error number {0} (0x{0:X8}): {2} at line {3}, column {4}. Source line: '{5}'."; } else { message = "Script exception: {1}. Error number {0} (0x{0:X8}): {2} at line {3}, column {4}."; } LastException = new ScriptException(string.Format(message, exceptionInfo.scode, exceptionInfo.bstrSource, exceptionInfo.bstrDescription, lineNumber, characterPosition, sourceLine)); LastException.Column = characterPosition; LastException.Description = exceptionInfo.bstrDescription; LastException.Line = lineNumber; LastException.Number = exceptionInfo.scode; LastException.Text = sourceLine; return 0; } int IActiveScriptSite.OnEnterScript() { LastException = null; return 0; } int IActiveScriptSite.OnLeaveScript() { return 0; } } /// <summary> /// Evaluates an expression using the specified language. /// </summary> /// <param name="language">The language.</param> /// <param name="expression">The expression. May not be null.</param> /// <returns>The result of the evaluation.</returns> public static object Eval(string language, string expression) { return Eval(language, expression, null); } /// <summary> /// Evaluates an expression using the specified language, with an optional array of named items. /// </summary> /// <param name="language">The language.</param> /// <param name="expression">The expression. May not be null.</param> /// <param name="namedItems">The named items array.</param> /// <returns>The result of the evaluation.</returns> public static object Eval(string language, string expression, params KeyValuePair<string, object>[] namedItems) { if (language == null) throw new ArgumentNullException("language"); if (expression == null) throw new ArgumentNullException("expression"); using (ScriptEngine engine = new ScriptEngine(language)) { if (namedItems != null) { foreach (KeyValuePair<string, object> kvp in namedItems) { engine.SetNamedItem(kvp.Key, kvp.Value); } } return engine.Eval(expression); } } /// <summary> /// Evaluates an expression. /// </summary> /// <param name="expression">The expression. May not be null.</param> /// <returns>The result of the evaluation.</returns> public object Eval(string expression) { if (expression == null) throw new ArgumentNullException("expression"); return Parse(expression, true); } /// <summary> /// Parses the specified text and returns an object that can be used for evaluation. /// </summary> /// <param name="text">The text to parse.</param> /// <returns>An instance of the ParsedScript class.</returns> public ParsedScript Parse(string text) { if (text == null) throw new ArgumentNullException("text"); return (ParsedScript)Parse(text, false); } private object Parse(string text, bool expression) { const string varName = "x___"; object result; _engine.SetScriptState(ScriptState.Connected); ScriptText flags = ScriptText.None; if (expression) { flags |= ScriptText.IsExpression; } try { // immediate expression computation seems to work only for 64-bit // so hack something for 32-bit... System.Runtime.InteropServices.ComTypes.EXCEPINFO exceptionInfo; if (_parse32 != null) { if (expression) { // should work for jscript & vbscript at least... text = varName + "=" + text; } _parse32.ParseScriptText(text, null, IntPtr.Zero, null, 0, 0, flags, out result, out exceptionInfo); } else { _parse64.ParseScriptText(text, null, IntPtr.Zero, null, 0, 0, flags, out result, out exceptionInfo); } } catch { if (Site.LastException != null) throw Site.LastException; throw; } IntPtr dispatch; if (expression) { // continue our 32-bit hack... if (_parse32 != null) { _engine.GetScriptDispatch(null, out dispatch); object dp = Marshal.GetObjectForIUnknown(dispatch); try { return dp.GetType().InvokeMember(varName, BindingFlags.GetProperty, null, dp, null); } catch { if (Site.LastException != null) throw Site.LastException; throw; } } return result; } _engine.GetScriptDispatch(null, out dispatch); ParsedScript parsed = new ParsedScript(this, dispatch); return parsed; } /// <summary> /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// </summary> public void Dispose() { if (_parse32 != null) { Marshal.ReleaseComObject(_parse32); _parse32 = null; } if (_parse64 != null) { Marshal.ReleaseComObject(_parse64); _parse64 = null; } if (_engine != null) { Marshal.ReleaseComObject(_engine); _engine = null; } } } public sealed class ParsedScript : IDisposable { private object _dispatch; private readonly ScriptEngine _engine; internal ParsedScript(ScriptEngine engine, IntPtr dispatch) { _engine = engine; _dispatch = Marshal.GetObjectForIUnknown(dispatch); } public object CallMethod(string methodName, params object[] arguments) { if (_dispatch == null) throw new InvalidOperationException(); if (methodName == null) throw new ArgumentNullException("methodName"); try { return _dispatch.GetType().InvokeMember(methodName, BindingFlags.InvokeMethod, null, _dispatch, arguments); } catch { if (_engine.Site.LastException != null) throw _engine.Site.LastException; throw; } } void IDisposable.Dispose() { if (_dispatch != null) { Marshal.ReleaseComObject(_dispatch); _dispatch = null; } } } [Serializable] public class ScriptException : Exception { public ScriptException() : base("Script Exception") { } public ScriptException(string message) : base(message) { } public ScriptException(Exception innerException) : base(null, innerException) { } public ScriptException(string message, Exception innerException) : base(message, innerException) { } protected ScriptException(SerializationInfo info, StreamingContext context) : base(info, context) { } public string Description { get; internal set; } public int Line { get; internal set; } public int Column { get; internal set; } public int Number { get; internal set; } public string Text { get; internal set; } }
提示:该方法同样可以用来执行VBScript代码。
推荐阅读
-
用 C# 解析和运行 JavaScript 代码
-
用 C# 解析和编码 CJ-T188 水表协议和 DL-T645 电表协议。
-
Android 开发中 nodpi、xhdpi、hdpi、mdpi、ldpi 的概念 - 术语和概念 屏幕尺寸 屏幕的物理尺寸,基于屏幕的对角线长度(如 2.8 英寸、3.5 英寸)。 简而言之,安卓系统将所有屏幕尺寸简化为三大类:大、普通和小。 程序可以为这三种屏幕尺寸提供三种不同的布局选项,然后系统会以合适的方式将布局选项呈现到相应的屏幕上,这个过程不需要程序员用代码进行干预。 屏幕纵横比 屏幕的物理长度与物理宽度之比。程序只需使用系统提供的资源分类器 long(长)和 notlong(不长),就能为具有特定长宽比的屏幕提供配制材料。 分辨率 屏幕的像素总数。请注意,分辨率并不意味着长宽比,尽管在大多数情况下,分辨率表示为 "宽度 x 长度"。在安卓系统中,程序一般不直接处理分辨率。 密度 根据屏幕分辨率,沿屏幕宽度和长度排列的像素数量。 密度较低的屏幕在长度和宽度方向上的像素都相对较少,而密度较高的屏幕通常会在同一区域内排列很多甚至非常非常多的像素。屏幕的密度非常重要;例如,一个界面元素(如按钮)的长度和宽度以像素为单位,在低密度屏幕上会显得很大,但在高密度屏幕上就会显得很小。 独立于密度的像素(DIP)是指程序用来定义界面元素的抽象意义上的像素。它作为一个与实际密度无关的单位,帮助程序员构建布局方案(界面元素的宽度、高度和位置)。 与密度无关的像素在逻辑上与像素密度为 160 DPI 的屏幕上的像素大小相同,而 160 DPI 是安卓平台默认的显示设备。在运行时,平台会以目标屏幕的密度为基准,"透明 "地处理所有所需的 DIP 缩放操作。要将与密度无关的像素转换为屏幕像素,可以使用一个简单的公式:像素 = DIP * (密度 / 160)。例如,在 240 DPI 的屏幕上,1 个 DIP 等于 1.5 个物理像素。强烈建议使用 DIP 来定义程序界面的布局,因为这样可以确保用户界面在所有分辨率的屏幕上都能正常显示。 为了简化程序员在面对各种分辨率时的麻烦,也为了让各种分辨率的平台都能直接运行这些程序,Android 平台将所有屏幕以密度和分辨率作为分类方式,分别分为三类:- 三大尺寸:大、普通、小;- 三种不同密度:高(hdpi)、中(mdpi)和低(ldpi)。DPI 表示 "每英寸点数",即每英寸的像素数。如果需要,程序可以为不同的屏幕尺寸提供不同的资源(主要是布局),为不同的屏幕密度提供不同的资源(主要是位图)。除此之外,程序无需对屏幕尺寸或密度进行任何额外处理。执行时,平台会根据屏幕本身的尺寸和密度特性自动加载相应的资源,并将其从逻辑像素(DIP,用于定义界面布局)转换为屏幕上的物理像素。
-
用简单易懂的方式解析单链表上的冒泡、快排、选择、插入和归并五种排序算法,配合详细图解与代码示例
-
用JAVA、Vue和SpringBoot打造的班级签到系统 - 重要部分:核心代码解析
-
用C#和Visual Studio 2010打造源代码编解码器
-
用HTML5 Canvas和JavaScript联手打造生动的动态星空背景效果 - 从代码实现开始
-
详细解析和代码演示:用D2L实现线性回归
-
Grid++Report 锐浪报表开发常见问题解答集锦-报表设计 问:怎样在设计时打印预览报表? 答:为了及时查看报表的设计效果,Grid++Report 报表设计应用程序提供了四种查看视图:普通视图、页面视图、预览视图与查询视图。通过窗口下边的 Tab 按钮可以在四种视图中任意切换。在预览视图中查看报表的打印预览效果,在查询视图中查看报表的查询显示效果。如果在报表的记录集提供了数据源连接串与查询 SQL,在进入预览视图与查询视图时会利用数据源连接串与查询 SQL 从数据源中自动取数,否则 Grid++Report 将自动生成模拟数据进行模拟打印预览与查询显示。注意:在预览视图与查询视图中看到的报表运行结果有可能与在你程序中的最终运行结果有差异,因为在报表的生成过程中我们可以在程序中对报表的生成行为进行一定的控制。 问:怎样用 Grid++Report 设计交叉表? 答:Grid++Report 没有提供专门实现交叉表的功能,其它的报表构件提供的交叉表功能一般也比较死板和功能有限。利用 Grid++Report 的编程接口可以做出灵活多变,功能丰富的交叉表。示例程序 CrossTab 就是一个实现交叉表的例子程序,认真领会此例子程序,你就可以做出自己想要各种交叉表,并能提取一些共用代码,便于重复使用。 问:怎样设置整个报表的缺省字体? 答:设置报表主对象的字体属性,也就是设置了整个报表的缺省字体。如果改变报表主对象的字体属性,则没有专门的设置字体属性的子对象的字体属性也跟随改变。同样每个报表节与明细网格也有字体属性,他们的字体属性也就是其拥有的子对象的缺省字体。 问:怎样在打印时限制一页的输出行数? 答:设定明细网格的内容行的‘每页行数(RowsPerPage)’属性即可。另外要注意‘调节行高(AdjustRowHeight)’属性值:为真时根据页面的输出高度自动调整行的高度,使整个页面的输出区域充满。为假时按设计时的高度输出行。 问:怎样显示中文大写金额? 答:将对象的“格式(Format)”属性设为 “$$” 及可,可以设置格式的对象有:字段(IGRField)、参数(IGRParameter)、系统变量(IGRSystemVarBox)与综合文字框(IGRMemoBox),其中综合文字框是在报表式上设格式。 问:能否实现自定义纸张与票据打印? 答:Grid++Report 完全支持自定义纸张的打印,只要在报表设定时在页面设置中选定自定义纸张,并指定准确的纸张尺寸。当然要在最终输出时得道合适的打印结果,输出打印机必须支持自定义纸张打印。Windows2000/XP/2003 操作系统上可以在打印机上定义自定义纸张,也可以采用这种方式实现自定义纸张打印。 问:怎样实现 0 值不打印? 答:直接设置格式串就可以,在“数字格式”设置对话框中选定“0 不显示”,就会得到合适的格式串。也可以通过直接录入格式串来指定 0 不显示,但格式串必须符合 Grid++Report 的规定格式。另一种实现办法是在报表获取明细记录数据时,在 BeforePostRecord 事件中将值为零的字段设为空,调用字段的 Clear 方法将字段置为空。 问:怎样实现多栏报表? 答:在明细网格上设‘页栏数(PageColumnCount)’属性值大于 1 即可。通过 Grid++Report 的“页栏输出顺序”还可以指定多栏报表的输出顺序是“先从上到下”还是“先从左到右”。 问:如何实现票据套打? 答:Grid++Report 为实现票据套打做了很多专门的安排:报表设计器提供了页面设计模式,按照设定的纸张尺寸显示设计面板,如果将空白票据的扫描图设为设计背景图,在定位报表内容的输出位置会非常方便。报表部件可以设定打印类别,非套打输出的内容在套打打印模式下就不会输出。 问:Grid++Report 有没有横向分页功能? 答:回答是肯定的,在列的总宽度超过打印页面的输出宽度时,Grid++Report 可以另起新页输出剩余的列,如果左边存在锁定列,锁定列可以在后面的新页中重复输出,这样可以保证关键数据列在每一页都有输出。仔细体会 Grid++Report 提供的多种打印适应策略,选用最合适的方式。Grid++Report 的多种打印适应策略为开发动态报表提供了很好的支持。 问:怎样实现报表本页小计功能? 答:定义一个报表分组,将本分组定义为页分组,在本分组的分组头与分组尾上定义统计。页分组就是在每页产生一个分组项,在每页的上端与下端都会分别显示页分组的分组头与分组尾,页分组不用定义分组依据字段。 报表运行 问:怎样与数据库建立连接? 答:如果在设计报表时指定了数据集的数据源连接串与查询 SQL 语句,Grid++Report 采用拉模式直接从数据源取得报表数据,Grid++Report 利用 OLE DB 从数据源取数,OLE DB 提供了广泛的数据源操作能力。如果 Grid++Report 的数据来源采用推模式,即 Grid++Report 不直接与数据库建立连接,各种编程语言/平台都提供了很好的数据库连接方式,并且易于操作,应用程序在报表主对象(IGridppReport)的 FetchRecord 事件中将数据传入,例子程序提供了各种编程语言填入数据的通用方法,对C++Builder 和 Delphi 还进行了专门的包装,直接关联 TDataSet 对象也可以将 TDataSet 对象中的数据传给报表。 问:打印时能否对打印纸张进行自适应?支持表格的折行打印吗? 答:Grid++Report 在打印时采用多种适应策略,通过设置明细网格(IGRDetailGrid)的‘打印策略(PrintAdaptMethod)’属性指定打印策略。(1)丢弃:按设计时列的宽度输出,超出范围的内容不显示。(2)绕行:按设计时列的宽度输出,如果在当前行不能完整输出,则另起新行进行输出。(3)缩放适应:对所有列的输出宽度进行按比例地缩放,使总宽度等于页面的输出宽度。(4)缩小适应:如果列的总宽度小于页面的输出宽度,对所有列的输出宽度进行按比例地缩小,使总宽度等于页面的输出宽度。(5)横向分页:超范围的列在新页中输出。(6)横向分页并重复锁定列。 问:如何改变缺省打印预览窗口的窗口标题? 答:改变报表主对象的‘标题(Title)’属性即可。 问:利用集合对象的编程接口取子对象的接口引用,但不是自己期望的结果。 答:Grid++Report中所有集合对象的下标索引都是从 1 开始,另按对象的名称查找对象的接口引用时,名称字符是不区分大小写的。 问:怎样在运行时控制报表中各个对象的可见性?即怎样在运行时显示或隐藏对象? 答:在报表主对象(GridppReport)的 SectionFormat 事件中设定相应报表子对象的可见(Visible)属性即可。 问:报表主对象重新载入数据,设计器中为什么没有反映新载入的数据? 答:应调用 IGRDesigner 的 Reload 方法。 问:怎样实现不进入打印预览界面,直接将报表打印出来?
-
【Netty】「萌新入门」(七)ByteBuf 的性能优化-堆内存的分配和释放都是由 Java 虚拟机自动管理的,这意味着它们可以快速地被分配和释放,但是也会产生一些开销。 直接内存需要手动分配和释放,因为它由操作系统管理,这使得分配和释放的速度更快,但是也需要更多的系统资源。 另外,直接内存可以映射到本地文件中,这对于需要频繁读写文件的应用程序非常有用。 此外,直接内存还可以避免在使用 NIO 进行网络传输时发生数据拷贝的情况。在使用传统的 I/O 时,数据必须先从文件或网络中读取到堆内存中,然后再从堆内存中复制到直接缓冲区中,最后再通过 SocketChannel 发送到网络中。而使用直接缓冲区时,数据可以直接从文件或网络中读取到直接缓冲区中,并且可以直接从直接缓冲区中发送到网络中,避免了不必要的数据拷贝和内存分配。 通过 ByteBufAllocator.DEFAULT.directBuffer 方法来创建基于直接内存的 ByteBuf: ByteBuf directBuf = ByteBufAllocator.DEFAULT.directBuffer(16); 通过 ByteBufAllocator.DEFAULT.heapBuffer 方法来创建基于堆内存的 ByteBuf: ByteBuf heapBuf = ByteBufAllocator.DEFAULT.heapBuffer(16); 注意: 直接内存是一种特殊的内存分配方式,可以通过在堆外申请内存来避免 JVM 堆内存的限制,从而提高读写性能和降低 GC 压力。但是,直接内存的创建和销毁代价昂贵,因此需要慎重使用。 此外,由于直接内存不受 JVM 垃圾回收的管理,我们需要主动释放这部分内存,否则会造成内存泄漏。通常情况下,可以使用 ByteBuffer.clear 方法来释放直接内存中的数据,或者使用 ByteBuffer.cleaner 方法来手动释放直接内存空间。 测试代码: public static void testCreateByteBuf { ByteBuf buf = ByteBufAllocator.DEFAULT.buffer(16); System.out.println(buf.getClass); ByteBuf heapBuf = ByteBufAllocator.DEFAULT.heapBuffer(16); System.out.println(heapBuf.getClass); ByteBuf directBuf = ByteBufAllocator.DEFAULT.directBuffer(16); System.out.println(directBuf.getClass); } 运行结果: class io.netty.buffer.PooledUnsafeDirectByteBuf class io.netty.buffer.PooledUnsafeHeapByteBuf class io.netty.buffer.PooledUnsafeDirectByteBuf 池化技术 在 Netty 中,池化技术指的是通过对象池来重用已经创建的对象,从而避免了频繁地创建和销毁对象,这种技术可以提高系统的性能和可伸缩性。 通过设置 VM options,来决定池化功能是否开启: -Dio.netty.allocator.type={unpooled|pooled} 在 Netty 4.1 版本以后,非 Android 平台默认启用池化实现,Android 平台启用非池化实现; 这里我们使用非池化功能进行测试,依旧使用的是上面的测试代码 testCreateByteBuf,运行结果如下所示: class io.netty.buffer.UnpooledByteBufAllocator$InstrumentedUnpooledUnsafeDirectByteBuf class io.netty.buffer.UnpooledByteBufAllocator$InstrumentedUnpooledUnsafeHeapByteBuf class io.netty.buffer.UnpooledByteBufAllocator$InstrumentedUnpooledUnsafeDirectByteBuf 可以看到,ByteBuf 类由 PooledUnsafeDirectByteBuf 变成了 UnpooledUnsafeDirectByteBuf; 在没有池化的情况下,每次使用都需要创建新的 ByteBuf 实例,这个操作会涉及到内存的分配和初始化,如果是直接内存则代价更为昂贵,而且频繁的内存分配也可能导致内存碎片问题,增加 GC 压力。 使用池化技术可以避免频繁内存分配带来的开销,并且重用池中的 ByteBuf 实例,减少了内存占用和内存碎片问题。另外,池化技术还可以采用类似 jemalloc 的内存分配算法,进一步提升分配效率。 在高并发环境下,池化技术的优点更加明显,因为内存的分配和释放都是比较耗时的操作,频繁的内存分配和释放会导致系统性能下降,甚至可能出现内存溢出的风险。使用池化技术可以将内存分配和释放的操作集中到预先分配的池中,从而有效地降低系统的内存开销和风险。 内存释放 当在 Netty 中使用 ByteBuf 来处理数据时,需要特别注意内存回收问题。 Netty 提供了不同类型的 ByteBuf 实现,包括堆内存(JVM 内存)实现 UnpooledHeapByteBuf 和堆外内存(直接内存)实现 UnpooledDirectByteBuf,以及池化技术实现的 PooledByteBuf 及其子类。 UnpooledHeapByteBuf:通过 Java 的垃圾回收机制来自动回收内存; UnpooledDirectByteBuf:由于 JVM 的垃圾回收机制无法管理这些内存,因此需要手动调用 release 方法来释放内存; PooledByteBuf:使用了池化机制,需要更复杂的规则来回收内存; 由于池化技术的特殊性质,释放 PooledByteBuf 对象所使用的内存并不是立即被回收的,而是被放入一个内存池中,待下次分配内存时再次使用。因此,释放 PooledByteBuf 对象的内存可能会延迟到后续的某个时间点。为了避免内存泄漏和占用过多内存,我们需要根据实际情况来设置池化技术的相关参数,以便及时回收内存; Netty 采用了引用计数法来控制 ByteBuf 对象的内存回收,在博文 「源码解析」ByteBuf 的引用计数机制 中将会通过解读源码的形式对 ByteBuf 的引用计数法进行深入理解; 每个 ByteBuf 对象被创建时,都会初始化为1,表示该对象的初始计数为1。 在使用 ByteBuf 对象过程中,如果当前 handler 已经使用完该对象,需要通过调用 release 方法将计数减1,当计数为0时,底层内存会被回收,该对象也就被销毁了。此时即使 ByteBuf 对象还在,其各个方法均无法正常使用。 但是,如果当前 handler 还需要继续使用该对象,可以通过调用 retain 方法将计数加1,这样即使其他 handler 已经调用了 release 方法,该对象的内存仍然不会被回收。这种机制可以有效地避免了内存泄漏和意外访问已经释放的内存的情况。 一般来说,应该尽可能地保证 retain 和 release 方法成对出现,以确保计数正确。