Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations Chris Miller on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Errors welcome 3

Status
Not open for further replies.

Olaf Doschke

Programmer
Oct 13, 2004
14,847
DE
In a curent project we change record IDs from integer to GUID. It's rather easy to create GUIDs with calls to Windows API. The main call is CoCreateGuid, which creates a binary string that you can transform with StringFromGUID2.

Many functions I found include the declaration within the function, eg:

Code:
Function CreateGuid()
   Declare Integer CoCreateGuid In Ole32.Dll String @cGuid

   Local lcGuid
   lcGuid = Space(16)
   =CoCreateGuid(@lcGuid)

   Return m.lcGuid
EndFunc

This makes sure CoCreateGUID is declared, before it is used. It does not matter how often the Declare is made, it does not error, if you define it twice, it's just important, that it is made at least once, otherwise the call to CoCreateGuid(@lcGuid) will error.

Most people tend to program in this way to prevent errors, as security goes first. That's perfectly okay, but let's see the huge performance difference:

Code:
* Declare Integer CoCreateGuid In Ole32.Dll String @cGuid
Local lnCount, lnTime
lnTime = Seconds()
For lnCount = 1 to 100000
   CreateGUID()
EndFor
? Seconds()-lnTime

Function CreateGuid()
   Declare Integer CoCreateGuid In Ole32.Dll String @cGuid

   Local lcGuid
   lcGuid = Space(16)
   =CoCreateGuid(@lcGuid)

   Return m.lcGuid
EndFunc

Running this on my laptop the loop takes about 25 seconds.

If I make the Declaration once at the first line (which is commented) and comment out the Declaration within the function, the loop takes about 0.55 to 0.6 seconds.

The disadvantage is of course, the function itself does not make sure the needed declaration is done. Think of the bigger picture: you may put the declaration in the main prg of your application, and it will work anyway. But if you use that as a default value of guid fields, the database only works with applications that make that declare within their code before adding data to that database.

There is a way to optimize this and have both the security of declaring the needed API call and make that declaration only when needed:

Code:
Local lnCount, lnTime
lnTime = Seconds()
For lnCount = 1 to 100000
   CreateGUID()
EndFor
? Seconds()-lnTime

Function CreateGuid()
   Try
      Local lcGuid
      lcGuid = "                " && Space(16)
      =CoCreateGuid(@lcGuid)
   Catch
      Declare Integer CoCreateGuid In Ole32.Dll String @cGuid
   Endtry

   Return Iif(Empty(lcGuid),CreateGuid(),lcGuid)
EndFunc

It still only takes about 0.6 to 0.7 seconds, slightly slower than the fastest version, but still much faster than the usual "safer" version, although this is exactly that safe. Therefore the thread caption: Errors welcome. Don't be afraid to code in such ways, as it allows you to set the requirements or prerequisites only, if that is really needed.

You may say it's not relevant, because GUID creation is fast enough if you think of users manually adding data, but think of mass data, eg data migration, that can make a huge difference.

Bye, Olaf.
 
you can of course make it a little faster with
Code:
Local lnCount, lnTime
lnTime = Seconds()
For lnCount = 1 to 100000
   CreateGUID()
EndFor
? Seconds()-lnTime

Function CreateGuid()
   Try
      Local lcGuid
      lcGuid = "                " && Space(16)
      =CoCreateGuid(@lcGuid)
   Catch
      Declare Integer CoCreateGuid In Ole32.Dll String @cGuid
      =CoCreateGuid(@lcGuid)
   Endtry

   Return lcGuid
EndFunc

Bye, Olaf.
 
Hi Mike,

thanks for your rating. Security and stability still goes before performance, but if you can combine both that way and if that makes a factor of about 37 (25/.7) it's worth a post, isn't it?

Bye, Olaf.
 
I've used the same mechanism of Try..Catch to spare time and code otherwise needed for some parameter checks here:

thread184-1141890

Bye, Olaf.
 
Olaf,

I've written about this issue myself recently.

I highlighted a case where a function took nearly ten seconds to run in a loop. The function included a DECLARE of GetPrivateProfileString(). By taking the DECLARE outside the loop, the time went down to less than one second.

By using a file-monitoring utility, I could see that the DECLARE statement was searching for the required DLL along the entire VFP search path before eventually finding it in the System32 directory -- even though it knew the DLL was in the Windows kernel. This probably explains the high overhead.

If you're interested, you can read about this at:
(FoxPro Advisor login required for this).

Mike



__________________________________
Mike Lewis (Edinburgh, Scotland)

My Visual FoxPro site: www.ml-consult.co.uk
 
I have the same experience.
From that time I write into DECLARE exact path to the DLL library. It is much faster, especially on the machine with many of files in a system dir.

*- somewhere on start
oApp.cSystemDir = .....
*- then
Declare Integer CoCreateGuid In (oApp.cSystemDir+'Ole32.Dll') String @cGuid

Also, you can use ADLLS() with ASCAN() to test if there is func.already loaded.


Tomas
 
Hi Tomas, hi Mike Lewis,

My point is rather in using Try Catch without prechecks or precaution and taking care of errors only if they happen. And the other point is, to still have a tight coupling of the requirements to the code needing it in a kind of lazy loading. It's no news to make api calls faster by declaring them once and declaring them early.

Bye, Olaf.
 
Olaf - looks like a good short article for FoxRockX to me.

Tamar
 
Hi Tamar,

thanks for motivating me to start writing articles. I'm already thinking in that direction. I already plan a series about DBC, eg while the dbc is open shared how to:

-add new tables
-change stored procs
-make a backup
-replicate the dbc

and blog about it.

Bye, Olaf.
 
Hi Mike Lewis,

RENAME TABLE does work for me even in shared mode. Do you have problems in changing the file name or changing the internal dbc name of a table?

Bye, Olaf.
 
Olaf,

The problem is that RENAME TABLE only changes the internal DBC name, not the physical DBF name. And if you RENAME TABLE and then subsequently RENAME the file, you get a broken backlink.

We had a long discussion on this in thread1252-1410361.

Mike


__________________________________
Mike Lewis (Edinburgh, Scotland)

My Visual FoxPro site: www.ml-consult.co.uk
 
Okay, Well, this would involve really low level changes in file headers and if anybody has opened a table shared it will surely fail to even just rename the file.

But considering this done in exclusive mode, if you don't want to copy all the data to a new file, you'd need DBSETPROP(tablename,"Path",Renamedpath). The backlinks to the dbc in the dbf files would still work if you only rename the dbf/cdx/fpt files and not move them.

So it's mainly about the Property Memo field, which has binary information in it along with the path of the table relative to the dbc.

The structure of the DBC property field is documented with 60dbcpro.frx within Tools/FileSpecs, but I think Doug Hennig has written a much better white paper about it.

Without deeper knowledge of that binary data you simply create a dummy dbc and the table with the right name and any field structure, then you could copy that Property Memo value to your dbc and would be fine.

Bye, Olaf.
 
please try it on a copy first... :)
 
Olaf,

This isn't something I need to do. It's no longer a problem for me, but I thought it might be a good subject for your article, given that you were planning to write about these DBC issues.

Mike

__________________________________
Mike Lewis (Edinburgh, Scotland)

My Visual FoxPro site: www.ml-consult.co.uk
 
Okay, so you don't need it anymore. It's quite a major change to rename a table, this could render an application useless.

But it might be something you want to do on a regular basis with log tables for example.

In the thread you pointed to you talked of three steps, of which one was to change ObjectType values. Did you meant the Objectname instead? What did you need to change in the Objecttype column?

Bye, Olaf.
 
Olaf,

This wasn't something we needed to do on a regular basis. It was a one-off requirement that came about because the client wanted to combine two smaller apps into one larger one, and there was a clash of table names.

I pointed out that it wasn't really necessary to rename any tables; they could have simply qualified them with the database name. But the client also wanted to enforce a naming convention for the tables. So I took the opportunity to do both.

It did involve a lot of code changes, but that was just a big find-and-replace (with the help of Code References). Renaming the physical tables proved much harder.

You're right about the ObjectType field. I should have said ObjectName. I'll fix that in the original thread.

Mike

__________________________________
Mike Lewis (Edinburgh, Scotland)

My Visual FoxPro site: www.ml-consult.co.uk
 
There is a much simpler way to do this:

Code:
Function apiCoCreateGuid(pguid)
	Declare Integer CoCreateGuid In Ole32 As apiCoCreateGuid;
		String  @pguid
	Return apiCoCreateGuid(@m.pguid)
Endfunc

This method relies in the fact that declared DLL functions take precedence over VFP functions with the same name, so the first time you call apiCoCreateGuid, the VFP function is called, which declares the DLL function with the same name, and then calls it.

Another advantange of this method is that the DLL function is self-protected against CLEAR DLLS

This method I took it from GDIPlusX.

Carlos Alloatti
 
Thanks Carlos,

This is cool. My version is safe against CLEAR DLLs too, but doing the CoCreate twice in both Try and Catch block is a bit ugly. I also could have called the function again after the Declare: Return CreateGUID().

Bye, Olaf.


 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top