21.08.2015 Views

Pro

May 1998 - JeffLuther.net

May 1998 - JeffLuther.net

SHOW MORE
SHOW LESS
  • No tags were found...

Create successful ePaper yourself

Turn your PDF publications into a flip-book with our unique Google optimized e-Paper software.

<strong>Pro</strong>CenturaTMVisit us at www.<strong>Pro</strong>Publishing.com!Hot Ideas for Centura® DevelopersA Java Client forRemote ObjectsGianluca PivatoMay 1998Volume 3, Number 51 A Java Client for Remote ObjectsGianluca PivatoJava is a greatlanguage; it’slike a simpler C++.But it’s still young andit’s not really strong onthe databaseconnectivity side.Historically, one ofCentura’s strongestfeatures has been itsdatabase connectivity.Using the included Javaclass library (maybe areal Java programmercould fix it up a little)you could leave yourbusiness rules and datamanagement in aCentura TeamDeveloper applicationand access it from Java,anywhere on thenetwork.Another situation in which you might use thisIn March 1998 Sybaseannounced plans for a futureJavaBeans proxy generator,which makes PowerBuilder logiclook like a JavaBean to developers. But aningenious, independent Centura developer hasbeaten them to the punch.Pivato’s article on Remote Objects in the November1997 issue of Centura <strong>Pro</strong> made it easy for multipleCTD or SQLWindows applications to do distributedcomputing with each other over a TCP/IPconnection. Now he dramatically expands theusefulness of this technique, by allowing the samecapability between Centura applications and Javaapplications or applets. A Java class library(package) makes it possible. Most of this articlediscusses how to code in Java to take advantage ofthis package. There are also some improvements tothe November version of the ROB library.approach is when you have some CTD code that is usingWindows proprietary code to access an external deviceand you don’t want to use Java’s native code interface.This way you can leave your proprietary code in aCentura object and access it from Java and Centura at thesame time.Differences between Java and SQLWindows/CTDThe differences are many! The two major differences that Ihad to face in order to write this library involved zeroS Q L W i n d o w sC e n t u r a2 Rally ’Round the FlagMark Hunter6 Tip: Token VirtuosityMike Robinson7 Animate Your App!R.J. David Burke11 Get That Picture BackR.J. David Burke12 Tip: '\'Quitchor\'\' Quotin\'\'Quibblin\'\'\''R.J. David Burketerminated strings and return parameters. Java stringsaren’t zero terminated. And Java can’t pass primitivetypes (integers, strings, or the like), also known as“intrinsic objects,” by reference.Zero terminated stringsIn CTD, as in C, strings are zero terminated. Whichdoesn’t mean that you can’t have non-zero terminatedstrings; it means only that CTD recognizes the zero as theterminator of the readable string. You can always haveblobs with many zeros here and there and just not careContinues on page 3


Centura<strong>Pro</strong>Rally ’Round the FlagMark HunterWhere are the Americans? We werechatting about that at <strong>Pro</strong>Publishing recently. Historically,Centura Software has obtained about halfof its revenue from the United States andthe other half from international sales.Recently the international market hasgrown to more than half. But the“mindshare” market seems to belong, evenmore completely, to overseas customers.Please don’t misunderstand—<strong>Pro</strong>Publishing loves its international customersand authors! My personal experience is thatinternational customers speak out more and supportCentura Software more strongly than American customersdo. And a quick look at our authors (check our Web site)shows that most of them either live outside the U.S. orwere born outside the U.S. and now work here. EvenDavid Burke, our most prolific contributor, is Canadian.Without our international authors, Centura <strong>Pro</strong> would be amuch poorer newsletter, indeed.A lot of sophisticated applications exist in U.S.companies, and more are being written all the time. Thereare a lot of talented developers, too. Certainly there is nolack of material to write about. So, where are theAmericans? I invite them to “come out ofthe closet” and write about their favoriteCentura tips, tricks, and projects.The new stuffAnd, just in case anyone needs a hint: Ithink SQLBase 7 is an interesting milestonein Centura’s history. It’s obvious thatSQLBase is becoming more important, andthat Centura management believes theyhave a shot at getting it used by all kinds ofcustomers, not just those who use otherCentura tools. In my consulting career, mostof my clients have used backends other than SQLBase,even for relatively small databases. Should they be takinganother look at SQLBase? Let me know what you think.We’re also thinking about giving out a special awardto anyone who can describe, in clear language, whatnet.DB is, and what it does. Are you evaluating theproduct? What do you think? E-mail me and give me youropinion, anonymous or otherwise.Centura Team Developer 1.5, code-named “Eiger”, isin beta. We would love to know what you think of this,too. Is it as good an OLE client as you had hoped?Continues on page 3El Niño Mark Hunter, La Niña Dian Schaffhauser, Forecaster Shelley Doyle,Singin’ in the Rain Paul Gould, Weather Lab MochaCentura <strong>Pro</strong> (ISSN: 1093-2100) is published monthly(12 times per year) by <strong>Pro</strong> Publishing, PO Box 18288,Seattle, WA 98118–0288.POSTMASTER: Send address changes to Centura <strong>Pro</strong>,PO Box 18288, Seattle, WA 98118–0288.Copyright © 1998 by <strong>Pro</strong> Publishing. All rights reserved.No part of this periodical may be used or reproduced in anyfashion whatsoever (except in the case of brief quotationsembodied in critical articles and reviews) without the priorwritten consent of <strong>Pro</strong> Publishing. Printed in the United Statesof America.Centura <strong>Pro</strong> is a trademark of <strong>Pro</strong> Publishing. Other brand andproduct names are trademarks or registered trademarks oftheir respective holders.This publication is intended as a general guide. It coversa highly technical and complex subject and should not beused for making decisions concerning specific products orapplications. This publication is sold as is, without warrantyof any kind, either express or implied, respecting the contentsof this publication, including but not limited to impliedwarranties for the publication, performance, quality,merchantability, or fitness for any particular purpose.<strong>Pro</strong> Publishing, shall not be liable to the purchaser or anyother person or entity with respect to any liability, loss, ordamage caused or alleged to be caused directly or indirectlyby this publication. Articles published in Centura <strong>Pro</strong> reflectthe views of their authors; they may or may not reflect theview of <strong>Pro</strong> Publishing. Opinions expressed by CenturaSoftware employees are their own and do not necessarilyreflect the views of the company.Subscription information: To order, call <strong>Pro</strong> Publishing at206-722-0406. Cost of domestic subscriptions: 12 issues, $119;Canada: 12 issues, $129. Other countries: 12 issues, $139.Ask about source code disk pricing. Individual issues cost $15.All funds must be in U.S. currency.Call Centura Software Corp. at 650-596-3400.If you have questions, ideas for bribing authors, or would justlove to chat about what you’re doing with Centura products,contact us via one of the means at right.Talk to UsCentura <strong>Pro</strong> on the Webhttp://www.<strong>Pro</strong>Publishing.comEditorial DepartmentPhone: 818-249-1364Fax: 818-246-0487E-mail: mhunter@sprintmail.comSubscription ServicesPhone: 206-722-0406Fax: 206-760-9026E-mail: dschaffhauser@MCI2000.comMail<strong>Pro</strong> PublishingPO Box 18288Seattle, WA 98118-02882 Centura <strong>Pro</strong> May1998 http://www.<strong>Pro</strong>Publishing.com


A Java Client . . .Continued from page 1about them. For blobs, you just don’t use functions thatassume zero-termination (basically every SalStr* functionexcept SalStrSet/GetBufferLength). The ROB library forCTD allows you to use any kind of string, normal andblob; you just have to use it accordingly.In the Java client code I emulated CTD stringbehavior, appending a termination (“\0”) to outgoingmessages and truncating it from incoming messages.However, this simple approach would limit ROBfunctionality to regular strings; it wouldn’t work forblobs, because the message that arrives on the other sideof the connectionwould be differentfrom the startingone. The solutionwas to duplicate theset/get functions toadd their blobequivalents, wherethe termination isn’tFigure 1. The class structure.used.Passing references to functionsUsing SAL, like C, you can pass a primitive parameter byits reference (pointer) instead of its value. This iscommonly used in SAL to retrieve a string from a functionusing the return code to signal the success or failure of theoperation, since a null string itself could be a valid result.In Java you can’t pass primitives this way. The only thingsthat Java can pass by reference are objects. Actuallythey’re always passed by reference, like SAL does.This means that if you want to pass a string byreference, like I needed to do to rewrite the client code inJava, you’ll have to create a class that contains a stringand use objects created from that class instead of thestring itself. Since I had to use this class anyway, I alsoused it to fix the zero-terminated string problem. I createda class called __cROB_ReturnValue where I store thestring that comes from the ROB server and where thefunctions reside that extract a valid Java string, removingthe termination or the unmodified blob string.Class structureThe library (maybe I should call it “package,” as Javawould) is composed of three classes (see Figure 1):• _cROB_CommunicatorClient• _cROB_returnValue• ROB_Client(I didn’t use the “c” prefex for ROB_Client because ofJava’s naming conventions.)The __cROB_CommunicatorClient is responsible forthe connection procedures: It connects, disconnects, andsends and receives messages to and from the connectedROB server.The __cROB_ReturnValue class handles the incomingmessages. It stores theincoming value andinterprets the returncode and removes thezero termination, ifnecessary.ROB_Client is theactual class that you’llhave to use. It handlesall the services that aROB client needs inorder to access remote members and to execute remoteprocedures.ROB_Client references __cROB_CommunicatorClient(it’s a class variable), and both have an instanceconnection with __cROB_ReturnValue since they use it asa parameter.Java’s implementation of the WinsockUsing the Winsock in Java is enormously simpler thanusing it in any Windows development environment(unless, of course, you’re using some third-party library,OCX, VBX, DLL, or anything else between you and theWinsock). In Java you simply create a socket object fromthe Socket class, an input stream object and an outputstream object, bind everything together, and voila! itworks. You don’t have any blocking or non-blocking orsynchronous or asynchronous problems. Everythingworks; after all, Java was primarily developed fornetworking operations.Rally ’Round . . .Continued from page 2Happy talk from Wall StreetCentura has faxed out reprints of an article that ran in theMarch 4th issue of the Wall Street Journal, which notedthat new management has fixed many of the fundamentalfinancial problems that had been plaguing the company.They indicate that the stock may be worth looking at for aspeculative purchase. What do you think? I’d be interestedin the opinions of some long-term Centura investors. CPhttp://www.<strong>Pro</strong>Publishing.comCentura <strong>Pro</strong> May 1998 3


Here’s the Java code I used to connect to a ROB server:{String sValue;// Connect to servertry{Peer = new Socket( sServer, nPort.intValue() );c_In = new DataInputStream( Peer.getInputStream() );c_Out = new DataOutputStream( Peer.getOutputStream() );}catch ( Exception e ){System.out.println( "Error: " + e );return false;}After the connection is established, you can simplyread and write from the two streams. Of course, you stillhave to implement your own protocol. In this case I hadto use the same protocol used for the ROB library frommy last article. Nothing fancy; I only send the totalmessage size in the first four bytes so that the receivingapplication knows how many bytes to wait for. Javadoesn’t even need to convert this four bytes (longnumber) from host order to network order, as the Centuralibrary must, since Java uses the network order itself.Applets and securityIf you just use the virtual machine in a Java application,then you don’t have any security limitation; you canconnect wherever you like. However, if you’re using theROB client in an applet, you can connect only to the serverfrom which you downloaded the applet.Here’s an example. Say your HTML looks like this:This applet is able to connect only to a ROB serverrunning on www.propublishing.com.How to use the ROB_ClientIt’s really simple. Include the “rob” directory with thethree classes into your Java CLASSPATH setting. Thenimport the “rob” package in your Java application orapplet. If you want to distribute your applet over theInternet, you must copy the “rob” directory (package) intothe same directory where you have the applet.The following sample code shows how to access, inJava, a simple Centura ROB that connects to the database,loads a result set, and returns the content of each row.This code works using jview.exe. The applet version is alittle bit different because you have to deal with differentevents: loading of the applet, unloading, painting, etc.import rob.*;public class Test{static ROB_Client rob;public static void main( String args[] )}}// create the ROBrob = new ROB_Client( “localhost/10100”,“TableLoader” );// test validityif ( !rob.isAllocated() )return;// initialize ROB’s attributesrob.setm( “sDatabase”, “demo” );rob.setm( “sUser”, “sysadm” );rob.setm( “sPassword”, “sysadm” );rob.setm( “sTable” ,”systables” );rob.setm( “sColumn”, “name” );rob.setm( “sLike”, “sys%” );// prepare the result setrob.exec( “load”, “” );// print the rows countSystem.out.println( “number of rows=” +rob.exec( “getCount”, “” ) );// print each valuesValue = rob.exec( “getFirst”, “” );while ( sValue.length()>0 ){System.out.println(sValue );sValue = rob.exec( “getNext”, “” );}// free the object and disconnect.// the disconnection by itself would be enough// because the server automatically frees// all the objects associated with the connection.rob.free();rob.disconnect();First, you create an object from the class ROB_Client,then initialize the object with the “new” operator. SinceJava has constructors (SAL doesn’t), you can immediatelyspecify the server to connect to and the remote object toallocate. In theSAL version youhad to useclient.connect( )andclient.allocate( ).In the exampleabove I initializethe object’smember toconnect to the“Demo”Database and toread the “name”column, only forvalues that beginwith “sys%”,from the“systables”table. Then Iprint out all theretrieved valuesusing getFirst( )and getNext( ).Obviously, in theserver code, the Figure 2. The Java client result screen.4 Centura <strong>Pro</strong> May 1998 http://www.<strong>Pro</strong>Publishing.com


getFirst( ) method should reset an internal index andreturn the first row of the result set; the getNext( ) methodshould increase the index and return the next row in theresult set. The Remote Object “TableLoader” is includedin the server test code. See Figure 2 for the test client resultwith the jvm, Figure 3 for the same code in an applet, andFigure 4 for the server transactions log.The test applet (included with the sample code)connects and tells the server to load the result set onlyonce, when the browser loads it, and retrieves all the rowseach time it needs to repaint its window. This approachcauses the applet to generate network traffic each time thewindow needs to be repainted. It would be better toconnect, load all the rows, and disconnect when theapplet gets loaded the first time and then use the localstorage to refresh the screen. Changing the code could bean interesting first test of the library.Error handlingThere are two kinds of errors: local errors, generated bythe application (mostly because of connection problems),and remote errors, generated by the remote object. In bothcases the function being executed fails. The get* functionsreturn false when they fail; the set* and exec functionsreturn a null string; and in both cases you can use thegetLastError( ) method to retrieve the actual error number.Another approach would be to derive your own clientclass from ROB_Client and implement the iError( ) virtualmethod, which is called by ROB_Client every time anerror occurs. This is probably not the best way to handleerrors since Java has the capability to use exceptions—butI’m not very familiar with it yet; I’m sure that any Javaprogrammer can do better than that.Fixes to the Centura ROB class libraryTesting the Java client gave me a chance to fix some errorsin the first version of the ROB library. The first problemwas the Winsock version clash. I was requesting version2.0, which caused Centura’s TCP/IP router to fail if calledafter the ROB library had already allocated the Winsock.The reason is that the Winsock can be allocated multipletimes in the same process, but each subsequent allocationmust request the same version requested by the firstallocation. Centura’s routers request 1.1 and the ROBlibrary was requesting 2.0.The second problem was the way I handled blockingoperations. I was waiting in a loop, with yielding enabled,waiting for the entire packet to be received. This approachcause the server to go into a recursion if the messageswere too close to each other. Now I detect a blockingoperation right in the Winsock library, and I don’t get outof there until the whole packet is received or until atimeout error occurs. CPDownload the software discussed in this article from <strong>Pro</strong>Publishing’s Web site or obtain it from this month’sCompanion Disk.Figure 3. The Java applet-clientresult screen.Figure 4. The server transactions log.Gianluca Pivatodesigns LANs, WANs,databases, software,and anything thatinteracts with thesystem. Hegraduated in Italywith a degree indigital electronics,and he’s been in thecomputer businesssince 1982. Currently,he’s working on aproject for an Italianpatent andtrademark firm. Hiscompany is PivatoConsulting, inc. andhe can be reached atgianlucapivato@pipeline.com. He’salso selling Centuratools through hisnew online company,XSal,athttp://xsal.hexagon.net.http://www.<strong>Pro</strong>Publishing.comCentura <strong>Pro</strong> May 1998 5


Token VirtuosityCenturaTip!Mike Robinson—I’m constantly finding neatuses for SalStrTokenize( ) and VisStrExpand( ).These powerful functions are like two sides ofthe same coin. SalStrTokenize( ) can parse a string into anarray of substrings, and VisStrExpand( ) takes an array ofstrings or numbers and combines them into one string.In this tip I show examples for using each one, and thenan example of how they can be used together.With VisStrExpand( ), after SELECTing a series ofvalues into an array, you can combine them into acomma-delimited string and use them, for example, in anIN clause of a SQL string. The same basic mechanismcould be used to generate strings of bind variables orselect columns for a Select statement:Call SqlPrepareAndExecute( hSql,"Select employee_id from employees into:nEmpID[nIdx] Where salaried = 'N'")Set nIdx = 0While SqlFetchNext(hSql,nErr)Set nIdx = nIdx + 1If nIdx > 0Set sInClause = VisStrExpand('{%,n}',nEmp)ElseSet sInClause = 'NULL'Set sSql = "Select amount From paychecks Into:nAmount Where employee_idIN (" || sInClause || ")"Call SqlPrepareAndExecute( hSql, sSql )A sub-tipPlease note that if you’re using array elements as INTO orBIND variables in a SELECT, you shouldn’t put spacesbetween the brackets and the index variable, becauseCentura won’t process the SELECT statement correctly:Wrong: Into :nEmpID[ nIdx ]Right: Into :nEmpID[nIdx]SalStrTokenize( ) could be used to split up thecontents of a listbox or combo box into separate fieldsthat could be saved into a database record. For example,a listbox could hold on each line a message code, amessage priority, and a message, with the codeseparated from the priority by a tab character, and thepriority separated from the message by a tab character.A sample entry might look like this:S Q L W i n d o w sC e n t u r a'MESG_ONE 1 This is sample message one with priority one'If this is the first entry in a listbox, it can be splitup like this:! (The third parameter is a string containingone tab character)Call SalStrTokenize( SalListQueryTextX( hWndMesgList, 0 ), STRING_Null, STRING_Null,sArray )Set sCode = sArray[0]Set nPriority = SalStrToNumber( sArray[1] )Set sMessage = sArray[2]! Store the listbox entryCall SqlPrepareAndExecute( hSql, "Update messagesset msg_priority = :nPriority, msg_text =:sMessage Where msg_code = :sCode" )Using them togetherWith SalStrTokenize, you can load in a line at a time from acomma-delimited file or a standard spreadsheet tabdelimitedfile and instantly break up all the values into anarray:While SalFileGetStr( hFileIn, sTabbedLine, 255 )! (The third parameter is a string containingone tab character)Set nElements = SalStrTokenize( sTabbedLine,STRING_Null, ' ', sArray )Set nIdx = 0! Example <strong>Pro</strong>cessing of array; add 10 toeach elementWhile nIdx < nElementsSet sArray[nIdx] =SalNumberToStrX( SalStrToNumber(sArray[nIdx] ) + 10, 2 )Set nIdx = nIdx + 1After processing this array, the results can becombined again using VisStrExpand( ) and written out toanother file in the same format as the original:! The first parameter has a single tab characterbetween the open brace and the percent sign.Set sLineOut = VisStrExpand( '{%s}',sArray )! Remove the first tab from the output line.Call SalStrLop( sLineOut )Call SalFilePutStr( hFileOut, sLineOut )Whether they’re used together or separately, thesefunctions add another rabbit to your bag of programmingtricks. CPMike Robinson is the Manager of Development for ElectronicHealthcare Systems, Inc., which makes integrated healthcaresoftware. His expertise is in database development and softwaretool design, and when he’s notworking, reads—especially histories,biographies, and science essays.Reach him at miker@ehsmed.com.6 Centura <strong>Pro</strong> May 1998 http://www.<strong>Pro</strong>Publishing.com


Centura<strong>Pro</strong>Animate Your App!R.J. David BurkeWindows 95 introduced theWin32 common controlslibrary to the masses. Thecommon controls library includessuch things as toolbar controls, progress meters,tabs, trackbars, and so forth. One of the newcommon controls is the animation control.The animation control is a lightweightmultimedia control for playing “silent” AVI files. (That’sright, the AVI files passed to an animation control can’thave any audio information.) Typically, the animationcontrol is used as a feedback mechanism that indicates tothe user that a lengthy operation is in progress. It can beuseful to use the animation control in conjunction with aprogress meter for very lengthy operations.With Windows 95, animation controls are used formany of the file management operations initiated withWindows Explorer. The “flying sheets of paper” that yousee when you copy, move, or delete a file, as shown inFigure 1, are an animation control at work—as are themagnifying glass and flashlight when you do find andseek operations.Creating an animation controlLike many of the other common controls, animationcontrols require that a special initialization function becalled first, before any animation controls are created. Forthis reason, you won’t be able to use animation controls atdesign time in Builder; you’ll have to create themdynamically at runtime.However, I know how important it is to be able toposition these things at design time and see how they fitin with the rest of the form or dialog. So I borrowed JimTierney’s technique (see “Tierney’s Cool Tool” in the AprilAdd the Win32 Animation Controlto your bevy of custom controls.C e n t u r a1996 issue of Centura <strong>Pro</strong>) of using aplaceholder control at design time.This at least lets you see how muchreal estate you’re allocating to thecontrol and where it sits in relation to the othercontrols.The placeholder control is just a plain vanillaWindows edit control in the guise of a Centuracustom control class. At runtime the placeholder controlhides itself and creates an animation control in the samescreen space. Listing 1 provides the class definition.Listing 1. The Animation control placeholder class.♦ Custom Control Class: AnimateDLL Name: user32MS Windows Class Name: editTitle: Animation ControlBorder? Yes◊ Description: Animation Control for Centura◊ Derived From♦ Class Variables◊ Boolean: c_bInitialized◊ Number: c_nLeft◊ Number: c_nTop◊ Number: c_nWidth◊ Number: c_nHeight♦ Instance Variables◊ Window Handle: i_hWndAnim♦ Functions♦ Message Actions♦ On SAM_Create♦ If NOT c_bInitialized◊ Call InitCommonControls( )◊ Set c_bInitialized = TRUE◊ Call SalGetWindowLoc( hWndItem,c_nLeft, c_nTop )◊ Call SalGetWindowSize( hWndItem,c_nWidth, c_nHeight )◊ Call SalHideWindow( hWndItem )◊ Set i_hWndAnim = CreateWindowExA( 0,ANIMATE_CLASS, STRING_Null,ACS_TRANSPARENT | WS_CHILD, 0, 0, 0, 0,SalParentWindow( hWndItem ), 0x00002001,GetWindowLongA( hWndItem,GWL_HINSTANCE ), 0 )◊ Call SalSetWindowLoc( i_hWndAnim,c_nLeft, c_nTop )◊ Call SalSetWindowSize( hWndItem,c_nWidth, c_nHeight )◊ Call ShowWindow( i_hWndAnim, SW_SHOW )♦ On SAM_Destroy◊ Call SalDestroyWindow( i_hWndAnim )Figure 1. A frame from the AVI stream playedduring long file copy operations.First, call the common controls library initializationfunction, if it hasn’t already been called. A Boolean classhttp://www.<strong>Pro</strong>Publishing.comCentura <strong>Pro</strong> May 1998 7


variable is used to keep track of whether this function hasbeen called. If the Boolean is FALSE, the call toInitCommonControls is made and the Boolean is set toTRUE.Next, the location and position of the placeholdercontrol are gathered. Then the placeholder control ishidden.The animation control is created with a call toCreateWindowExA. One thing about my implementationhere is that the control ID parameter is hard coded at0x00002001. Since each control on a form needs a uniqueID, this limits the use of my class to one instantiation pertop-level window. If you need more than one animationcontrol per top-level window, you could modify the codeto handle this, incrementing a new control ID as needed.The animation control’s size and location are set tomatch the placeholder control, and then the control itselfis shown.On destruction of the placeholder control, it destroysthe animation control, as you can see from theSAM_Destroy message handler.Now that the Animation class is defined (or really, theplaceholder class is defined), it can be used at design timeto show where an animation control will appear atruntime, as in Figure 2.The animation control interfaceThe Animation control class includes a handful offunctions that provide the interface for working with thecontrol. The code in Listing 2 shows the functions declaredin the class. All these functions are simply wrappersaround the animation control’s underlying message-basedinterface, which you can read about in the Win32 SDK.Listing 2. The functions in the Animate class that constitute theinterface for the animation control.♦ Functions♦ Function: Open◊ Description:♦ Returns◊ Number:♦ Parameters◊ String: p_sName◊ Static Variables◊ Local variables♦ Actions♦ If p_sName != ''◊ Return VisSendMsgString( i_hWndAnim,ACM_OPEN, 0, p_sName )♦ Else◊ Return SalSendMsg( i_hWndAnim,ACM_OPEN, 0, 0 )♦ Function: Play◊ Description:♦ Returns◊ Number:♦ Parameters◊ Number: p_nFrom◊ Number: p_nTo◊ Number: p_nRep◊ Static Variables◊ Local variables♦ Actions◊ Return SalSendMsg( i_hWndAnim,ACM_PLAY, p_nRep, p_nFrom + 0x10000 * p_nTo )♦ Function: Stop◊ Description:♦ Returns◊ Number:◊ Parameters◊ Static Variables◊ Local variables♦ Actions◊ Return SalSendMsg( i_hWndAnim,ACM_STOP, 0, 0 )♦ Function: Close◊ Description:♦ Returns◊ Number:◊ Parameters◊ Static Variables◊ Local variables♦ Actions◊ Call SalInvalidateWindow( i_hWndAnim )◊ Return Open( STRING_Null )♦ Function: Seek◊ Description:♦ Returns◊ Number:♦ Parameters◊ Number: p_nFrame◊ Static Variables◊ Local variables♦ Actions◊ Return Play( p_nFrame, p_nFrame, 1 )The first action to take is to open an AVI file. Thenyou tell the control to play the AVI file. When you’redone, you can stop the playback and close the AVI file. So,if you’ve created an instance of the Animate class in adialog box, you might have something like the exampleshown in Listing 3.Listing 3. An example using the Animate class in a dialog box.Figure 2. At design time you’ll see a placeholder that showsyou where the animation control will appear at runtime.♦ Dialog Box: dlg<strong>Pro</strong>cess◊ Description:♦ Tool Bar♦ Contents♦ Animate: anim1◊ Message Actions◊ Functions◊ Window Parameters◊ Window Variables♦ Message Actions♦ On SAM_CreateComplete◊ Call anim1.Open( "search.avi" )◊ Call anim1.Play( 0, -1, -1 )♦ On SAM_Close◊ Call anim1.Stop( )◊ Call anim1.Close( )◊ Call SalEndDialog( hWndForm, 0 )8 Centura <strong>Pro</strong> May 1998 http://www.<strong>Pro</strong>Publishing.com


You open an AVI file by calling the Open function andpassing it the name of the AVI file. The name can bequalified with a path or you can just refer to a file in thecurrent directory.You play the opened AVI file by calling the Playfunction. This function takes three arguments. The firstargument is the frame number to start playing. Thesecond argument is the frame to end playing on. Use –1 toindicate the last frame. The third argument specifies howmany repetitions to play the AVI file. Use –1 to indicatecontinuous playing until Stop is called.The Stop function stops the control from playing thefile further. The current frame remains visible in thecontrol. The Close function closes the AVI file. The call toSalInvalidateWindow in Close is necessary since theAnimation control isn’t part of the runtime environmentmanaged by Centura’s runtime libraries.If you trap SAM_Create at the instance level, makesure you call the class SAM_Create message handlerbefore you do anything further, as shown in the followingcode fragment:♦ Animate: animSearch♦ Message Actions♦ On SAM_Create◊ Call SalSendClassMessage( SAM_Create,wParam, lParam )◊ Call Open( “search.avi” )◊ Call Play( 0, -1, -1 )So, for example, you may want to play an animationwhile you populate a table window, as in Listing 4.Listing 4. A child table window takes advantage of an animationcontrol during its population operation.♦ Child Table: tbl1◊ Contents◊ Functions♦ Window Variables◊ Sql Handle: hSql♦ Message Actions♦ On SAM_CreateComplete◊ Call anim1.Open( "findfile.avi" )◊ Call anim1.Play( 0, -1, -1 )◊ Set SqlDatabase = 'ISLAND'◊ Call SqlConnect( hSql )◊ Call SalTblPopulate( hWndForm, hSql,'SELECT * FROM COMPANY C,INVOICE I, INVOICE_ITEM TWHERE C.COMPANY_ID = I.COMPANY_ID ANDI.INVOICE_NO = T.INVOICE_NO ',TBL_FillAllBackground )♦ On SAM_FetchDone◊ Call SqlDisconnect( hSql )◊ Call anim1.Stop( )◊ Call anim1.Close( )Figure 3 is a screenshot of the animation controlplaying while a table window is populating.How does the animation control work?The animation control creates a thread that runs in thebackground, which changes the frames as required. Sincea separate thread is used, the animation control continuesto update, even while the main CTD thread is doingdatabase I/O or other lengthy operations.The animation control has a few styles that are worthmentioning.• ACS_CENTERED—When this style isn’t used, theanimation control is dynamically resized to the framesize. This is usually what you want. When this style isused, the animation control is sized to a width andheight of 0. You must use SetWindowPos (from theWindows API) to resize the control. The frames aredisplayed centered within the control.• ACS_TRANSPARENT—When this style is used, theanimation is drawn with a transparent color insteadof its background color. This is usually what youwant.• ACS_AUTOPLAY—When this style is used, thecontrol will automatically play the AVI file when itopens it.A design time property dialogTo provide an easy, friendly interface for setting whatstyle to use as well as a startup AVI, I created a propertydialog application and turned my Animate custom controlclass into a QuickObject class. The property dialogFigure 3. The animation control playingwhile the table window is populating.Figure 4. The property dialog for the Animate class.http://www.<strong>Pro</strong>Publishing.comCentura <strong>Pro</strong> May 1998 9


application just provides a simple UI for setting the initialstate of an Animate instance. The dialog it displays isshown in Figure 4.The dialog appears whenever an instance of Animateis dropped on a top-level window, or when the“Animation <strong>Pro</strong>perties…” item is selected from thecustomizer. The dialog is connected to the class with theQuickObject Editor as shown in Figure 5.Any changes made through the property dialog aresaved as named properties in the host application. TheSAM_Create handler code of the Animate class changed alittle to check the named properties, just in case they’reprovided, as shown in Listing 5.Listing 5. The revised SAM_Create handler for Animate thatchecks named properties.♦ On SAM_Create♦ If NOT c_bInitialized◊ Call InitCommonControls( )◊ Set c_bInitialized = TRUE◊ Call SalWindowGet<strong>Pro</strong>perty( hWndItem, ACP_STYLE,i_sStyle )◊ Set i_nStyle = SalStrToNumber( i_sStyle )♦ If i_nStyle = 0◊ Set i_nStyle = ACS_TRANSPARENT◊ Call SalGetWindowLoc( hWndItem, c_nLeft,c_nTop )◊ Call SalGetWindowSize( hWndItem, c_nWidth,c_nHeight )◊ Call SalHideWindow( hWndItem )◊ Set i_hWndAnim = CreateWindowExA( 0,ANIMATE_CLASS, STRING_Null,i_nStyle | WS_CHILD, 0, 0, 0, 0,SalParentWindow( hWndItem ),0x00002001, GetWindowLongA( hWndItem,GWL_HINSTANCE ), 0 )◊ Call SalWindowGet<strong>Pro</strong>perty( hWndItem,ACP_FILE, i_sFile )♦ If i_sFile != '' AND SalFileOpen( c_hFile,i_sFile, OF_Exist )◊ Call Open( i_sFile )◊ Call SalSetWindowLoc( i_hWndAnim,c_nLeft, c_nTop )◊ Call SalSetWindowSize( hWndItem,c_nWidth, c_nHeight )◊ Call ShowWindow( i_hWndAnim, SW_SHOW )AVI filesNow that all the code is in place, it would be useful tohave some stock AVI files to use with Animate objects.Remember, the AVI files used with animation controlsFigure 5. The QuickObject Editor settings for the Animate class.can’t have any audio information; they must be “silent.”Along with the source code (see the downloadinstructions at the end of the article), I’ve provided ahandful of AVI files, including the eight AVI files used bythe Windows Explorer (file move, file copy, file delete, filerecycle, recycle empty, find computer, find file, search).These AVI files were created by extracting the AVIresources from Shell32.dll using Developer Studio. Whileyou may find AVI files suitable for your purposes on theWeb or online forums, you’ll probably have to create yourown. Check the offerings at this URL for some AVIeditors: www.sharewarefinder.com/multimedia/vidplay/index.shtml.Download the software discussed in this article from <strong>Pro</strong>Publishing’s Web site or obtain it from this month’sCompanion Disk.R.J. David Burke is a Senior Consulting Engineer with Centura Software.Blast It!Get That Tip To Us!Submit your best programming tip toCentura <strong>Pro</strong>. If we publish it, we’ll send you an officialCentura <strong>Pro</strong> Spud Gun and $20. Shoot it tomhunter@sprintmail.com. Ask about our bug guns.10 Centura <strong>Pro</strong> May 1998 http://www.<strong>Pro</strong>Publishing.com


Centura<strong>Pro</strong>Get That Picture BackR. J. David BurkeWhen you extract thecontents of a picture controlto a string usingSalPicGetString, a special header isprefixed to the actual image data. Theheader defines the image format (.bmp, .pcx, .gif, .jpg,.wmf, .tif, or .ico) and other details about the image dataso that the SAL runtime can reprocess the image in thefuture. For example, you can retrieve the contents of apicture control, and then write it out to a file orsave it in a LONG column in a database forpersistent storage. Later, you can retrieve thestring from persistent storage and assign it to apicture control (or push button or optionbutton). The SAL runtime uses the header to correctlyrender the image data.The problem is that the picture contents with theheader are saved to a file—and the file can’t be read byother graphic editing programs like MS PaintBrush orPaint Shop <strong>Pro</strong>. However, there are always solutions tosuch problems. Here’s mine:◊ Call SalPicGetDescription( pic, sPicDesc, 128 )♦ If sPicDesc = '' ! handle bitmaps◊ Call SalSetFocus( pic )◊ Call SalEditCopy( )◊ Call SalEditPaste( )◊ Call SalPicGetDescription( pic, sPicDesc, 128 )◊ Set sExt = SalStrLowerX( SalStrRightX(sPicDesc, SalStrLength( sPicDesc ) –SalStrLength( "Centura:" ) ) )♦ If sExt = 'jpeg'◊ Set sExt = 'jpg'♦ If sExt = 'tiff'◊ Set sExt = 'tif'♦ If SalStrLowerX(SalStrRightX( sPathedFile, 3 ) ) != sExt◊ Set sFileName = SalStrLeftX( sFileName,SalStrLength( sFileName ) - 3 ) || sExt◊ Set nSize = SalPicGetString( pic,PIC_FormatObject, sPic )◊ Call SalStrSetBufferLength( sBuffer, nSize )◊ Set nFSize = GoomGetPicBytes( sPic,sBuffer, nSize )♦ If SalFileOpen( hFile, sFileName, OF_Write |OF_Create | OF_Binary )◊ Call SalFileWrite( hFile, sBuffer, nFSize )◊ Call SalFileClose( hFile )The above code is used to save the contents of apicture control to a file. The name of the picture controlHere’s how to work with imagesonce they’ve been sucked into theSAL runtime.S Q L W i n d o w sC e n t u r ais pic and the file to save to is insFileName. The first bit of codevalidates the image file type. First,the image file type is retrieved fromthe picture header withSalPicGetDescription. The first part of the descriptionis always “Centura:” (or “Gupta:” in SQLWindows),and that’s stripped away. The remainder of thedescription is the common file extension for the imageformat, except for a couple of exceptions thatare handled in the code. Then the extension isadded to the filename if necessary. It seemsthat if the picture contents are a bitmap imagefile, then the contents come back as an emptystring. The copy and paste code is a workaround thatforces a header for bitmap images.Now the fun begins. The picture control contentsare copied to a string variable, sPic, withSalPicGetString. For the second parameter, usePIC_FormatBitmap if you know for certain that thepicture control has a bitmap image. Otherwise, youcan use PIC_FormatObject as in the example. Thereturn value from SalPicGetString is the length of thedata stuffed into the string variable, including theheader.To extract the data without the header, we need tocall GoomGetPicBytes, which is an undocumentedfunction from one of the runtime support DLLs,gobji1x.dll. Since the function takes a receive stringparameter (parameter 2), it’s necessary to preallocatethe size of the string with SalStrSetBufferLength.Next, the call is made to GoomGetPicBytes. Thefirst parameter is the data from the picture control,including the header. The second parameter willreceive the same data, minus the header. The thirdparameter is the size of the second parameter. Since Isaved the size from calling SalPicGetString, I can usethat value to initialize the headerless picture buffer, asit will never be bigger without the header. The returnvalue is the size of the image data without the header.Finally, the data bytes are written to the file. Thehttp://www.<strong>Pro</strong>Publishing.comCentura <strong>Pro</strong> May 1998 11


headerless data size is used to specify how much data towrite with the call to SalFileWrite.GoomGetPicBytes is defined as:♦ Library name: gobji11.dll ! gtiobj.dllin SQLWindows♦ Function: GoomGetPicBytes◊ Description:◊ Export Ordinal: 0♦ Returns◊ Number: DWORD♦ Parameters◊ String: LPVOID◊ Receive String: LPVOID◊ Number: DWORDLPVOIDs are used so that the SAL runtime doesn’ttreat the data as character data and possibly truncate it.CPDownload im.app, the sample application that includesall the code discussed in this, article from the <strong>Pro</strong>Publishing Web site or obtain it on this month’sCompanion Disk.'\'Quitchor\'\' Quotin\'\'Quibblin\'\'\''CenturaTip!R.J. David Burke—In SQL, string literals must bedelimited with the single quote character:INSERT INTO T (C1) VALUES ('foo')An embedded single quote is handled by twoconsecutive single quotes:INSERT INTO T (C1) VALUES ('foo''s bar')In the above INSERT statement,S Q L W i n d o w sthe string “foo's bar” is inserted intoC e n t u r a column C1 of table T.This behavior only applies toS Q L B a s estring literals in SQL statements, notbind variables. Thus, there’s no needto delimit string bind data with single quotes, nor anyneed to double up embedded single quotes in bindvariables either.In SQLWindows or Centura Team Developer SALcode, string literals are delimited with either singlequotes or double quotes. The end delimiter must matchthe start delimiter. So you can have:◊ Call SqlPrepareAndExecute( hSql, 'INSERT INTO T(C1) VALUES (:1)' )or:◊ Call SqlPrepareAndExecute( hSql, "INSERT INTO T(C1) VALUES (:1)" )In SAL code, you can have an embedded delimitercharacter by preceding it with a backslash. So:◊ Call SqlPrepareAndExecute( hSql, 'INSERT INTO T(C1) VALUES (\'foo\')' )will insert “foo” into the table column, while:◊ Call SqlPrepareAndExecute( hSql, "INSERT INTO T(C1) VALUES ('\"foo\"')" )will insert “"foo"” into the table column. Evaluating thestring literal in the next line of code:◊ Call SqlPrepareAndExecute( hSql, 'INSERT INTO T(C1) VALUES (\'foo\'\'s bar\')' )The SAL string that gets passed to the database server is:INSERT INTO T (C1) VALUES ('foo''s bar')When the database server evaluates the string literal,it stores:foo's barinto the table column. The original SAL code is morereadable if double quotes are used to delimit the stringliteral:◊ Call SqlPrepareAndExecute( hSql, "INSERT INTO T(C1) VALUES ('foo''s bar')" )Finally, the SAL dialect used in SQLBase stored proceduresonly supports using single quotes to delimit string literals.R.J. David Burke is a Senior Consulting Engineer with CenturaSoftware.12 Centura <strong>Pro</strong> May 1998 http://www.<strong>Pro</strong>Publishing.com

Hooray! Your file is uploaded and ready to be published.

Saved successfully!

Ooh no, something went wrong!