【问题标题】:Getting the current instance of the PApplet in Processing在处理中获取 PApplet 的当前实例
【发布时间】:2022-10-05 03:00:09
【问题描述】:

许多处理库需要用户传递一个 PApplet 对象以供库类进行操作。
问题是:是否可以创建一个静态方法PAppletFinder.find() 来获取所有 PApplet 实例?
(处理论坛中的帖子链接:https://discourse.processing.org/t/get-the-current-instance-of-papplet-from-a-library/36925/28

【问题讨论】:

    标签: java processing


    【解决方案1】:

    这实际上是可能的!处理有两个可以利用的弱点!
    这是在处理 4 和 3 中工作的代码:

    import processing.awt.*;
    import processing.opengl.*;
    import processing.core.*;
    
    import javax.swing.*;
    import java.awt.*;
    import java.util.*;
    import java.lang.reflect.*;
    
    import sun.misc.*;
    /**This class finds the active PApplet (Processing program)
     @author NumericPrime*/
    public class PAppletFinder {
      /**Unsafe instance used*/
      public static Unsafe unsafe=null;
      /**Unsafe Field offset*/
      public static long fieldIndex=0;
      /**Target field of the thread*/
      public static Field threadTargField=null;
      /**Here the three Fields unsafe,fieldIndex, threadTargField are initalized*/
      static {
        try {
          Field f2=Unsafe.class.getDeclaredField("theUnsafe");
          f2.setAccessible(true);
          unsafe=(Unsafe) f2.get(null);
          //System.out.println("Unsafe instance: "+unsafe.toString());
          threadTargField=Thread.class.getDeclaredField("target");
          fieldIndex=unsafe.objectFieldOffset(threadTargField);
        }
        catch(Exception e) {
          e.printStackTrace();
        }
      }
      /**Getter-Method used to detect all PApplets. If the detection hasn't run yet this method will run it.
       @param refresh if true: redetects all PApplets
       @return all detected PApplets*/
      public static PApplet[] getPApplets(boolean refresh) {
        int allfpSize=0;
        if (refresh) allFoundPapplets.clear();
        if ((allfpSize=allFoundPapplets.size())==0) findAll();
        return allFoundPapplets.toArray(new PApplet[allfpSize]);
      }
      /**All results will be saved here*/
      public static java.util.List<PApplet> allFoundPapplets=new LinkedList<PApplet>();
      /**This class takes the contents of the window and pieces together the PApplet
       * @return PApplet of the current sketch.
       * @param c contents of the Processing window (must be a SmoothCanvas)*/
      public static void get(Component c) {
        //System.out.println("Processing Window content");
        PSurfaceAWT.SmoothCanvas sc=(PSurfaceAWT.SmoothCanvas) c;
        try {
          //gets the PSurface for the PApplet
          PSurfaceAWT psa=(PSurfaceAWT) get(sc, "this$0", sc.getClass());
          //gets the PApplet
          PApplet prg=(PApplet) get((PSurfaceNone)psa, "sketch", PSurfaceNone.class);
          allFoundPapplets.add(prg);
        }
        catch(Exception e) {
          //System.err.println("Something went wrong");
          e.printStackTrace();
        }
      }
      /**This method tries to detect all PApplet used*/
      public static void findAll() {
        findFromWindow();
        fromThreads();
        //System.out.println("Detection done");
      }
    
      /**This looks out for processing-windows and gives the contents of the right one to the get method*/
      public static void findFromWindow() {
        //System.out.println("Searching Windows for instances");
        JFrame mainWindow=null;
        java.awt.Window win[]=java.awt.Window.getWindows();
        for (int i=0; i<win.length; i++) if (win[i] instanceof JFrame) {
          mainWindow=(JFrame) win[i];
          Component c=mainWindow.getContentPane().getComponents()[0];
          if (c instanceof PSurfaceAWT.SmoothCanvas) get(c);
        }
      }
      /**This is used to get the value of fields
       * @return Object value of a field
       * @param j the Object from which the value is taken
       * @param target name of the field
       * @param ref class the field is taken from*/
      public static Object get(Object j, String target, Class<?> ref) {
        try {
          Field f=ref.getDeclaredField(target);
          f.setAccessible(true);
          return f.get(j);
        } 
        catch (Exception e) {
          e.printStackTrace();
        } 
        return null;
      }
      /** When using P2D or P3D everything is built differently however there is a weakness to be found in 
        * PSurfaceJOGL as it dispatches a Thread using an annonymous inner class as Runnable*/
      public static void fromThreads() {
        //System.out.println("Searching Threads for instances");
        //This fetches all threads
        Set<Thread> threadSet=Thread.getAllStackTraces().keySet();
        //It iterates upon the threads to find ones dispatched by PSurfaceJOGL
        for (Thread th : threadSet) {
        iteration:
          {
            try {
              //Field f=th.getClass().getDeclaredField("target");
              //f.setAccessible(true);
              Object currinstance=null;
              
              //Here Unsafe is used to breach encapsulation of java.lang
              currinstance=unsafe.getObject(th, fieldIndex);
              if (!currinstance.getClass().toString().contains("PSurfaceJOGL$")) break iteration;
              //System.out.println("Weak spot found! "+th.getName());
              //gets the PSurfaceJOGL
              currinstance=getEnclosingInstance(currinstance);
              //gets the PApplet
              Field f=PSurfaceJOGL.class.getDeclaredField("sketch");
              f.setAccessible(true);
              allFoundPapplets.add((PApplet) (currinstance=f.get(currinstance)));
              //System.out.println("Detection successful");
            }
            catch(Exception e) 
            {
            catchBlock:
              {
                if (e instanceof NoSuchFieldException||e instanceof NullPointerException) {
                  //System.err.println("target not found "+th.getName());
                  //A NullPointerException may occur
                  break catchBlock;
                }
                //System.err.println("Something went wrong!");
                e.printStackTrace();
              }
            }
          }
        }
      }
      //This will try to get the enclosing instance of an object (this can also be a lambda)
      public static Object getEnclosingInstance(Object o) {
        //This code will work for Processing 3 (uses annonymous inner classes)
        try {
          Class<?> classused=o.getClass();
          Field this0=classused.getDeclaredField("this$0");
          this0.setAccessible(true);
          return this0.get(o);
        }
        catch(Exception e) {
          //System.err.println("Can't find enclosing instance of Object, trying to use Lambdas... (To be expected with Processing 4)");
        }
        //This code should work for Processing 4 (lambdas are used instead of inner classes, Lambdas use arg$1 instead this$0) 
        try {
          Class<?> classused=o.getClass();
          Field this0=classused.getDeclaredField("arg$1");
          this0.setAccessible(true);
          return this0.get(o);
        }
        catch(Exception e) {
          //System.err.println("Detection Failed!");
        }
        return null;
      }
    }
    

    为什么这段代码有效?
    如前所述,处理 3 和 4 共有两个弱点。第一个是 JAVA2D 独有的,第二个是 P2D 和 P3D 独有的:
    JAVA2D 的弱点:
    java.awt.Window 中有一个方法可以获取自身每个子类的所有活动实例:java.awt.Window.getWindows();
    这包括 Processing 使用的 JFrame。事实上,Processing 使用了类 PSurfaceAWT.SmoothCanvas 的自定义组件,它绘制了在 Processing 中完成的所有事情。从那时起,只需检索封闭实例并获取它的草图字段。

    P2D 和 P3D 的弱点
    第二个弱点在于 PSurfaceJOGL https://github.com/processing/processing4/blob/main/core/src/processing/opengl/PSurfaceJOGL.java#L399-L417 此时,使用通过 lambda 实现的 Runnable 调度线程。在第 405 行,这个 lambda 使用了 drawException 一个 PSurfaceJOGL 的字段。这样做时,lambda 将持有对其封闭实例的引用,可以访问。
    但是我们如何首先访问 Runnable 呢?
    在 17 以下的 Java 版本中,可以使用反射来做到这一点。但是,由于 Java 17 java.lang 被封装并且处理不允许添加自定义命令行参数来打开 java.lang !
    这个问题的答案是 sun.misc.Unsafe
    这个类不关心封装,可以帮助获取字段值。
    不过,这确实有丑陋的副作用:使用任何 SecurityManager 都会炸毁这个类!
    幸运的是,Processing 默认没有 SecurityManager,所以我可以使用 Unsafe 从 java.lang.Thread 获取目标字段。从那里我需要获取它的封闭实例并可以从那里获取草图字段!

    如果需要,我可以发布更简单的代码(不使用 Unsafe)(但仅适用于处理 3)。

    【讨论】:

      猜你喜欢
      • 2013-09-07
      • 1970-01-01
      • 2011-12-22
      • 1970-01-01
      • 2023-03-26
      • 1970-01-01
      • 1970-01-01
      • 2017-01-23
      相关资源
      最近更新 更多