- 浏览: 3047843 次
- 性别:
- 来自: 海外
文章分类
- 全部博客 (430)
- Programming Languages (23)
- Compiler (20)
- Virtual Machine (57)
- Garbage Collection (4)
- HotSpot VM (26)
- Mono (2)
- SSCLI Rotor (1)
- Harmony (0)
- DLR (19)
- Ruby (28)
- C# (38)
- F# (3)
- Haskell (0)
- Scheme (1)
- Regular Expression (5)
- Python (4)
- ECMAScript (2)
- JavaScript (18)
- ActionScript (7)
- Squirrel (2)
- C (6)
- C++ (10)
- D (2)
- .NET (13)
- Java (86)
- Scala (1)
- Groovy (3)
- Optimization (6)
- Data Structure and Algorithm (3)
- Books (4)
- WPF (1)
- Game Engines (7)
- 吉里吉里 (12)
- UML (1)
- Reverse Engineering (11)
- NSIS (4)
- Utilities (3)
- Design Patterns (1)
- Visual Studio (9)
- Windows 7 (3)
- x86 Assembler (1)
- Android (2)
- School Assignment / Test (6)
- Anti-virus (1)
- REST (1)
- Profiling (1)
- misc (39)
- NetOA (12)
- rant (6)
- anime (5)
- Links (12)
- CLR (7)
- GC (1)
- OpenJDK (2)
- JVM (4)
- KVM (0)
- Rhino (1)
- LINQ (2)
- JScript (0)
- Nashorn (0)
- Dalvik (1)
- DTrace (0)
- LLVM (0)
- MSIL (0)
最新评论
-
mldxs:
虽然很多还是看不懂,写的很好!
虚拟机随谈(一):解释器,树遍历解释器,基于栈与基于寄存器,大杂烩 -
HanyuKing:
Java的多维数组 -
funnyone:
Java 8的default method与method resolution -
ljs_nogard:
Xamarin workbook - .Net Core 中不 ...
LINQ的恶搞…… -
txm119161336:
allocatestlye1 顺序为 // Fields o ...
最近做的两次Java/JVM分享的概要
昨天在处理一处动态加载逻辑的时候遇到了bug……做个最小repro出来记着。
假设有以下目录结构:
其中IAlpha、Alpha、Beta、Charlie几个.class文件是先前由以下源码编译得到的:
注意我特意把Charlie.class放到了不同的目录下来再现遇到的问题。
然后写以下代码来测试动态加载:
编译,在上述目录结构中的test目录不加任何特别参数运行,一切正常。
在上面测试的最后遍历一下动态加载进来的接口上的所有public方法:
这就挂了:
再挖一挖可以看到真正出错的stack trace:
把需要的路径加上,使classloader能找到Charlie.class,问题自然消失:
其实原本遇到的问题跟这个repro有细微不同,但都是在加载接口时漏了一些依赖的类使得加载失败,抛出NoClassDefFoundError。
根据JVM规范第二版,class要进入可用状态需要经过loading、linking、initialization三个阶段,其中linking阶段又包括verification、preparation、resolution三步。
通过传入JVM启动参数-XX:+TraceClassLoading,可以看到第一版测试里与我写的代码直接相关的类的输出是:
很明显,在IAlpha达到"loaded"状态的时候,它依赖的其它class还没进入加载范围。
第二版的相关输出是:
可以看到IAlpha.foo()的参数与返回值的类型对应的class也被加载进来了。IAlpha.bar()参数列表为空,返回值类型为Charlie,如果正常的话应该在这里看到Charlie也被加载进来才对。然而实际看到的是抛异常的stack trace。
第三版的相关输出是:
这里就看到一切正常了,在main()里调用System.out.println()要输出的内容都能看到了。
OK,那到底异常是从哪里抛出来的呢?
测试是在Sun的JDK 1.6系上跑的。下面以这个实现为前提来讨论。
先看点小细节。注意在第二和第三版的输出中都可以看到HotSpot加载了java.lang.Class$MethodArray这个嵌套类。它是哪儿来的呢?在出错的stack trace上可以看到有Class.privateGetPublicMethods()方法,看看它的实现:
这个方法创建了MethodArray的实例,顺带就让HotSpot把该类给加载了进来。
现在来看看上面例子里对抛出异常有直接贡献的方法调用树:
(同名重载相互调用的只写一个,缩进在同一层的说明是被同一个方法调用的。
相关方法上面的文件路径是实际代码在JDK源码中的位置。)
看到这个流程,可以发现HotSpot VM中加载类是lazy的,一个class被加载后并不马上解析(resolve)所有依赖项。在调用Class.getMethods()之前,IAlpha.class已经被正确加载并初始化了。等到实际要去获取IAlpha上的方法信息时,方法上依赖的类型才被解析,解析的过程中又会尝试加载依赖的类型。
回头看JVM规范里的一段描述:
第二段里举个这么明确的例子,自然不是没有道理的……
记完。留着文件名和方法名以后回头再找的时候方便。
话说在寻找相关方法的时候,发现instanceKlass::link_class_impl()有调用初始化vtable(虚方法表)和itable(接口方法表)的逻辑。再看JVM规范里的一段话:
不由得一笑 ^_^
假设有以下目录结构:
├─path1_not_on_classpath │ Alpha.class │ Beta.class │ IAlpha.class │ ├─path2_not_on_classpath │ Charlie.class │ └─test TestInterfaceClassLoad.class TestInterfaceClassLoad.java
其中IAlpha、Alpha、Beta、Charlie几个.class文件是先前由以下源码编译得到的:
public interface IAlpha { Alpha foo(Beta b); Charlie bar(); } class Alpha { } class Beta { } class Charlie { }
注意我特意把Charlie.class放到了不同的目录下来再现遇到的问题。
然后写以下代码来测试动态加载:
import java.io.File; import java.lang.reflect.Method; import java.net.URL; import java.net.URLClassLoader; public class TestInterfaceClassLoad { public static void main(String[] args) throws Exception { URLClassLoader loader = new URLClassLoader(new URL[] { new File("../path1_not_on_classpath").toURI().toURL() }, Thread.currentThread().getContextClassLoader()); Class<?> clzAlpha = Class.forName("IAlpha", true, loader); System.out.println(clzAlpha); } }
编译,在上述目录结构中的test目录不加任何特别参数运行,一切正常。
在上面测试的最后遍历一下动态加载进来的接口上的所有public方法:
import java.io.File; import java.lang.reflect.Method; import java.net.URL; import java.net.URLClassLoader; public class TestInterfaceClassLoad { public static void main(String[] args) throws Exception { URLClassLoader loader = new URLClassLoader(new URL[] { new File("../path1_not_on_classpath").toURI().toURL() }, Thread.currentThread().getContextClassLoader()); Class<?> clzAlpha = Class.forName("IAlpha", true, loader); System.out.println(clzAlpha); for (Method m : clzAlpha.getMethods()) System.out.println(m); } }
这就挂了:
Exception in thread "main" java.lang.NoClassDefFoundError: Charlie at java.lang.Class.getDeclaredMethods0(Native Method) at java.lang.Class.privateGetDeclaredMethods(Unknown Source) at java.lang.Class.privateGetPublicMethods(Unknown Source) at java.lang.Class.getMethods(Unknown Source) at TestInterfaceClassLoad.main(TestInterfaceClassLoad.java:13) Caused by: java.lang.ClassNotFoundException: Charlie at java.net.URLClassLoader$1.run(Unknown Source) at java.security.AccessController.doPrivileged(Native Method) at java.net.URLClassLoader.findClass(Unknown Source) at java.lang.ClassLoader.loadClass(Unknown Source) at java.lang.ClassLoader.loadClass(Unknown Source) at java.lang.ClassLoader.loadClassInternal(Unknown Source) ... 5 more
再挖一挖可以看到真正出错的stack trace:
java.lang.Class.getDeclaredMethods0(Native Method) java.lang.Class.privateGetDeclaredMethods(Unknown Source) java.lang.Class.privateGetPublicMethods(Unknown Source) java.lang.Class.getMethods(Unknown Source) TestInterfaceClassLoad.main(TestInterfaceClassLoad.java:13)
把需要的路径加上,使classloader能找到Charlie.class,问题自然消失:
import java.io.File; import java.lang.reflect.Method; import java.net.URL; import java.net.URLClassLoader; public class TestInterfaceClassLoad { public static void main(String[] args) throws Exception { URLClassLoader loader = new URLClassLoader(new URL[] { new File("../path1_not_on_classpath").toURI().toURL(), new File("../path2_not_on_classpath").toURI().toURL() }, Thread.currentThread().getContextClassLoader()); Class<?> clzAlpha = Class.forName("IAlpha", true, loader); System.out.println(clzAlpha); for (Method m : clzAlpha.getMethods()) System.out.println(m); } }
其实原本遇到的问题跟这个repro有细微不同,但都是在加载接口时漏了一些依赖的类使得加载失败,抛出NoClassDefFoundError。
根据JVM规范第二版,class要进入可用状态需要经过loading、linking、initialization三个阶段,其中linking阶段又包括verification、preparation、resolution三步。
通过传入JVM启动参数-XX:+TraceClassLoading,可以看到第一版测试里与我写的代码直接相关的类的输出是:
[Loading TestInterfaceClassLoad from file:/D:/test/jdk1.6.0_14/test_classload/test/] [Loaded TestInterfaceClassLoad from file:/D:/test/jdk1.6.0_14/test_classload/test/] [Loading IAlpha from file:/D:/test/jdk1.6.0_14/test_classload/test/../path1_not_on_classpath/] [Loaded IAlpha from file:/D:/test/jdk1.6.0_14/test_classload/test/../path1_not_on_classpath/] interface IAlpha
很明显,在IAlpha达到"loaded"状态的时候,它依赖的其它class还没进入加载范围。
第二版的相关输出是:
[Loading TestInterfaceClassLoad from file:/D:/test/jdk1.6.0_14/test_classload/test/] [Loaded TestInterfaceClassLoad from file:/D:/test/jdk1.6.0_14/test_classload/test/] [Loading IAlpha from file:/D:/test/jdk1.6.0_14/test_classload/test/../path1_not_on_classpath/] [Loaded IAlpha from file:/D:/test/jdk1.6.0_14/test_classload/test/../path1_not_on_classpath/] interface IAlpha [Loading java.lang.Class$MethodArray from D:\test\jdk1.6.0_14\fastdebug\jre\lib\rt.jar] [Loaded java.lang.Class$MethodArray from D:\test\jdk1.6.0_14\fastdebug\jre\lib\rt.jar] [Loading Beta from file:/D:/test/jdk1.6.0_14/test_classload/test/../path1_not_on_classpath/] [Loaded Beta from file:/D:/test/jdk1.6.0_14/test_classload/test/../path1_not_on_classpath/] [Loading Alpha from file:/D:/test/jdk1.6.0_14/test_classload/test/../path1_not_on_classpath/] [Loaded Alpha from file:/D:/test/jdk1.6.0_14/test_classload/test/../path1_not_on_classpath/]
可以看到IAlpha.foo()的参数与返回值的类型对应的class也被加载进来了。IAlpha.bar()参数列表为空,返回值类型为Charlie,如果正常的话应该在这里看到Charlie也被加载进来才对。然而实际看到的是抛异常的stack trace。
第三版的相关输出是:
[Loading TestInterfaceClassLoad from file:/D:/test/jdk1.6.0_14/test_classload/test/] [Loaded TestInterfaceClassLoad from file:/D:/test/jdk1.6.0_14/test_classload/test/] [Loading IAlpha from file:/D:/test/jdk1.6.0_14/test_classload/test/../path1_not_on_classpath/] [Loaded IAlpha from file:/D:/test/jdk1.6.0_14/test_classload/test/../path1_not_on_classpath/] interface IAlpha [Loading java.lang.Class$MethodArray from D:\test\jdk1.6.0_14\fastdebug\jre\lib\rt.jar] [Loaded java.lang.Class$MethodArray from D:\test\jdk1.6.0_14\fastdebug\jre\lib\rt.jar] [Loading Beta from file:/D:/test/jdk1.6.0_14/test_classload/test/../path1_not_on_classpath/] [Loaded Beta from file:/D:/test/jdk1.6.0_14/test_classload/test/../path1_not_on_classpath/] [Loading Alpha from file:/D:/test/jdk1.6.0_14/test_classload/test/../path1_not_on_classpath/] [Loaded Alpha from file:/D:/test/jdk1.6.0_14/test_classload/test/../path1_not_on_classpath/] [Loading Charlie from file:/D:/test/jdk1.6.0_14/test_classload/test/../path2_not_on_classpath/] [Loaded Charlie from file:/D:/test/jdk1.6.0_14/test_classload/test/../path2_not_on_classpath/] public abstract Alpha IAlpha.foo(Beta) public abstract Charlie IAlpha.bar()
这里就看到一切正常了,在main()里调用System.out.println()要输出的内容都能看到了。
OK,那到底异常是从哪里抛出来的呢?
测试是在Sun的JDK 1.6系上跑的。下面以这个实现为前提来讨论。
先看点小细节。注意在第二和第三版的输出中都可以看到HotSpot加载了java.lang.Class$MethodArray这个嵌套类。它是哪儿来的呢?在出错的stack trace上可以看到有Class.privateGetPublicMethods()方法,看看它的实现:
// Returns an array of "root" methods. These Method objects must NOT // be propagated to the outside world, but must instead be copied // via ReflectionFactory.copyMethod. private Method[] privateGetPublicMethods() { checkInitted(); Method[] res = null; if (useCaches) { clearCachesOnClassRedefinition(); if (publicMethods != null) { res = (Method[]) publicMethods.get(); } if (res != null) return res; } // No cached value available; compute value recursively. // Start by fetching public declared methods MethodArray methods = new MethodArray(); { Method[] tmp = privateGetDeclaredMethods(true); methods.addAll(tmp); } // Now recur over superclass and direct superinterfaces. // Go over superinterfaces first so we can more easily filter // out concrete implementations inherited from superclasses at // the end. MethodArray inheritedMethods = new MethodArray(); Class[] interfaces = getInterfaces(); for (int i = 0; i < interfaces.length; i++) { inheritedMethods.addAll(interfaces[i].privateGetPublicMethods()); } if (!isInterface()) { Class c = getSuperclass(); if (c != null) { MethodArray supers = new MethodArray(); supers.addAll(c.privateGetPublicMethods()); // Filter out concrete implementations of any // interface methods for (int i = 0; i < supers.length(); i++) { Method m = supers.get(i); if (m != null && !Modifier.isAbstract(m.getModifiers())) { inheritedMethods.removeByNameAndSignature(m); } } // Insert superclass's inherited methods before // superinterfaces' to satisfy getMethod's search // order supers.addAll(inheritedMethods); inheritedMethods = supers; } } // Filter out all local methods from inherited ones for (int i = 0; i < methods.length(); i++) { Method m = methods.get(i); inheritedMethods.removeByNameAndSignature(m); } methods.addAllIfNotPresent(inheritedMethods); methods.compactAndTrim(); res = methods.getArray(); if (useCaches) { publicMethods = new SoftReference(res); } return res; }
这个方法创建了MethodArray的实例,顺带就让HotSpot把该类给加载了进来。
现在来看看上面例子里对抛出异常有直接贡献的方法调用树:
(同名重载相互调用的只写一个,缩进在同一层的说明是被同一个方法调用的。
相关方法上面的文件路径是实际代码在JDK源码中的位置。)
j2se/src/share/classes/java/lang/Class.java java.lang.Class.getMethods() java.lang.Class.privateGetPublicMethods() java.lang.Class.privateGetDeclaredMethods() java.lang.Class.getDeclaredMethods0() hotspot/src/share/vm/prims/jvm.cpp JVM_GetClassDeclaredMethods() hotspot/src/share/vm/runtime/reflection.cpp Reflection::new_method() get_parameter_types() <= 参数与返回值类型都包含在内 get_mirror_from_signature() hotspot/src/share/vm/classfile/systemDictionary.cpp SystemDictionary::resolve_or_fail() SystemDictionary::resolve_or_null() SystemDictionary::resolve_instance_class_or_null() SystemDictionary::load_instance_class() hotspot/src/share/vm/runtime/javaCalls.cpp JavaCalls::call_virtual() <= 对ClassLoader.loadClass()的虚方法调用; 由于URLClassLoader没有覆写loadClass(), 这里实际调用的是ClassLoader的版本 j2se/src/share/classes/java/lang/ClassLoader.java java.lang.ClassLoader.loadClass() j2se/src/share/classes/java/net/URLClassLoader.java java.net.URLClassLoader.findClass() <= 找不到,抛ClassNotFoundException SystemDictionary::handle_resolution_exception() <= 换成NoClassDefFoundError再抛出来
看到这个流程,可以发现HotSpot VM中加载类是lazy的,一个class被加载后并不马上解析(resolve)所有依赖项。在调用Class.getMethods()之前,IAlpha.class已经被正确加载并初始化了。等到实际要去获取IAlpha上的方法信息时,方法上依赖的类型才被解析,解析的过程中又会尝试加载依赖的类型。
回头看JVM规范里的一段描述:
Java Virtual Machine Specification, 2nd, 2.17.3 写道
The Java programming language allows an implementation flexibility as to when linking activities (and, because of recursion, loading) take place, provided that the semantics of the language are respected, that a class or interface is completely verified and prepared before it is initialized, and that errors detected during linkage are thrown at a point in the program where some action is taken by the program that might require linkage to the class or interface involved in the error.
For example, an implementation may choose to resolve each symbolic reference in a class or interface individually, only when it is used (lazy or late resolution), or to resolve them all at once, for example, while the class is being verified (static resolution). This means that the resolution process may continue, in some implementations, after a class or interface has been initialized.
For example, an implementation may choose to resolve each symbolic reference in a class or interface individually, only when it is used (lazy or late resolution), or to resolve them all at once, for example, while the class is being verified (static resolution). This means that the resolution process may continue, in some implementations, after a class or interface has been initialized.
第二段里举个这么明确的例子,自然不是没有道理的……
记完。留着文件名和方法名以后回头再找的时候方便。
话说在寻找相关方法的时候,发现instanceKlass::link_class_impl()有调用初始化vtable(虚方法表)和itable(接口方法表)的逻辑。再看JVM规范里的一段话:
Java Virtual Machine Specification, 2nd, 2.17.3 写道
Implementations of the Java virtual machine may precompute additional data structures at preparation time in order to make later operations on a class or interface more efficient. One particularly useful data structure is a "method table" or other data structure that allows any method to be invoked on instances of a class without requiring a search of superclasses at invocation time.
不由得一笑 ^_^
发表评论
-
The Prehistory of Java, HotSpot and Train
2014-06-02 08:18 0http://cs.gmu.edu/cne/itcore/vi ... -
MSJVM and Sun 1.0.x/1.1.x
2014-05-20 18:50 0当年的survey paper: http://www.sym ... -
Sun JDK1.4.2_28有TieredCompilation
2014-05-12 08:48 0原来以前Sun的JDK 1.4.2 update 28就已经有 ... -
IBM JVM notes (2014 ver)
2014-05-11 07:16 0Sovereign JIT http://publib.bou ... -
class data sharing by Apple
2014-03-28 05:17 0class data sharing is implement ... -
Java 8与静态工具类
2014-03-19 08:43 16273以前要在Java里实现所谓“静态工具类”(static uti ... -
Java 8的default method与method resolution
2014-03-19 02:23 10450先看看下面这个代码例子, interface IFoo { ... -
HotSpot Server VM与Server Class Machine
2014-02-18 13:21 0HotSpot VM历来有Client VM与Server V ... -
Java 8的lambda表达式在OpenJDK8中的实现
2014-02-04 12:08 0三月份JDK8就要发布首发了,现在JDK8 release c ... -
GC stack map与deopt stack map的异同
2014-01-08 09:56 0两者之间不并存在包含关系。它们有交集,但也各自有特别的地方。 ... -
HotSpot Server Compiler与data-flow analysis
2014-01-07 17:41 0http://en.wikipedia.org/wiki/Da ... -
字符串的一般封装方式的内存布局 (1): 元数据与字符串内容,整体还是分离?
2013-11-07 17:44 22390(Disclaimer:未经许可请 ... -
字符串的一般封装方式的内存布局
2013-11-01 12:55 0(Disclaimer:未经许可请 ... -
关于string,内存布局,C++ std::string,CoW
2013-10-30 20:45 0(Disclaimer:未经许可请 ... -
对C语义的for循环的基本代码生成模式
2013-10-19 23:12 21871之前有同学在做龙书(第二版)题目,做到8.4的练习,跟我对答案 ... -
Java的instanceof是如何实现的
2013-09-22 16:57 0Java语言规范,Java SE 7版 http://docs ... -
oop、klass、handle的关系
2013-07-30 17:34 0oopDesc及其子类的实例 oop : oopDesc* ... -
Nashorn各种笔记
2013-07-15 17:03 0http://bits.netbeans.org/netbea ... -
《深入理解Java虚拟机(第二版)》书评
2013-07-08 19:19 0值得推荐的中文Java虚拟机入门书 感谢作者赠与的样书,以下 ... -
豆列:从表到里学习JVM实现
2013-06-13 14:13 48359刚写了个学习JVM用的豆列跟大家分享。 豆列地址:http: ...
相关推荐
例如,DynamicAPK是阿里巴巴开源的一个强大的动态加载框架,提供了完整的解决方案。 4. **资源管理**:动态加载的APK也需要获取到自己的资源,这需要处理资源ID映射。Android的资源ID是全局唯一的,因此需要在主...
在动态加载的这个DLL中定义了一个函数MRun,该函数可以动态执行一 个EXE,即:调用资源中的EXE文件或TMemoryStream中被载入的EXE流。 此技术的好处:直接把资源中的EXE加载到内存中执行,使用程序自 身嵌入的EXE...
在项目中添加一个新的C#类,如`ImageItem.cs`: ```csharp public class ImageItem { public string ImagePath { get; set; } } ``` 这个简单的类包含了图片的路径信息。 为了动态加载图片,我们需要填充...
这个视图通常是一个具有透明背景的`<div>`,里面嵌套一个更小的`<img>`元素。`<div>`的大小可以根据需求设置,比如200x200像素。 4. **更新放大视图的内容**:每次鼠标移动时,根据计算出的放大区域,调整小`<img>`...
5. **封装库的实现**:自己封装的权限管理库可能会包含一个类或一系列辅助方法,这些方法会隐藏上述细节,提供更简洁的API供开发者调用。例如,可能有一个`checkAndRequestPermission()`方法,它自动检查权限状态并...
本文将深入探讨如何使用MFC加载动态图片,这是一个常见的需求,尤其是在创建用户界面时添加动画效果。 首先,我们需要理解动态图片通常指的是GIF格式的图像,因为GIF支持帧间的延迟时间,可以连续播放,形成动画...
本篇将深入探讨如何利用SWF(ShockWave Flash)文件来创建仿QQ动态背景,并介绍一个封装好的类库,使其实现变得更加简便。 SWF是一种由Adobe Flash技术生成的文件格式,主要用于在网页上展示动画、交互式内容以及...
首先在页面加载完成事件中,遍历所有的iframe,将src设置为空字符串,然后绑定一个事件监听器到easyui的tab组件上,监听tab的select事件。在该事件中,获取被选中的标签对应的iframe,并为其动态设置正确的src值。 ...
网站加载图标是网页设计中的一个重要元素,它在用户等待页面完全加载时提供视觉反馈,以显示网站正在运行并处理请求。这些图标通常是一系列动态图形,如旋转的圆圈、循环箭头或填充进度条,旨在减少用户的感知等待...
`swing之滚动条下拉加载数据源码`这个标题暗示了我们将在 Swing 应用程序中实现一个功能,即当用户滚动到底部时自动加载更多数据。这种机制常见于许多现代应用程序,如社交媒体应用、新闻阅读器等,以提高用户体验。...
在Android开发中,NDK(Native Development Kit)允许...总之,动态加载SD卡上的.so文件可以为大型应用带来诸多优势,但同时也需要处理更多细节问题。通过合理的规划和实现,这种技术能够有效地优化应用的大小和性能。
本资料主要涉及RecyclerView的下拉刷新和上拉加载功能,这两种功能在移动应用中非常常见,用于实现数据的动态加载,提升用户体验。 下拉刷新(Pull-to-Refresh)是指用户在顶部向下滑动列表时,触发加载新数据的...
1. **创建Loader对象**:首先,我们需要创建一个Loader对象,它是加载所有类型内容的基础。在AS3.0中,你可以这样创建它: ```actionscript var loader:Loader = new Loader(); ``` 2. **监听事件**:然后,我们...
2. **设计对话框资源**:尽管动态创建对话框,但通常仍需要一个对话框模板资源,用于在设计时布局控件的初始位置和大小。在资源文件中,可能会有一个名为`IDD_DYNAMIC_DIALOG`的对话框资源。 3. **实现对话框的...
它是一个强大的组件,可以用来显示和编辑表格数据。默认情况下,每个单元格只能显示单一的文本或数值。但是,通过自定义绘制或者使用其他控件,我们可以扩展其功能,使其能够承载更复杂的元素,如树控件。 树控件...
在Windows Forms(Winform)应用程序开发中,有时候我们需要与Microsoft Office接口进行交互,例如向已存在的Word文档中动态添加内容。这个过程涉及到自动化技术,它允许我们使用C#或VB.NET等.NET语言来控制Word应用...
在动态库的上下文中,这意味着我们的动态库可以接受一个函数指针,然后在适当的时候调用这个函数。这样,动态库就可以通知或与调用它的应用程序进行交互,而无需知道具体的实现细节。 在 Delphi 中创建动态库,我们...
在本文中,我们将深入探讨如何使用Flutter框架来实现一个动态高斯模糊的AppBar。Flutter,由Google开发,是一个用于构建高性能、多平台的移动应用程序的开源UI工具包。它的核心特性之一是Dart编程语言,这使得开发者...
首先,我们需要创建一个`DataGridView`控件并添加到窗体中。这可以通过Visual Studio的拖放功能完成,或者通过代码动态创建。一旦`DataGridView`创建完毕,我们可以在设计时或运行时添加列。对于包含下拉列表的列,...
在资源编辑器中,你需要为工具栏添加一个新的按钮。这个按钮将作为Edit控件的占位符。设置其ID,例如ID_EDIT_TOOLBAR,并给它一个合适的图标或文字描述。 步骤2:自定义消息映射 在你的对话框类(通常是CDialog派生...