CString Management_by Joseph M. Newcomer@code project
2006-09-12 22:32
447 查看
CStringconvert(BSTRb) { if(b==NULL) returnCString(_T("")); CStrings(b);//inUNICODEmode returns; }
IfyouareinANSImode,youneedtoconvertthestringinamorecomplexfashion.Thiswillaccomplishit.Notethatthiscodeusesthesameargumentvaluesto
::WideCharToMultiBytethattheimplicitconstructorfor
CStringuses,soyouwouldusethistechniqueonlyifyouwantedtochangetheseparameterstodotheconversioninsomeotherfashion,forexample,specifyingadifferentdefaultcharacter,adifferentsetofflags,etc.
Collapse
CStringconvert(BSTRb) { CStrings; if(b==NULL) returns;//emptyforNULLBSTR #ifdefUNICODE s=b; #else LPSTRp=s.GetBuffer(SysStringLen(b)+1); ::WideCharToMultiByte(CP_ACP,//ANSICodePage 0,//noflags b,//sourcewidecharstring -1,//assumeNUL-terminated p,//targetbuffer SysStringLen(b)+1,//targetbufferlength NULL,//usesystemdefaultchar NULL);//don'tcareifdefaultused s.ReleaseBuffer(); #endif returns; }
NotethatIdonotworryaboutwhathappensifthe
BSTRcontainsUnicodecharactersthatdonotmaptothe8-bitcharacterset,becauseIspecify
NULLasthelasttwoparameters.Thisisthesortofthingyoumightwanttochange.
VARIANTtoCString
Actually,I'veneverdonethis;Idon'tworkinCOM/OLE/ActiveXwherethisisanissue.ButIsawapostingbyRobertQuirkonthemicrosoft.public.vc.mfcnewsgrouponhowtodothis,anditseemedsillynottoincludeitinthisessay,sohereitis,withabitmoreexplanationandelaboration.Anyerrorsrelativetowhathewrotearemyfault.
A
VARIANTisagenericparameter/returntypeinCOMprogramming.Youcanwritemethodsthatreturnatype
VARIANT,andwhichtypethefunctionreturnsmay(andoftendoes)dependontheinputparameterstoyourmethod(forexample,inAutomation,dependingonwhichmethodyoucall,
IDispatch::Invokemayreturn(viaoneofitsparameters)a
VARIANTwhichholdsa
BYTE,a
WORD,an
float,a
double,adate,a
BSTR,andaboutthreedozenothertypes(seethespecificationsofthe
VARIANTstructureintheMSDN).Intheexamplebelow,itisassumedthatthetypeisknowntobeavariantoftype
BSTR,whichmeansthatthevalueisfoundinthestringreferencedby
bstrVal.Thistakesadvantageofthefactthatthereisaconstructorwhich,inanANSIapplication,willconvertavaluereferencedbyan
LPCWCHARtoa
CString(see
BSTR
CString).InUnicodemode,thisturnsouttobethenormal
CStringconstructor.Seethecaveatsaboutthedefault
::WideCharToMultibyteconversionandwhetherornotyoufindtheseacceptable(mostly,youwill).
VARIANTvaData; vaData=m_com.YourMethodHere(); ASSERT(vaData.vt==VT_BSTR); CStringstrData(vaData.bstrVal);
Notethatyoucouldalsomakeamoregenericconversionroutinethatlookedatthe
vtfield.Inthiscase,youmightconsidersomethinglike:
CStringVariantToString(VARIANT*va) { CStrings; switch(va->vt) {/*vt*/ caseVT_BSTR: returnCString(vaData->bstrVal); caseVT_BSTR|VT_BYREF: returnCString(*vaData->pbstrVal); caseVT_I4: s.Format(_T("%d"),va->lVal); returns; caseVT_I4|VT_BYREF: s.Format(_T("%d"),*va->plVal); caseVT_R8: s.Format(_T("%f"),va->dblVal); returns; ...remainingcasesleftasanExerciseForTheReader default: ASSERT(FALSE);//unknownVARIANTtype(thisASSERTisoptional) returnCString(""); }/*vt*/ }
LoadingSTRINGTABLEvalues
Ifyouwanttocreateaprogramthatiseasilyportedtootherlanguages,youmustnotincludenative-languagestringsinyoursourcecode.(Fortheseexamples,I'lluseEnglish,sincethatismynativelanguage(aberIchkanneinbischenDeutschsprechen).SoitisverybadpracticetowriteCStrings="Thereisanerror";
Instead,youshouldputallyourlanguage-specificstrings(except,perhaps,debugstrings,whichareneverinaproductdeliverable).Thismeansthatisfinetowrite
s.Format(_T("%d-%s"),code,text);
inyourprogram;thatliteralstringisnotlanguage-sensitive.However,youmustbeverycarefultonotusestringslike
//fmtis"Errorin%sfile%s"
//readorwriteis"reading"or"writing"
s.Format(fmt,readorwrite,filename);
Ispeakofthisfromexperience.InmyfirstinternationalizedapplicationImadethiserror,andinspiteofthefactthatIknowGerman,andthatGermanwordorderplacestheverbattheendofasentence,Ihaddonethis.OurGermandistributorcomplainedbitterlythathehadtocomeupwithtrulyweirderrormessagesinGermantogettheformatcodestodotherightthing.Itismuchbetter(andwhatIdonow)tohavetwostrings,oneforreadingandoneforwriting,andloadtheappropriateone,makingthemstringparameter-insensitive,thatis,insteadofloadingthestrings"reading"or"writing",loadthewholeformat:
//fmtis"Errorinreadingfile%s"
//"Errorinwritingfile%s"
s.Format(fmt,filename);
Notethatifyouhavemorethanonesubstitution,youshouldmakesurethatifthewordorderofthesubstitutionsdoesnotmatter,forexample,subject-object,subject-verb,orverb-object,inEnglish.
Fornow,Iwon'ttalkabout
FormatMessage,whichactuallyisbetterthan
sprintf/
Format,butispoorlyintegratedintothe
CStringclass.Itsolvesthisbynamingtheparametersbytheirpositionintheparameterlistandallowsyoutorearrangethemintheoutputstring.
Sohowdoweaccomplishallthis?Bystoringthestringvaluesintheresourceknownasthe
STRINGTABLEintheresourcesegment.Todothis,youmustfirstcreatethestring,usingtheVisualStudioresourceeditor.AstringisgivenastringID,typicallystarting
IDS_.Soyouhaveamessage,youcreatethestringandcallit
IDS_READING_FILEandanothercalled
IDS_WRITING_FILE.Theyappearinyour.rcfileas
STRINGTABLE
IDS_READING_FILE"Readingfile%s"
IDS_WRITING_FILE"Writingfile%s"
END
Note:theseresourcesarealwaysstoredasUnicodestrings,nomatterwhatyourprogramiscompiledas.TheyareevenUnicodestringsonWin9xplatforms,whichotherwisehavenorealgraspofUnicode(buttheydoforresources!).Thenyougotowhereyouhadstoredthestrings
//previouscode
CStringfmt;
if(...)
fmt="Readingfile%s";
else
fmt="Writingfile%s";
...
//muchlater
CStrings;
s.Format(fmt,filename);
andinsteaddo
//revisedcode
CStringfmt;
if(...)
fmt.LoadString(IDS_READING_FILE);
else
fmt.LoadString(DS_WRITING_FILE);
...
//muchlater
CStrings;
s.Format(fmt,filename);
Nowyourcodecanbemovedtoanylanguage.The
LoadStringmethodtakesastringIDandretrievesthe
STRINGTABLEvalueitrepresents,andassignsthatvaluetothe
CString.
Thereisacleverfeatureofthe
CStringconstructorthatsimplifiestheuseof
STRINGTABLEentries.Itisnotexplicitlydocumentedinthe
CString::CStringspecification,butisobscurelyshownintheexampleusageoftheconstructor!(Whythiscouldn'tbepartoftheformaldocumentationandhastobeshowninanexampleescapesme!).Thefeatureisthatifyoucasta
STRINGTABLEIDtoan
LPCTSTRitwillimplicitlydoa
LoadString.Thusthefollowingtwoexamplesofcreatingastringvalueproducethesameeffect,andthe
ASSERTwillnottriggerindebugmodecompilations:
CStrings;
s.LoadString(IDS_WHATEVER);
CStringt((LPCTSTR)IDS_WHATEVER);
ASSERT(s==t);
Now,youmaysay,howcanthispossiblywork?Howcanittellavalidpointerfroma
STRINGTABLEID?Simple:allstringIDsareintherange1..65535.Thismeansthatthehigh-orderbitsofthepointerwillbe0.Soundsgood,butwhatifIhavevaliddatainalowaddress?Well,theansweris,youcan't.Thelower64Kofyouraddressspacewillnever,ever,exist.Anyattempttoaccessavalueintheaddressrange
0x00000000through
0x0000FFFF(0..65535)willalwaysandforevergiveanaccessfault.Theseaddressesarenever,evervalidaddresses.Thusavalueinthatrange(otherthan0)mustnecessarilyrepresenta
STRINGTABLEID.
Itendtousethe
MAKEINTRESOURCEmacrotodothecasting.Ithinkitmakesthecodeclearerregardingwhatisgoingon.Itisastandardmacrowhichdoesn'thavemuchapplicabilityotherwiseinMFC.Youmayhavenotedthatmanymethodstakeeithera
UINToran
LPCTSTRasparameters,usingC++overloading.ThisgetsusaroundtheuglinessofpureCwherethe"overloaded"methods(whicharen'treallyoverloadedinC)requiredexplicitcasts.Thisisalsousefulinassigningresourcenamestovariousotherstructures.
CStrings;
s.LoadString(IDS_WHATEVER);
CStringt(MAKEINTRESOURCE(IDS_WHATEVER));
ASSERT(s==t);
Justtogiveyouanidea:IpracticewhatIpreachhere.Youwillrarelyifeverfindaliteralstringinmyprogram,otherthantheoccasionaldebugoutputmessages,and,ofcourse,anylanguage-independentstring.
CStringsandtemporaryobjects
Here'salittleproblemthatcameuponthemicrosoft.public.vc.mfcnewsgroupawhileago.I'llsimplifyitabit.ThebasicproblemwastheprogrammerwantedtowriteastringtotheRegistry.Sohewrote:
Iamtryingtosetaregistryvalueusing
RegSetValueEx()anditisthevaluethatIamhavingtroublewith.IfIdeclareavariableof
char[]itworksfine.However,Iamtryingtoconvertfroma
CStringandIgetgarbage."ÝÝÝÝ...ÝÝÝÝÝÝ"tobeexact.Ihavetried
GetBuffer,typecastingto
char*,
LPCSTR.Thereturnof
GetBuffer(fromdebug)isthecorrectstringbutwhenIassignittoa
char*(or
LPCSTR)itisgarbage.Followingisapieceofmycode:
char*szName=GetName().GetBuffer(20);
RegSetValueEx(hKey,"Name",0,REG_SZ,
(CONSTBYTE*)szName,
strlen(szName+1));
The
Namestringislessthen20charslong,soIdon'tthinkthe
GetBufferparameteristoblame.Itisveryfrustratingandanyhelpisappreciated.
DearFrustrated,
Youhavebeendoneinbyafairlysubtleerror,causedbytryingtobeabittooclever.Whathappenedwasthatyoufellvictimtoknowingtoomuch.Thecorrectcodeisshownbelow:
CStringName=GetName();
RegSetValueEx(hKey,_T("Name"),0,REG_SZ,
(CONSTBYTE*)(LPCTSTR)Name,
(Name.GetLength()+1)*sizeof(TCHAR));
Here'swhymycodeworksandyoursdidn't.Whenyourfunction
GetNamereturneda
CString,itreturneda"temporaryobject".SeetheC++Referencemanual§12.2.
Insomecircumstancesitmaybenecessaryorconvenientforthecompilertogenerateatemporaryobject.Suchintroductionoftemporariesisimplementationdependent.Whenacompilerintroducesatemporaryobjectofaclassthathasaconstructoritmustensurethataconstructiscalledforthetemporaryobject.Similarly,thedestructormustbecalledforatemporaryobjectofaclasswhereadestructorisdeclared.
Thecompilermustensurethatatemporaryobjectisdestroyed.Theexactpointofdestructionisimplementationdependent....Thisdestructionmusttakeplacebeforeexitfromthescopeinwhichthetemporaryiscreated.
Mostcompilersimplementtheimplicitdestructorforatemporaryatthenextprogramsequencingpointfollowingitscreation,thatis,forallpracticalpurposes,thenextsemicolon.Hencethe
CStringexistedwhenthe
GetBuffercallwasmade,butwasdestroyedfollowingthesemicolon.(Asanaside,therewasnoreasontoprovideanargumentto
GetBuffer,andthecodeaswrittenisincorrectsincethereisno
ReleaseBufferperformed).Sowhat
GetBufferreturnedwasapointertostorageforthetextofthe
CString.Whenthedestructorwascalledatthesemicolon,thebasic
CStringobjectwasfreed,alongwiththestoragethathadbeenallocatedtoit.TheMFCdebugstorageallocatorthenrewritesthisfreedstoragewith0xDD,whichisthesymbol"Ý".BythetimeyoudothewritetotheRegistry,thestringcontentshavebeendestroyed.
Thereisnoparticularreasontoneedtocasttheresulttoa
char*immediately.Storingitasa
CStringmeansthatacopyoftheresultismade,soafterthetemporary
CStringisdestroyed,thestringstillexistsinthevariable's
CString.ThecastingatthetimeoftheRegistrycallissufficienttogetthevalueofastringwhichalreadyexists.
Inaddition,mycodeisUnicode-ready.TheRegistrycallwantsabytecount.Notealsothatthecall
lstrlen(Name+1)returnsavaluethatistoosmallby2foranANSIstring,sinceitdoesn'tstartuntilthesecondcharacterofthestring.Whatyoumeanttowritewas
lstrlen(Name)+1(OK,Iadmitit,I'vemadethesameerror!).However,inUnicode,whereallcharactersaretwobyteslong,weneedtocopewiththis.TheMicrosoftdocumentationissurprisinglysilentonthispoint:isthevaluegivenfor
REG_SZvaluesabytecountoracharactercount?I'massumingthattheirspecificationof"bytecount"meansexactlythat,andyouhavetocompensate.
CStringEfficiency
OneproblemofCStringisthatithidescertaininefficienciesfromyou.Ontheotherhand,italsomeansthatitcanimplementcertainefficiencies.Youmaybetemptedtosayofthefollowingcode
CStrings=SomeCString1;
s+=SomeCString2;
s+=SomeCString3;
s+=",";
s+=SomeCString4;
thatitishorriblyinefficientcomparedto,say
chars[1024];
lstrcpy(s,SomeString1);
lstrcat(s,SomeString2);
lstrcat(s,SomeString3);
lstrcat(s,",");
lstrcat(s,SomeString4);
Afterall,youmightthink,firstitallocatesabuffertohold
SomeCString1,thencopies
SomeCString1toit,thendetectsitisdoingaconcatenate,allocatesanewbufferlargeenoughtoholdthecurrentstringplus
SomeCString2,copiesthecontentstothebufferandconcatenatesthe
SomeCString2toit,thendiscardsthefirstbufferandreplacesthepointerwithapointertothenewbuffer,thenrepeatsthisforeachofthestrings,beinghorriblyinefficientwithallthosecopies.
Thetruthis,itprobablynevercopiesthesourcestrings(theleftsideofthe+=)formostcases.
InVC++6.0,inReleasemode,all
CStringbuffersareallocatedinpredefinedquanta.Thesearedefinedas64,128,256,and512bytes.Thismeansthatunlessthestringsareverylong,thecreationoftheconcatenatedstringisanoptimizedversionofa
strcatoperation(sinceitknowsthelocationoftheendofthestringitdoesn'thavetosearchforit,as
strcatwould;itjustdoesa
memcpytothecorrectplace)plusarecomputationofthelengthofthestring.Soitisaboutasefficientastheclumsierpure-Ccode,andonewholeloteasiertowrite.Andmaintain.Andunderstand.
Thoseofyouwhoaren'tsurethisiswhatisreallyhappening,lookinthesourcecodefor
CString,strcore.cpp,inthemfc/srcsubdirectoryofyourvc98installation.Lookforthemethod
ConcatInPlacewhichiscalledfromallthe+=operators.
Aha!So
CStringisn'treally"efficient!"Forexample,ifIcreate
CStringcat("Mew!");
thenIdon'tgetanice,tidylittlebuffer5byteslong(4databytesplustheterminal
NUL).Insteadthesystemwastesallthatspacebygivingme64bytesandwasting59ofthem.
Ifthisishowyouthink,bepreparedtoreeducateyourself.Somewhereinyourcareersomebodytaughtyouthatyoualwayshadtouseaslittlespaceaspossible,andthiswasaGoodThing.
Thisisincorrect.Itignoressomeseriouslyimportantaspectsofreality.
Ifyouareusedtoprogrammingembeddedapplicationswith16KEPROMs,youhaveaparticularmindsetfordoingsuchallocation.Forthatapplicationdomain,thisishealthy.ButforwritingWindowsapplicationson500MHz,256MBmachines,itactuallyworksagainstyou,andcreatesprogramsthatperformfarworsethanwhatyouwouldthinkofas"lessefficient"code.
Forexample,sizeofstringsisthoughttobeafirst-ordereffect.ItisGoodtomakethissmall,andBadtomakeitlarge.Nonsense.Theeffectofpreciseallocationisthatafterafewhoursoftheprogramrunning,theheapisclutteredupwithlittletinypiecesofstoragewhichareuselessforanything,buttheyincreasethestoragefootprintofyourapplication,increasepagingtraffic,canactuallyslowdownthestorageallocatortounacceptableperformancelevels,andeventuallyallowyourapplicationtogrowtoconsumeallofavailablememory.Storagefragmentation,asecond-orderorthird-ordereffect,actuallydominatessystemperformance.Eventually,itcompromisesreliability,whichiscompletelyunacceptable.
NotethatinDebugmodecompilations,theallocationisalwaysexact.Thishelpsshakeoutbugs.
Assumeyourapplicationisgoingtorunformonthsatatime.Forexample,IbringupVC++,Word,PowerPoint,FrontPage,OutlookExpress,FortéAgent,InternetExplorer,andafewotherapplications,andessentiallyneverclosethem.I'veeditedusingPowerPointfordaysonend(ontheotherhand,ifyou'vehadthemisfortunetohavetousesomethinglikeAdobeFrameMaker,youbegintoappreciatereliability;I'verarelybeenabletousethisapplicationwithoutitcrashingfourtosixtimesaday!Andalwaysbecauseithasrunoutofspace,usuallybyfillingupmyentiremassiveswapspace!)Preciseallocationisoneofthemisfeaturesthatwillcompromisereliabilityandleadtoapplicationcrashes.
Bymaking
CStringsbemultiplesofsomequantum,thememoryallocatorwillendupclutteredwithchunksofmemorywhicharealmostalwaysimmediatelyreusableforanother
CString,sothefragmentationisminimized,allocatorperformanceisenhanced,applicationfootprintremainsalmostassmallaspossible,andyoucanrunforweeksormonthswithoutproblem.
Aside:Manyyearsago,atCMU,wewerewritinganinteractivesystem.Somestudiesofthestorageallocatorshowedthatithadatendencytofragmentmemorybadly.JimMitchell,nowatSunMicrosystems,createdastorageallocatorthatmaintainedrunningstatisticsaboutallocationsize,suchasthemeanandstandarddeviationofallallocations.Ifachunkofstoragewouldbesplitintoasizethatwassmallerthanthemeanminusone
sthantheprevailingallocation,hedidn'tsplititatall,thusavoidingclutteringuptheallocatorwithpiecestoosmalltobeusable.Heactuallyusedfloatingpointinsideanallocator!Hisobservationwasthatthelong-termsavingininstructionsbynothavingtoignoreunusablesmallstoragechunksfarandawayexceededtheadditionalcostofdoingafewfloatingpointoperationsonanallocationoperation.Hewasright.
Never,everthinkabout"optimization"intermsofsmall-and-fastanalyzedonaper-line-of-codebasis.Optimizationshouldmeansmall-and-fastanalyzedatthecompleteapplicationlevel(ifyoulikeNewAgebuzzwords,thinkofthisastheholisticapproachtoprogramoptimization,awholelotbetterthantheper-linebasisweteachnewprogrammers).Atthecompleteapplicationlevel,minimum-chunkstringallocationisabouttheworstmethodyoucouldpossiblyuse.
Ifyouthinkoptimizationissomethingyoudoatthecode-linelevel,thinkagain.Optimizationatthislevelrarelymatters.Readmyessayon
Notethatthe+=operatorisspecial-cased;ifyouweretowrite:
CStrings=SomeCString1+SomeCString2+SomeCString3+","+SomeCString4;
theneachapplicationofthe+operatorcausesanewstringtobecreatedandacopytobedone(althoughitisanoptimizedversion,sincethelengthofthestringisknownandtheinefficienciesof
strcatdonotcomeintoplay).
Summary
ThesearejustsomeofthetechniquesforusingCString.Iusetheseeverydayinmyprogramming.
CStringisnotaterriblydifficultclasstodealwith,butgenerallytheMFCmaterialsdonotmakeallofthisapparent,leavingyoutofigureitoutonyourown.
相关文章推荐
- DLINQ Introduction Part 2 Of 3 By Sacha Barber(Refer from codeproject)
- Get data by SAFEARRAY** from COM(VB Code) in My VC COM Project (Code Part)
- The Project Management Life Cycle: A Complete Step-By-Step Methodology For Initiating, Planning, Exe
- Running CodeBehind ASP.NET CSharp Projects on Mono by Charlie Calvert
- LINQ Introduction Part 1 Of 3 By Sacha Barber(Refer from codeproject)
- Process-wide API spying - an ultimate hack[ By Anton Bassov ][www.codeproject.com]
- Cache Management in ASP.NET(http://www.codeproject.com/KB/web-cache/cachemanagementinaspnet.aspx)
- Running CodeBehind ASP.NET CSharp Projects on Mono by Charlie Calvert
- XLINQ Introduction Part 3 Of 3 By Sacha Barber(Refer from codeproject)
- Team Project Proposal: 开始页--为你的Outlook而生--By Qiaolin Xia
- CodeProject每日精选: Progress controls 进度条
- Creating and consuming MFC DLLs for Beginners(转自www.codeproject.com)
- experiment : convert function as shellCode on our C project
- 10021---Creating a Code Base for Your Project
- tingPM -- a Agile project management tool
- NHibernate 3.2或以上的版本就没有 NHibernate.ByteCode.Castle.dll,NHibernate.ByteCode.LinFu.dll, NHibernate.Byt
- Unable to locate Android SDK used by project
- Android Project from Existing Code 生成 R 文件错误、失败等问题解决办法 - 持续更新
- Moving to Windows Vista x64(codeproject)
- Systems Thinking in Project Management