יום שני, 27 בינואר 2014

C# Reflection Guide



אחד התחומים המעניינים בפיתוח, האפשרות לתוכנית ללמוד על עצמה תוך כדי ריצה מאפשרת לפתח תוכניות מיוחדות במיוחד, האמת שנושא ה Reflection ישן והוא קיים עוד מתחילת ה Framework הראשון אבל אף פעם לא התייחסתי אליו כי בדרך כלל אין בו צורך, לרוב אנחנו יודעים מה אנחנו הולכים לפתח אבל קיימים מקרים מסויימים בהם יש חור שחור ואין לנו מספיק מידע ולכן עלינו לשמור על קוד גנרי ככל האפשר.

Reflection

יכולת של תוכנית לזהות במהלך הריצה את האובייקטים שרצים בה, לנתח אותם ולהפעיל בהם פונקציות באופן דינמי לפי סוג האובייקט או על פי קונבנציה קבועה, במאמר זה אני יעבור על נושא ה Reflection בסביבת .Net אבל היכולת הזאת נתמכת גם בסביבות נוספות כמו JavaScript, C++,Phyton וכו', הרעיון המרכזי הוא להפוך את התוכנית ל"שקופה" ושתוכל להתמודד עם אובייקטים שונים מבלי לעבור Compile כל פעם מחדש.

לצורך הדוגמה בניתי תוכנית והיא מאפשרת למשתמשים ליצור עבורה Plugins מיוחדים, מהרגע שהכנסתי את התכונה הזאת אני לא יודע מה המשתמש יעשה (האמת שזה לא כלכך צריך לעניין אותי), ואני לא יכול לעשות Compile לתוכנית המקורית כל פעם מחדש כי זה יהיה מגוחך, כאן נכנס ה Reflection לתמונה שבעזרתו אני יכול להגדיר קונבנציה עבור טעינת Plugin כחלק מה SDK של התוכנית, משתמש הקצה מייצר אובייקט שמכיל בתוכו קריאות לפונקציות שנקבעו מראש שמטרתם לחבר בין התוכנית לקוד שהמשתמש יצר, ברגע שמופעלת הפונקציה הקוד של המשתמש רץ מבלי שהיה חלק מהתוכנית עצמה כפי שניתן לראות בקוד הבא:

public class example
    {
        //private string
        private string msg0 = "this is exmaple of private string!";

        //public string
        public string msg1 = "this is exmaple of public string!";

        //constructor
        public example()
        {

        }

        //public function for invoke return string
        public string returnPrivateMessage()
        {
            return msg0;
        }

        //public function for invoke getting external 
        //parameter and return string
        public string returnEditMessage(string msg)
        {
            return "you write: " + msg;
        }
    }


המשתמש עוטף את המחלקה כ DLL ומעתיק אותה לתיקיית Plugin בתוכנית,התוכנית טוענת אותו לזיכרון ומחפשת את הפונקציה שנקבעה מראש על מנת להפעיל אותה ובעצם מעבירה את הריצה ל DLL מבלי לדעת מה בדיוק עומד לקרות שם.


class Program
    {
        static void Main(string[] args)
        {
            //load assmbly dynamically
            Assembly assemblyInstance = Assembly.LoadFrom(@"C:\\RefDLL.dll");

            Type[] types = assemblyInstance.GetTypes();

            for (int i = 0; i < types.Length; i++)
            {
                //search for specific type
                if (types[i].Name.ToLower() == "example")
                {
                    MethodInfo[] methods = types[i].GetMethods();
                    if (methods != null)
                    {
                        for (int z = 0; z < methods.Length; z++)
                        {
                            //search for specific method
                            if (methods[z].Name == "returnPrivateMessage")
                            {
                                //create instance for dynamic object
                                object classInstance = Activator.CreateInstance(types[i], null);

                                //execute method and gettig string
                                object res = methods[z].Invoke(classInstance, new object[0]);

                            }

                            if (methods[z].Name == "returnEditMessage")
                            {
                                //create parameters array for function,
                                //you can get the types of the arguments using
                                //ParameterInfo[] parameters = methods[z].GetParameters();
                                object[] parametersArray = new object[] { "my information!" };
                                object classInstance = Activator.CreateInstance(types[i], null);

                                object res = methods[z].Invoke(classInstance, parametersArray);
                            }
                        }
                    }
                }
            }

        }
    }



ראינו כיצד ניתן להפוך את התוכנית למשהו גנרי אבל מצד שני הקוד שלנו חשוף וכל אחד יכול להפעיל אותו, הצגתי בעבר כיצד ניתן לעשות Disassembly לקבצי DLL שנוצרו ב .Net וה Reflection לוקח חלק חשוב בתהליך, לסיום החלטתי שזו הזדמנות טובה להראות את היכולות של ה Reflection בעזרת כלי קטן שמאפשר לנתח קבצי DLL בצורה נוחה ומהירה, בדומה לדוגמה הקודמת התוכנית מקבלת נתיב ל DLL ופורסת אותו, היא מראה את הפונקציות, המשתנים והאירועים של האובייקטים בקובץ.

DLLReflector


סיכום:

אז כמו שכבר אמרתי בהתחלה בדרך כלל לא נמצא ל Reflection שימוש אלא במקרים מאוד מסויימים, ההתעסקות בניתוח האובייקט עלולה להפוך את התוכנית למסורבלת ואיטית ולכן כדאי לחשוב פעמיים לפני שבוחרים בדרך הזאת, אבל אין ספק שזה לוקח את האינטליגנציה של התוכנה צעד אחד קדימה.

קוד להורדה:


סקיינט?



יום שישי, 17 בינואר 2014

Arduino And EEPROM


שנה חדשה נפתחה בבלוג, היא עמוסה באתגרים חדשים את זה אני יכול להבטיח לכם, את המאמר הראשון השנה החלטתי להקדיש לכוכב מהשנה שעברה, הפעם נתעסק בכיצד ניתן להרחיב את הזיכרון המוגבל ב Arduino שהוא 32K בלבד.

דרישות

  • Arduino Uno
  • 24LC256 EEPROM
  • Wires
  • Breadboard




EEPROM - Electrically Erasable Programmable Read-Only Memory

אין ספק שהשם מפוצץ אבל בסופו של דבר מדובר ביחידת זיכרון שמאפשרת לשמור על המידע גם כאשר המכשיר כבוי, הרכיב שבחרתי עליו להדגים הוא 24LC256 שמאוד נפוץ במחיר 2 $ בלבד מאפשר לנו להרחיב את הזיכרון בעוד 256K ,הוא אינו נדיף בניגוד ל Ram ובנוסף הכתיבה והקריאה הן ברמת Bit בודד בניגוד לזיכרון Flash שלצורך שינוי ביט אחד יש לקרוא בלוק של מידע ,לטעון אותו לזיכרון ה Ram, לעשות את השינוי ולכתוב בחזרה ל Flash מה שהופך את העסק למסורבל אבל חיוני מאוד למידע בנפחים גדולים.

256K של זיכרון לא נדיף
הרגליים האנלוגיות (A5, A4) ב Arduino Uno מאפשרות לעבוד בתקשורת I2C Inter-Integrated Circuit שדומה מאוד ל SPI שראינו במאמר Arduino and MicroSD ההבדל העיקרי הוא שב SPI עבור כל רכיב נצטרך רגל אחת מה Arduino לעומת I2C שבו כל הרכיבים מתחברים לאותן כניסות בלוח ובקלות ניתן להוסיף עוד רכיבים, מדובר על 2 רגליים שבעזרתן מייצרים Bus הרגל הראשונה היא השעון, והשניה ל Data, לכל רכיב יש כתובת ייחודית שדרכה מתחברים בעזרת הממשק.

המון חיבורים עם 2 רגליים בלבד

קוד

החלטתי לתת דוגמה קצת יותר מורכבת מאשר כתיבה וקריאה של ערך בודד אלא לכתוב ולקרוא מחרוזות, ניתן לכתוב ולקרוא  16bytes  בלבד בכל פעם לכן יש להתקדם בכתובות בזיכרון בעזרת חלוקה ל Pages. 

#include <Wire.h>

//unique address for the eeprom 24LC256
#define eeprom1 0x50 
#define readblock 100
#define pageblock 16

//size of the message
unsigned char length = 0;
//receive data buffer
unsigned char rdata[readblock];

 
void setup(void)
{
  //example of parsable string
  unsigned char str_data[]={"{message:this is working! using EEPROM}\n"};

  Serial.begin(9600);

  //Active I2C interface
  Wire.begin();

  //writing  data to 24LC256
   writeData(str_data);
}

 void loop(){

    Serial.println("DATA READ");
 
    //reading data to 24LC256 
    readData(rdata,readblock);
    Serial.write(rdata,readblock);

    delay(1000);
}


void writeData( unsigned char * str_data)
{

  unsigned char pages = 0;
  unsigned char offset = 0;
  unsigned char index = 0;
  unsigned char counter = 0;

  //get the length of data
  length = getStrLength(str_data);
  //get the number of page to write
  pages = length / pageblock;

  //make the magic
  for(index = 0; index <= pages;index++)
  {
       //connecting to the device
       Wire.beginTransmission(eeprom1);
 
       //set the position of writing,
       Wire.write((int)((offset) >> 8));   //MSB -  Most significant bit
       Wire.write((int)((offset) & 0xFF)); // LSB - Least significant bit
     
       //total bytes writed to jump the next page address
       unsigned char i= 0;
     
       for(i= 0; i < pageblock;i++)
        {
                //writing the char to the memory unit.
                Wire.write((byte) str_data[counter]);
 
                //if counter equal data length quit!
                if(counter == length)
                      break;
                   
                 //increment the total counter of length
                 counter++;                
        }
   
       //end I2C transmission
        Wire.endTransmission();
     
        //move offset to the next page  
        offset += i;
     
        //give it some time...
        delay(10);
     
  }

}

 void readData(unsigned char* data, unsigned int length)
 {
 
   unsigned char offset = 0;
   unsigned char counter = 0;
 
   //start reading pages until new line break
  while(true)
  {
     //total bytes writed to jump the next page address 
     unsigned char i=0;
      Wire.beginTransmission(eeprom1);
   
      //set position of reading
      Wire.write((int)(offset >> 8));   //MSB -  Most significant bit
      Wire.write((int)(offset & 0xFF)); //LSB - Least significant bit
   
     //end I2C transmission
      Wire.endTransmission();

       //request data from device
      Wire.requestFrom(eeprom1,length);

      while(Wire.available())
      {
          //write char to position data
          data[counter] = Wire.read();
       
          //end of message!
          if(data[counter] == '\n')
          {
              return;
          }
       
         i++;
         counter++;
     }
   
     offset+=i;
     delay(10);
  }
}

 //get the length from chars array
 unsigned char getStrLength(unsigned char * data)
 {
   unsigned char str_len=0;
    do{ str_len++; } while(data[str_len]);
    return str_len;
 }



מבנה סופי



סיכום

אפשרות הרחבת הזיכרון מאפשרת  לשמור מידע יעודי עבור התוכניות שלנו ועדיין להשאיר מספיק מקום לקוד ב Arduino, החיבור עצמו פשוט ומאפשר לתפעל מספר רכיבים מבלי לבזבז רגליים, ממשק I2C נפוץ מאוד ויש בו שימוש רחב במוצרים רבים לכן חשוב להכיר אותו.

שנה חמישית, ואו!