Guest User

Ntuple Tools

a guest
Aug 29th, 2014
356
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 17.85 KB | None | 0 0
  1. //---NtupleTools----------
  2. //   Version 2.0
  3. //   15.07.2010
  4. //   19.11.2010 duplicate check in AllRootFilesNoDup
  5. //   29.11.2010 clash removed in case of multiple opened EasyChains
  6. //   15.02.2011 conditionals for pure header use if __NTHEADER___ defined.
  7. //              This aproach allows the use of NtupleTools2.h with command line macros
  8. //              since the complete implementation is in one file
  9. //     2.5.2011 - Improved performance in case of multiple acces of a branch for same entry
  10. //              - new dcache door
  11. //     8.6.2013 - simple TClonesArray to vector interface
  12. //              - no duplicates default -> false
  13. //              - unique name base argument
  14.  
  15. #ifndef NtupleTools3_h
  16. #define NtupleTools3_h
  17.  
  18. #ifdef __GNUC__
  19. // only visible for the gnu pre-compiler
  20. //#warning __GNUC__
  21. #pragma GCC diagnostic ignored "-Wunused-parameter"
  22. #pragma GCC diagnostic ignored "-Wuninitialized"
  23. #endif
  24.  
  25. #include <iostream>
  26. #include <sstream>
  27. #include <iomanip>
  28. #include <string>
  29. #include <map>
  30. #include <vector>
  31. #include <cstdlib>
  32. #include <algorithm>
  33. #include "TTree.h"
  34. #include "TFile.h"
  35. #include "TChain.h"
  36. #include "TChainElement.h"
  37. #include "TSystem.h"
  38. #include "TROOT.h"
  39. #include "TString.h"
  40. #include "TObjArray.h"
  41. #include "TClonesArray.h"
  42. #include "TStopwatch.h"
  43. #include "Math/LorentzVector.h"
  44. #include "Math/DisplacementVector3D.h"
  45. #include "Math/GenVector/PositionVector3D.h"
  46. #include "Math/GenVector/PxPyPzE4D.h"
  47. #include "Math/GenVector/PtEtaPhiE4D.h"
  48. #include "Math/GenVector/PxPyPzM4D.h"
  49. #include "Math/GenVector/PtEtaPhiM4D.h"
  50.  
  51. using namespace std;
  52.  
  53. typedef ROOT::Math::LorentzVector<ROOT::Math::PxPyPzE4D<double> >            LorentzV;
  54. typedef ROOT::Math::LorentzVector<ROOT::Math::PtEtaPhiM4D<float> >          LorentzM;
  55. typedef ROOT::Math::LorentzVector<ROOT::Math::PtEtaPhiE4D<float> >          LorentzE;
  56. typedef ROOT::Math::LorentzVector<ROOT::Math::PtEtaPhiM4D<double> >          LorentzMD;
  57. typedef ROOT::Math::LorentzVector<ROOT::Math::PtEtaPhiE4D<double> >          LorentzED;
  58. typedef ROOT::Math::DisplacementVector3D<ROOT::Math::Cartesian3D<double> > XYZVectorD;
  59. typedef ROOT::Math::PositionVector3D<ROOT::Math::Cartesian3D<double>  >     XYZPointD;
  60. typedef ROOT::Math::DisplacementVector3D<ROOT::Math::Cartesian3D<float> > XYZVectorF;
  61. typedef ROOT::Math::PositionVector3D<ROOT::Math::Cartesian3D<float>  >     XYZPointF;
  62.  
  63.  
  64. #ifdef __CINT__
  65. // for the command line ACLIC macro
  66. // compiled during rootcint
  67. //#warning __CINT__
  68. #pragma link C++ class vector<LorentzV>+;
  69. #pragma link C++ class vector<LorentzM>+;
  70. #pragma link C++ class vector<XYZVectorD>+;
  71. #pragma link C++ class vector<XYZPointD>+;
  72. #pragma link C++ class vector<XYZVectorF>+;
  73. #pragma link C++ class vector<XYZPointF>+;
  74. #pragma link C++ class map<string,bool>+;
  75. #pragma link C++ class map<string,string>+;
  76. //
  77. #pragma link C++ class pair<string,bool>+;
  78. #pragma link C++ class pair<string,string>+;
  79. #pragma link C++ class map<string,int>+;
  80. #pragma link C++ class map<string,string>+;
  81. //
  82. #pragma link C++ class ROOT::Math::Cartesian3D<float>+;
  83. #pragma link C++ class ROOT::Math::LorentzVector<ROOT::Math::PxPyPzE4D<float> >+;
  84. #pragma link C++ class ROOT::Math::PositionVector3D<ROOT::Math::Cartesian3D<float>  >+;
  85. #pragma link C++ class ROOT::Math::DisplacementVector3D<ROOT::Math::Cartesian3D<float> >+;
  86. #pragma link C++ class ROOT::Math::LorentzVector<ROOT::Math::PtEtaPhiM4D<float> >+;
  87. #pragma link C++ class ROOT::Math::LorentzVector<ROOT::Math::PtEtaPhiE4D<float> >+;
  88. #pragma link C++ class ROOT::Math::PtEtaPhiM4D<float>+;
  89. #endif
  90.  
  91. //namespace ROOT {
  92. #ifndef __CINT__
  93. // following functions should not be processed by rootcint
  94. //---------- simple progress counter and timer -----------------------------
  95. void progress(ostream& os=cout,const TString& pref="",const TString& postf="")
  96. #ifndef __NTHEADER___
  97. {
  98.         static int cnt(0),step(1),next(1);
  99.         cnt++;
  100.         if(cnt==next){
  101.                 os<<pref<<cnt<<postf<<endl;
  102.                 next+=step;
  103.         }
  104.         if((cnt+1)/step==10)step*=10;
  105. }
  106. #endif
  107. ;
  108. void progressT(int flush=0,ostream& os=cout)
  109. #ifndef __NTHEADER___
  110. {
  111.     static TStopwatch timer;
  112.         static int cnt(0),step(1),next(1);
  113.     if(cnt==0) timer.Start();
  114.         cnt++;
  115.         if(cnt==next||flush==1){
  116.             timer.Stop();
  117.                 os<<setw(10)<<left<<cnt
  118.                   <<" time (time/evt) - real: "
  119.                   <<setw(10)<<timer.RealTime()
  120.                   <<"("<<setw(10)<<timer.RealTime()/step
  121.                   <<"), \tCPU: "<<setw(10)<<timer.CpuTime()
  122.                   <<"("<<setw(10)<<timer.CpuTime()/step<<")"<<endl;
  123.                 timer.Start(false);
  124.                 next+=step;
  125.         }
  126.         if((cnt+1)/step==10)step*=10;
  127. }
  128. #endif
  129. ;
  130. void timer(ostream& os=cout,int stp=10)
  131. #ifndef __NTHEADER___
  132. {
  133.     static TStopwatch timer;
  134.         static int cnt(0),step(stp),next(stp);
  135.     if(cnt==0) timer.Start();
  136.         cnt++;
  137.         if(cnt==next){
  138.             timer.Stop();
  139.                 os<<"Evts: "<<setw(10)<<right<<cnt
  140.                   <<" | time [sec] - real: "
  141.                   <<left<<setprecision(4)<<setw(10)<<timer.RealTime()
  142.                   <<" \tCPU: "<<setw(10)<<left<<setprecision(4)<<timer.CpuTime()<<endl;
  143.                 timer.Start(false);
  144.                 next+=step;
  145.         }
  146.         if((cnt+1)/step==10)step*=10;
  147. }
  148. #endif
  149. ;
  150.  
  151. //---------- file handling -----------------------------
  152. int GetResult(vector<string>& out, const TString& command,bool nodup)
  153. #ifndef __NTHEADER___
  154. {
  155.     TString line;
  156.     FILE* pipe= gSystem->OpenPipe(command,"r");
  157.     if(!pipe){
  158.         cerr<<"Did not work: "<<command<<endl;
  159.     } else {
  160.         while (line.Gets(pipe)) if(line!="") {
  161.             out.push_back(string(line));
  162.         }
  163.         gSystem->ClosePipe(pipe);
  164.     }
  165.     if(nodup){
  166.         map<string, pair<unsigned,string> > singleOut;
  167.         map<string, pair<unsigned,string> >::iterator it;
  168.         unsigned i;
  169.         for(i=0;i<out.size();i++){
  170.             //check format
  171.             if(count(out[i].begin(),out[i].end(),'_') < 3) break;
  172.             //let's hope it fits
  173.             unsigned pos =out[i].rfind("_");
  174.             unsigned pos2=out[i].substr(0,pos).rfind("_")+1;
  175.             unsigned n=atoi(out[i].substr(pos2,pos-pos2).c_str());
  176.             it = singleOut.find(out[i].substr(0,pos2));
  177.             if(it!=singleOut.end()) {
  178.                 if(it->second.first<n) {
  179.                     it->second.first=n;
  180.                     it->second.second=out[i].substr(pos2);
  181.                 }
  182.             } else singleOut[out[i].substr(0,pos2)]=pair<unsigned,string>(n,out[i].substr(pos2));
  183.         }
  184.         if(i==out.size()){
  185.             if (out.size()!=singleOut.size()) cout<< out.size()-singleOut.size() <<" duplicates ignored!"<<endl;;
  186.             out.clear();
  187.             for(it = singleOut.begin();it!=singleOut.end();it++)
  188.                     out.push_back(it->first+it->second.second);
  189.         } else cout<<"File name format not appropriate for duplicate check!"<<endl;
  190.     }
  191.     return out.size();
  192. }
  193. #endif
  194. ;
  195. //AllRootFilesIn(dir,tree)           // any 'ls'-able directory
  196. //AllRootFilesIn(dir,tree,10)        // any 'ls'-able directory, first 10 files
  197. //AllRootFilesIn(dir,tree,"dcls")    // dcache -  needs proxy & dctools !
  198. //AllRootFilesIn(dir,tree,"dcls",10) //first 10 files in dir
  199. int AllRootFilesIn(const TString& dir,TChain* chain,const TString& LScommand,int max,bool nodup=false)
  200. #ifndef __NTHEADER___
  201. {
  202.     vector<string> files;
  203.     int n=0;
  204.     if(LScommand=="dcls"){
  205.         n=GetResult(files,"dcls "+dir+" | grep \"\\.root\" ",nodup);
  206.         n=n>max?max:n;
  207. //old       const string dcache_gate="dcap://dcache-ses-cms.desy.de:22125/";
  208.         const string dcache_gate="dcap://dcache-cms-dcap.desy.de:22125/";
  209.         for(int i=0;i<n;++i) chain->Add((dcache_gate+dir+"/"+files[i]));
  210.     } else if(LScommand=="rfdir") {
  211.         n=GetResult(files,LScommand+" "+dir+" | grep \"\\.root\" | awk \'{print $9}\' ",nodup);
  212.         n=n>max?max:n;
  213.         for(int i=0;i<n;++i) chain->Add(("rfio:"+dir+"/"+files[i]));
  214.     } else {
  215.         n=GetResult(files,LScommand+" "+dir+" | grep \"\\.root\"",nodup);
  216.         n=n>max?max:n;
  217.         for(int i=0;i<n;++i) chain->Add((dir+"/"+files[i]));
  218.     }
  219.     return n;
  220. }
  221. #endif
  222. ;
  223. // no duplicate check
  224. int AllRootFilesIn(const TString& dir,TChain* chain)
  225. #ifndef __NTHEADER___
  226. {
  227.     return AllRootFilesIn(dir,chain,"ls",1000,false);
  228. }
  229. #endif
  230. ;
  231. int AllRootFilesIn(const TString& dir,TChain* chain,const TString& LScommand)
  232. #ifndef __NTHEADER___
  233. {
  234.     return AllRootFilesIn(dir,chain,LScommand,1000,false);
  235. }
  236. #endif
  237. ;
  238. int AllRootFilesIn(const TString& dir,TChain* chain,int max)
  239. #ifndef __NTHEADER___
  240. {
  241.     return AllRootFilesIn(dir,chain,"ls",max,false);
  242. }
  243. #endif
  244. ;
  245. // duplicate check
  246. int AllRootFilesNoDup(const TString& dir,TChain* chain)
  247. #ifndef __NTHEADER___
  248. {
  249.     return AllRootFilesIn(dir,chain,"ls",1000,true);
  250. }
  251. #endif
  252. ;
  253. int AllRootFilesNoDup(const TString& dir,TChain* chain,const TString& LScommand)
  254. #ifndef __NTHEADER___
  255. {
  256.     return AllRootFilesIn(dir,chain,LScommand,1000,true);
  257. }
  258. #endif
  259. ;
  260. int AllRootFilesNoDup(const TString& dir,TChain* chain,int max)
  261. #ifndef __NTHEADER___
  262. {
  263.     return AllRootFilesIn(dir,chain,"ls",max,true);
  264. }
  265. #endif
  266. ;
  267. string file_base(const string& nam)
  268. #ifndef __NTHEADER___
  269. {
  270.     int dot=nam.rfind(".");
  271.     int slash=nam.rfind("/")+1;
  272.     return nam.substr(slash,dot-slash);
  273. }
  274. #endif
  275. ;
  276. TString file_base(const TString& nam)
  277. #ifndef __NTHEADER___
  278. {
  279.     const string namstr(nam.Data());
  280.     return file_base(namstr).c_str();
  281. }
  282. #endif
  283. ;
  284. #endif
  285. //---------- chain and tree handling -----------------------------
  286. // inline is redundant since this is a header files
  287. class EasyChain: public TChain {
  288. public:
  289.     EasyChain(const char* tname) : TChain(tname), localEntry(0), localMax(0), off(0), dcache(false) {
  290.         fileWeight=0;
  291.     };
  292.  
  293.     // here all kinds of variables can be load from the chain
  294.     // e.g.: vector<LorentzV>* electrons = tree->Get(&electrons,"electronP4Pat");
  295.     //       electron->size()
  296.     template<typename T>
  297.     inline T* Get(T** ppt, const char* name){
  298.         TBranch* branch;
  299.         // This increases the performance since GetBranch searches the full tree
  300.         // byNames.find only the used names
  301.         if( localByName.find(name)==localByName.end() ) {
  302.             branch = byName[name] = GetBranch( name );
  303.             localByName[name].second = 0;
  304.             localByName[name].first = -1;
  305.         }
  306.         else branch=byName[name];
  307.         if(branch==0) {
  308.             cerr<<"Branch "<<name<<" is undefined in tree: "<<GetName()<<endl;
  309.             exit(0);
  310.         }
  311.         if(localByName[name].first==localEntry && localByName[name].second!=0)
  312.             return static_cast<T*>(localByName[name].second);
  313.         if(localByName[name].second!=0){
  314.             T* toDelete = static_cast<T*>(localByName[name].second);
  315.             delete toDelete;
  316.             localByName[name].first=-1;
  317.         }
  318.         *ppt=0;
  319.         branch->SetAddress( ppt );
  320.         branch->GetEntry(localEntry,1);
  321.         localByName[name].second=*ppt;
  322.         localByName[name].first=localEntry;
  323.         return *ppt;
  324.     };
  325.     template<typename T>
  326.     inline T* Get(T** ppt, const TString& name){
  327.         return Get(ppt,name.Data());
  328.     }
  329.     // the same as above but the return type is a reference (same performance)
  330.     // e.g.: vector<LorentzV>& electrons = tree->Get(&electrons,"electronP4Pat");
  331.     //       Electron.size()
  332.     template<typename T>
  333.     inline T& Get(T* leaf,const char* name) {
  334.         leaf=leaf;//just to get rid of the  unused warning
  335.         TBranch* branch;
  336.         if( localByName.find(name)==localByName.end() ) {
  337.             branch = byName[name] = GetBranch( name );
  338.             localByName[name].second = 0;
  339.             localByName[name].first = -1;
  340.         }
  341.         else branch=byName[name];
  342.         if(branch==0) {
  343.             cerr<<"Branch "<<name<<" is undefined in tree: "<<GetName()<<endl;
  344.             exit(0);
  345.         }
  346.         if(localByName[name].first==localEntry && localByName[name].second!=0) return *static_cast<T*>(localByName[name].second);
  347.         if(localByName[name].second!=0) {
  348.             T* toDelete = static_cast<T*>(localByName[name].second);
  349.             delete toDelete;
  350.             localByName[name].first=-1;
  351.         }
  352.         T* pt=0;
  353.         branch->SetAddress( &pt );
  354.         branch->GetEntry(localEntry,1);
  355.         localByName[name].second=pt;
  356.         localByName[name].first=localEntry;
  357.         return *pt;
  358.     };
  359.     template<typename T>
  360.     inline T& Get(T* leaf, const TString& name) {
  361.         return Get(leaf,name.Data());
  362.     }
  363.     // this is meant for simple data types as int,float,double i.e. splitlevel 0
  364.     // e.g.: unsigned run = tree->Get(run,"run");
  365.     //                          note: ^ no &
  366.     template<typename T>
  367.     inline T Get(T& leaf,const char* name) {
  368.         TBranch* branch;
  369.         if( byName.find(name)==byName.end() ) branch=byName[name] = GetBranch( name );
  370.         else branch=byName[name];
  371.         if(branch==0) {
  372.             cerr<<"Branch "<<name<<" is undefined in tree: "<<GetName()<<endl;
  373.             exit(0);
  374.         }
  375.         branch->SetAddress(&leaf);
  376.         branch->GetEntry(localEntry,1);
  377.         return leaf;
  378.     };
  379.     template<typename T>
  380.     inline T Get(T& leaf, const TString& name) {
  381.         return Get(leaf,name.Data() ) ;
  382.     }
  383.  
  384.     template<typename T>
  385.     inline void GetCA(vector<T*>& some ,int N, const char* name){ // not optimal efficient but simple syntax
  386.         TClonesArray* ar = Get(&ar,name);
  387.         some.clear();
  388.         for(int i = 0;i < N;i++) {
  389.             some.push_back( (T*)ar->At(i) );
  390.         }
  391.     };
  392.     template<typename T>
  393.     inline void GetCA(vector<T*>& some ,int N,const TString& name){
  394.         GetCA(some,N,name.Data());
  395.     };
  396.  
  397.     template<typename T>
  398.     inline void Get4(vector<T*>& some ,int N, const char* name){ // not optimal efficient but simple syntax
  399.         TClonesArray* ar = Get(&ar,name);
  400.         some.clear();
  401.         for(int i = 0;i < N;i++) {
  402.             ((T*)(ar->At(i)))->Fill4();
  403.             some.push_back( (T*)ar->At(i) );
  404.         }
  405.     };
  406.     template<typename T>
  407.     inline void Get4(vector<T*>& some ,int N,const TString& name){
  408.         Get4(some,N,name.Data());
  409.     };
  410.  
  411. private:
  412.     void FillBranchAddresses(TTree* t){
  413.         for(map<const string,TBranch*>::iterator it=byName.begin();it!=byName.end();++it)
  414.             it->second = t->GetBranch(  it->first.c_str() );
  415.     }
  416.  
  417. public:
  418.     // get entry and check for new tree
  419.     // sequential gives best performance
  420.     virtual inline Int_t GetEntry(Long64_t  entry, Int_t getall = 0){
  421.         getall=getall;//just to get rid of unused warning
  422.         localEntry=entry-off;
  423.         if(localEntry>=localMax||localEntry<0){
  424.             localEntry=LoadTree(entry);
  425.             if(fTree) localMax=fTree->GetEntries();
  426.             else cout<<"hmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm"<<endl;
  427.             off=fTreeOffset[fTreeNumber];
  428.             FillBranchAddresses(fTree);
  429.         }
  430.         return localEntry>=0;
  431.     }
  432.     // get entry and check for new tree + weight
  433.     // sequential gives best performance
  434.     inline double GetEntryW(Long64_t  entry, Int_t getall = 0){
  435.         getall=getall;//just to get rid of unused warning
  436.         localEntry=entry-off;
  437.         if(localEntry>=localMax||localEntry<0){
  438.             localEntry=LoadTree(entry);
  439.             if(fTree) localMax=fTree->GetEntries();
  440.             else cout<<"hmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm"<<endl;
  441.             off=fTreeOffset[fTreeNumber];
  442.             FillBranchAddresses(fTree);
  443.             TString filename = GetFile()->GetName();
  444.             filename.ReplaceAll("//","/");
  445.             TString nam( filename(0,filename.Last('/')+1) );
  446.             nam.ReplaceAll("//","/");
  447.             if( weights.find(nam)==weights.end() ) {
  448.                 cout<<"NtupleTools3:GetEntry don't find a weight for "<<filename<<" "<<nam<<endl;
  449.                 exit(0);
  450.             }
  451.             fileWeight=weights[nam];
  452.         }
  453.         return fileWeight;
  454.     }
  455.     // helper function for AddSmart below
  456.     int AddSmartSingle(const TString& name,int max,bool nodup){
  457.         string dcache_gate="dcap://dcache-cms-dcap.desy.de:22125/";
  458.         vector<string> files;
  459.         int n=1;
  460.         if (name.Index("/pnfs/")==0 || name.Index("/store/")==0){
  461.             if(name.Index("/store/")==0) dcache_gate+="/pnfs/desy.de/cms/tier2/";
  462.             if(name.EndsWith("/")){
  463.                 n=GetResult(files,"dcls "+name+" | grep \"\\.root$\" ",nodup);
  464.                 n=n>max?max:n;
  465.                 for(int i=0;i<n;++i) Add((dcache_gate+name+"/"+files[i]));
  466.             } else  Add(dcache_gate+name);
  467.             dcache=true;
  468.         } else {
  469.             if(name.EndsWith("/")){
  470.                 n=GetResult(files,"ls "+name+" | grep \"\\.root$\"",nodup);
  471.                 n=n>max?max:n;
  472.                 for(int i=0;i<n;++i) Add((name+"/"+files[i]));
  473.             } else Add(name);
  474.         }
  475.         return n;
  476.     }
  477.     // Add single file or all files in directory to this tree
  478.     // assumes /pnfs or /store indicates dcache
  479.     // takes trailing / as directory
  480.     // checks for dir on normal filesystem
  481.     // if name contains ' ' or ',' assume a list of filenames
  482.     int AddSmart(const TString& longname,int max=10000,bool nodup=false){
  483.         // check if name contains ' ' or ','
  484.         TObjArray*  arr = longname.Tokenize(',');
  485.         if(arr->GetEntries()==1) arr = longname.Tokenize(' ');
  486.         if(arr->GetEntries()==1)
  487.             // just one name
  488.             return  AddSmartSingle(longname,max,nodup);
  489.         int k=0;
  490.         for(int idx=0;idx<arr->GetEntries();idx++){
  491.             TObjString* tok = (TObjString*) (*arr)[idx];
  492.                 k+=AddSmartSingle(tok->GetName(),max,nodup);
  493.                 max-=k;
  494.         }
  495.         return k;
  496.     }
  497.     int AddSmartW(const TString& longname,double w,int max=10000,bool nodup=false){
  498.         TString nam=longname;
  499.         nam.ReplaceAll("//","/");
  500.         weights[nam]=w;
  501.         return AddSmart(longname,max,nodup);
  502.     }
  503.     // return an (almost) unique file name depending on all files added to this chain
  504.     // if only 1 file use basename
  505.     string GetUniqeName(const char* base="SusyCAF_Tree"){
  506.         stringstream sstr;
  507.         sstr<<base;
  508.         TObjArray* trees = GetListOfFiles();
  509.         if(trees==0) {
  510.             cout<<"EasyChain::GetUniqeName no files in tree"<<endl;
  511.             exit(0);
  512.         } else if(trees->GetEntries()==1) {
  513.             string s((*trees)[0]->GetTitle());
  514.             return file_base(s)+"_out.root";
  515.         } else {
  516.             // we create a hash number depending on all names
  517.             sstr<<"_n"<<trees->GetEntries();
  518.             TString longname;
  519.             TIter next(trees);
  520.             TChainElement *chEl=0;
  521.             while (( chEl=(TChainElement*)next() )) longname.Append(chEl->GetTitle());
  522.             sstr<<"_"<<longname.Hash()<<"_out.root";
  523.             return sstr.str();
  524.         }
  525.     };
  526.     void GetAll(){
  527.         cout<<"EasyChain::GetAll not implemented"<<endl;
  528.     }
  529.     double fileWeight;
  530. private:
  531.     int localEntry;
  532.     int localMax;
  533.     int off;
  534.     // files are from dcache
  535.     bool dcache;
  536.     // acts as booster for tree with many branches
  537.     map<const string,TBranch*> byName;
  538.     map<string, pair<int,void*> > localByName; // a pointer to the branch and the localEntry number for which it had been read
  539.     map<TString,double>  weights;// weights depending on filename or dirname in AddSmartW
  540. #ifdef  __NTHEADER___
  541.     ClassDef(EasyChain, 1);
  542. #endif
  543. };
  544. //}
  545. #endif
Advertisement
Add Comment
Please, Sign In to add comment