@Mike - That might work for you, but it doesn't work for me. I never run an EXE in development, so your project inclusion loading would not work for me. Plus the fact that you have to add your non-direct referenced dependencies explicitly into the project, which is a pain in the ass.
My approach is every class declares its dependencies at the top:
*** Dependencies
SET PROCEDURE TO wwUtils ADDITIVE
SET PROCEDURE TO wwApi ADDITIVE
SET PROCEDURE TO wwSql ADDITIVE
*** this library
SET PROCEDURE TO wwBusinessObject ADDITIVE
Then to load the library I can simply do:
*** At app startup - or in `MyApp_Load.prg`
DO wwBusinessObject
DO wwDotnetBridge
*** Now use any class declared
The benefit is you have code based configuration - you can easily tell what dependencies your app has by looking at MyApp_Load.prg
and as a bonus all your dependencies automatically get puled into the project. If you start a new project you can copy/customize the list without manually adding files to your project file.
This works both for compiled and PRG files and you can run the PRG from the Command Window both running the app or loading only the libraries.
I suggest avoiding set procedure. Put customer.prg in a folder in your development path and add it to the exe. Just call it like this:
loCustomer=Customer(1,2,3)
Customer.prg lparameters one, two, three RETURN CreateObject('Customer',one,two,three)
define class customer procedure init lparameters one, two, three endproc enddefine
Loved the article. Compression is a great quick win here. I've used GZip myself and it really cuts down on the output size, especially for JSON. Definitely worth implementing if you’re worried about hitting that limit or just want to reduce the overall load.
Fortunately I define them with a startup prg to establish configuration and directories to be used by the current system...
The advantage of Drew's technique is that the DECLARE is only in one place, the FoxPro UDF of the same name as the Windows API. That also means if the API was released, it will be automatically re-installed at the next call to the API.
SLEEP.PRG lparameters m.tnMilliseconds declare Sleep in Win32API integer dwMilliseconds return sleep(m.tnMilliseconds)
The above runs once. Every subsequent call to sleep is very fast because the API reacts instead. If you use the alias, then you must name the UDF the same as the alias to gain these advantages.
Some of my applications were regenerated using Chen's VFPA10 (64-bit), but one thing makes this experience unhappy: there is no VFPOleDB @64bit, making certain options like automation to Excel much more complicated. Will we one day have 64-bit VFPoleDB?
Just what I was looking for. Tx for writing this out. Very useful.
I read your blog and the light came on in my head. Thank you very much sir.
Thanks Rick, this is very helpful.
I normally write utility programs for my own use in ad hoc data manipulation/cleanup. I am now tasked with creating an app for someone in another dept so was looking for help exactly like this.
Aloha! Steve
@Marco - that's cool. Glad I'm not the only one thinking along these lines - this seems almost address the identical points I'm addressing in this post.
Nice!
Hello Rick!
Some weeks ago I shared a little helper ( nfCustomEnvHelper.prg ) that automates all the steps you describe here.
You can give it a try at https://github.com/nfoxdev/nfCustomEnvHelper
Just ran into this problem on new Win11 computer. The latest OLE and ODBC installers on https://github.com/VFPX/VFPInstallers resolved the issue for me. Thank you!
The article is a good starting point for anyone new to Web Connect or like me, for someone that is setting up my first cloud server. Thank you Rick for taking the time to teach me through these writings.
Sorry to dredge up this old thread, but I recently made a discovery that affects performance. If you have a single prg with a define class, add code at the top of that prg to return the createobject of that class. That way there is no set procedure required. When the class is a subclass
dsoInventoryItems.prg IF NOT "\MYDATAOBJECT"$set("procedure") set procedure to mydataobject.prg additive endif return createobject("dsoInventoryItems")
define class dsoInventoryItems as MyDataObject
That means the set procedure is increased by only 1, and the individual data object subclasses can be instantiated directly like so:
lodsoInventoryItems=dsoInventoryItems()
I believe that should give as good EXE performance as having everything in one .PRG.
This reminded me of a trick I learned years ago at Eric Selje's Southwest Fox 2010 session on VFP2C32. This technique should survive a CLEAR DLLS. From Eric's whitepaper:
*Here’s a tip I remember Drew Speedie mentioning a few years back. Because API functions with the same name as FoxPro functions take precedence in the calling heirarchy, you can dynamically load API functions on-demand by wrapping them in a VFP function of the same name. The first time you call it, the VFP function will be called, the API function will load and get called, but on subsequent calls the API function will be called directly. *
? getActiveWindow()
FUNCTION getActiveWindow
DECLARE INTEGER GetActiveWindow IN user32
RETURN GetActiveWindow()
Thanks for the write-up! I've been using Bootstrap 3 - had planned to go to 4 but never did. Definitely want to move to 5 (primarily for the floating labels more than anything, but also to do a better job of keeping up with the latest version).
I haven't used the HTMLHelpers much, so I personally don't need an upgrade for those. Though I'd suspect there's a lot of use of them out in the real world.
Really appreciate the link to the git commit for the upgrade. Very easy to see the types of changes needed in my apps.
Where has powershell been all my life? Love this Rick.
Thanks, Rick. This is a great article. Much appreciated!. I'm still working my way through it as I learn ES5 and ES6 at the same time.
By the way, I missed you as the Virtual Fox Fest 2020.
@Mattias - good catch.
Actually a more recent version of this code already has that in it.
IF VARTYPE(lnShowWindow) # "N"
lnShowWindow = 1
ENDIF
There's a bug in your code, in GetStartupInfo() you are checking for:
IF EMPTY(lnShowWindow) lnShowWindow = 1 ENDIF
But lnShowWindow = 0 (and therefore EMPTY) is a valid value! With lnShowWindow = 0 you can start a process "hidden", invisible in task bar and invisible in gui.
So you should replace it with IF VARTYPE(lnShowWindow) != "N" or something like that...
@Christof - that's great! Another great solution - looks like roughly on par with Marco's improvement.
It'll be faster if you copy the string into memory and then use SYS(2600) to read byte by byte.
LOCAL lnX, lcString
lcString = REPLICATE("1234567890",100000)
Declare Integer HeapAlloc in Win32Api Integer, Integer, Integer
Declare Integer HeapFree in Win32APi Integer, Integer, Integer
Declare Long GetProcessHeap in Win32API
lnLength = LEN(lcString)
TRANSFORM(lnLength,"9,999,999")
IF .T.
lnSecs = SECONDS()
lnBase = HeapAlloc( GetProcessHeap(), 0, m.lnLength )
Sys(2600, m.lnBase, m.lnLength, m.lcString)
FOR lnX = 1 TO lnLength
lcVal = Sys(2600,m.lnBase-1+m.lnX,1)
ENDFOR
HeapFree( GetProcessHeap(), 0, m.lnBase )
? "SYS(2600): " + TRANSFORM(SECONDS() - lnSecs )
ENDIF
That's 0.33 seconds on my machine vs 37 seconds with SUBSTR().
Thanks @Christof for the additional info!
There's one more async operation that is native to FoxPro and great source of frustration. Dynamic properties in grids. VFP executes these expressions which can call methods or user-defined functions seemingly randomly. Mouse pointer movement and the window placement of other applications impact when a dynamic expression are triggered.
In one application we have one nasty problem that appears to be related to dynamic expressions, although we are not entirely sure yet. VFP switches the data session of one complex form to the default data session at random lines in a form methods for some customers. We have added debug code to every single SET DATASESSION command and know that none of them are executing. Same for any timer event. So frustrating.
DOEVENTS works well in EXEs. Unfortunately, it's one of those commands that are not supported in an MTDLL, because VFP doesn't own the UI thread there. This leads to all kind of strange behavior with callbacks and kernel wait states when the VFP9T runtime is used. In AFP I've mitigated this issue by implementing my own event loop using GetMessage, TranslateMessage, DispatchMessage.
VFP interrupting after a line can occur in unexpected circumstances:
When VFP executes other code outside a wait state (READ EVENTS, MessageBox, etc.) it's in my experience more likely to do so upon entering or returning from a subprocedure. It appears that the code that manages DO levels also performs some checks.
The code interrupting the SCAN loop can be hard to debug because it also depends on the optimization state of SCAN expressions and filters. VFP will not reevaluate optimized expressions in SCAN loops, but it does so with non-optimizable expressions. If one of the side effects of the code that was called asynchronously impacts the filter condition, you suddenly see errors when adding or dropping an index, even when the code in neither the handler nor the SCAN loop has changed at all.
Hello Rick.
We have a problem to solve and I ask you if has some routine or API that do something like threads.
The atual cenario:
we must use three timers to execute differents routines in different times. Example:
Timer1 -> routine A -> at each 30 seconds
Timer2 -> routine B -> At each 60 seconds
Timer3 -> routine C -> at each 10 seconds
This cenario cause many problems because one timer in execution stops the others, creating a fila. So, the 10 seconds timer stops the others that never executes.
My question: any routine or API to generate independ threads or something to simulate my cenario?
Thanks a Lot
@Matthias - GetUtcTime()
uses GetTimeZone()
to get the time offset?
Sorry, but this functions are NOT working correctly! Especially the GetUtcTime() is incorrect. Your function GetTimeZone() checks for daylight-savings-time, but GetUtcTime() does not! When passing a datetime as parameter to the GetUtcTime() function, you have to check, if the given datetime is in daylight-savings-time or standard-time. If you run that function in winter, it gives a different result compared to if you run it in summer!
Mapping as part of startup in group policy did the trick. Thank you very much.
@Phil - you can use wwDotnetBridge to call that .NET Component to encode your message.
Rick,
Great app and nice (though somewhat cryptic) descriptions of what can be done with it.
PLEASE, PLEASE address the OAUTH 2 signature question posed by Phil on Feb 26. I am trying to accomplish the exact same thing, and I'm an absolute noob when it comes to API/JSON services.
Many thanks, Will
Rick-
We are getting a similar "Clr Instance..." error but with a different hash identifier as follows:
Unable to load wwDotNetBridge: Unable to load Clr instance.0x8007000e:
We are using the WW library v.6.15 DtNetBridge DLL with a custom VFP v.09x enterprise platform on our LAN. VFP and the enterprise platform is running on Windows 2012 R12 Server and have .met v4.7 running as well.
The error is odd as it is occasional/intermittent and does not always occur. It just started occurring today.
Any ideas?
Chris
@Darko - if you're going to use an EXE maybe you should just shell out and use curl
instead. It has a lot of capabilities and it too can work just fine without the Windows TLS stack and it's a well known interface that works for all sorts of things beyond just HTTP.
Rick,
Sorry I forgot to mention that I'm running a VFP compiled exe with createprocess()
Rob
Rick,
I can get start a process using createprocess() but the lhProcess number is different to the PID listed in the TaskManager.
So when I use OpenProcess(PROCESS_QUERY_INFORMATION+PROCESS_VM_READ,0,lhProcess) it fails to open the handle.
Is there a way to get the correct pid back or is enumerating all the processes the only way to go?
Rob
Hi, thanks for sharing this.
I used in my various VFP applications directly Windows AJAX with l_oHttp = CREATEOBJECT("MSXML2.ServerXMLHTTP") ... and was biten with TLS 1.2 issue on various client PCs.
At the end, I created small EXE with Golang which only connects to server and returns result. Then I called this EXE instead of MSXML2.ServerXMLHTTP with l_oShell = CREATEOBJECT("Wscript.Shell") ...
Yes, it looks silly to call another EXE for every HTTP request, but this was only secure way to do it on older Windows.
Thank you Rick you a legend. FoxPro is so good with handling data it will be hard to find an alternative in the future unless someone reinvents it in a multithreaded form. Hopefully we can keep it going for another 20yrs or so. There is still Cobol Code out there that is 58yrs old and still running.
Rick - discovered a few minutes ago that another issue when trying to resolve this is the default settings for IE Enhanced Security Settings Configuration. If these are both active you will not be able to open this url https://api.authorize.net/xml/v1/request.api if you are running Windows 2012 R2. If you deactivate for both administrators and users the page will open.
I know we all hate flashing command windows, but:
STRTOFILE("systeminfo >systeminfo.txt","runsysteminfo.bat") RUN /n runsysteminfo.bat sysitext= FILETOSTR("systeminfo.txt")
I do like simple solutions.
Hi Rick,
I'm trying to figure out how to use wwJsonServiceClient to generate an OAUTH 2.0 signature.
Their API docs have an example: The signature is sent in a header named BDXAPI_NAME.
MessageDigestPasswordEncoder encoder = new MessageDigestPasswordEncoder("SHA-1");
StringBuilder sb = new StringBuilder();
sb.append("bdxapikey").append("=").append(bdxApiKey).append("&");
sb.append("bdxapiClientId").append("=").append(clientId).append("&");
sb.append("bdxapisecret").append("=").append(bdxApiSecret);
model.setSignature(encoder.encodePassword(sb.toString(), null));
return model;
Stejpan,
Did you ever find a resolve for this? We are migrating our website over to a new Windows 2012 Server and are not able to get the foxpro dlls to work even when we have registered them successfully?
Thanks.
Yes you can call CreatewwHttp()
which returns a wwHttp instance once which you can set any wwHttp settings. That instance is then used in the request:
oProxy = CREATEOBJECT("wwJsonServiceClient")
*** Create custom Http Object for Authentication
loHttp = CREATEOBJECT("wwHttp")
loHttp.cUsername = "ricks"
loHttp.cPassword = "seekrit22"
loHttp.AddHeader("user_token","3241231")
*** Pass it to the proxy to use
loProxy.CreatewwHttp(loHttp)
loArtist = loProxy.CallService("http://albumviewerswf.west-wind.com/api/artist",loArtist,"PUT")
IF loProxy.lError
? loProxy.cErrorMsg
RETURN
ENDIF
? loArtist.Albums.Count
I like the idea using the wwJsonServiceClient, however is this also possible with headers? Using HTTP call I would normally so something like that: loHTTP = CREATEOBJECT("wwHTTP") loHTTP.AppendHeader("user_session", lcUserSession) lcHTML = loHTTP.HTTPGet("http://192.168.168.155:7564/api/v1/projekte")
Easy:
Response.Redirect("~/default.wcs",.T.)
The .T. parameter makes a permanent redirect.
Very helpful - but how would I return a 301 redirect? In my 5.x app, I do this: Response.Clear() Response.Status = "404 File not found" Response.Write("Page was not found")
TIA
I am working on a delivery routing module for my application and I decided to use javascript to pass the users browser time to vfp. I then convert the hex value to a fox datetime() easily. Javascript returns the time in hex starting from 1/1/1970 (aka "epoch time).
lnUnixEpochTime = 1500847437204 ? lnUnixEpochTime/(6060606060*60) + DATETIME()
This returns: 07/23/2017 05:16:29 PM
Using addproperty() in .Init() works around the COM signature issue.
Also, using state persistence the way you describe applied more to single server applications (logical servers such as COM objects)
Hello, i am facing the issue while using request filtering option. My website didn't work until I add '.' in allowed list. I am unable to find which particular extension I am missing. Thanks in advance.
Yes, the _screen.top and _screen.left can obtain value -32000 which is hard to find on any monitor...
This can happen when the main FoxPro window is minimized.
Rick Strahl
22 days ago