Mark Williams
Members-
Content Count
282 -
Joined
-
Last visited
Everything posted by Mark Williams
-
It's pretty much the same as mentioned in the link above. However: //Following is to enable opening of a folder and selecting of file const OFASI_EDIT = $0001; OFASI_OPENDESKTOP = $0002; {$IFDEF UNICODE} function ILCreateFromPath(pszPath: PChar): PItemIDList stdcall; external shell32 name 'ILCreateFromPathW'; {$ELSE} function ILCreateFromPath(pszPath: PChar): PItemIDList stdcall; external shell32 name 'ILCreateFromPathA'; {$ENDIF} procedure ILFree(pidl: PItemIDList) stdcall; external shell32; function SHOpenFolderAndSelectItems(pidlFolder: PItemIDList; cidl: Cardinal; apidl: pointer; dwFlags: DWORD): HRESULT; stdcall; external shell32; function OpenFolderAndSelectFile(const FileName: string): boolean; var IIDL: PItemIDList; begin result := false; CoInitialize(Nil); IIDL := ILCreateFromPath(PChar(FileName)); if IIDL <> nil then try result := SHOpenFolderAndSelectItems(IIDL, 0, nil, 0) = S_OK; finally ILFree(IIDL); CoUninitialize; end; end; This works fine in older version of Delphi and 32bit apps. It doesn't compile on 64 bit platform. I noted that Remy enclosed his references to Shell32 in single quotes. I have done the same and it now compiles. However, it doesn't work. Doesn't throw up any errors it just doesn't do anything. It fails to create a PItemIDList on the call to ILCreateFromPath. I have read on the Microsoft forums (MS Forum) that this may be a bug with Windows 10 64 bit only. @FredS mentions above that he has it working in Berlin and 64 bit. He doesn't mention if that is on Windows 10 or not.
-
I've tried that. I can't find which unit declares PCUITEMID_CHILD_ARRAY and my debugger is also unhappy with the reference to 'shell32'. Can Shell32 be referenced in 64 bit apps?
-
I had it working in 32 bit using very similar code to that in the forum message which Dave Nottage has linked above. I can't get it to work in 64 bit. The references to "Shell32" throw up unrecognised errors and I am not sure what to replace them with. Have had a look at the Windows documentation and it says its a Shell32 function. Can you explain what you did to get it to work in 64bit please?
-
His opening words are I can't see any reference in the rest of the post to 64 bit.
-
I am looking for a way to kill a hung thread, no questions asked! I've tried to find an answer to this on the web, but the replies are usually "that's really bad dude, debug your code" which I would say is sound advice. However, I am using OLE to open MS Powerpoint to convert to Powerpoint docs to pdf. This needs to be done in (sometimes large) batches along with many other conversion routines for other types of files. The problem is that if you try and open a password protected Powerpoint the process simply hangs, nothing you can do about it. Word is fine. You just feed it a fake password and it excepts if the password is wrong. Unfortunately, Powerpoint doesn't allow you to do that unless you use it's ProtectedWindowView. This will raise an exception if a password is needed. However, there are two problems with it: It is only available for 2010 onwards (minor issue) It's really clunky as it visibly opens Powerpoint and there is no way of stopping it doing that as far as I can see (deal breaker for me). So my idea was to deal with each Powerpoint document in a separate thread. If after a period of time the thread doesn't terminate I can assume it has hung due to password issues, but then I need some way of killing it.
-
Uhm... Are you sure? You said above it couldn't be killed. However, if it works it would solve the problem. I would only be killing the thread in circumstances where the thread had been cause to hung by MS Office. I may just go with the clunky solution I referred to in original post and only allow Office to try and open powerpoint where Office 2010 is installed.
-
Hadn't thought of a separate process. That's an excellent idea. Thanks
-
I am trying to load a TFDQuery from a memory stream using the default format. I can do this fine from file: Query.LoadFromFile('c:\data.DAT', sfAuto); If I load the same first into a stream and then call LoadFromStream I get error code 710 "Invalid Binary Storage Format". I have the DefaultStoreExt of the ResourceOptions for the Query's connection component set to ".DAT" and its DefaultStoreFormat to sfAuto. Is there anything else I should be doing? NB I have also tried setting the DefaultStoreExt without the lading period.
-
FireDAC TFDQuery LoadFromStream default format
Mark Williams replied to Mark Williams's topic in Databases
have found cause of problem. Had to do with something else. Please ignore. -
I create an instance of Word using TWordApplication: WordApp := TWordApplication.Create(frmWord); WordApp.ConnectKind := ckNewInstance; The Word app is owned by my form. TFrmWord is basically just a form with a toolbar, which is designed to sit above the Word app. FrmWord needs: to be visible when the Word app it has created is visible. minimize when the Word app is minimized. Move when the Word app is moved. Resize when the Word app is resized And vice versa for the Word app (ie any movement, resizing, minmimizing etc needs to be relected back to my form). Basically, I am trying to get this to work as if my form and the Word instance are one form. Before anyone suggests, I have tried OLEContainers. They are, of course, buggy, but there are other problems: It is problematic with the Word ribbon when more than one container's open. It has horrific refresh problems when the OLEContainer is moved on to a second monitor. If the user opens a word side panel (eg search or help) and then closes it the Word object does not refresh with the result that there is blank space where the panel used to be and detecting this and trying to refresh is difficult. There were a number of other problems, which I can't remember now, but so many as I gave up the ghost. So I worked with SetWindowPos and timer's instead. It took weeks of trial and error to do what I want it to do and in the end I had it working reasonably well. However, it seems that in a recent Windows update the way SetWindowPos works has changed. Apparently, MS has decided to place further restrictions on one app trying to pop itself on top of another active app. I can understand why, but it has caused problems for the way I have tried to marry up my form and the Word app. Before I spend weeks more time trying to figure out how now to do this, I thought it was worth a post to see if anyone can think of a simpler way of achieving this.
-
I don't have any experience with Office plugins. I thought about it at the time and it looked difficult and I seem to remember there were certain things I thought I would be unable to do using a plugin. I considered having a look at Add-in Express, but I'm keen to avoid reliance on third party tools as far as possible. I think I'll have a look at it and see if it can do what I want.
-
In order to pop up a login box I have removed my main form from the autocreation section and I create it in the project file as follows: Application.Initialize; Application.MainFormOnTaskbar := False; Application.CreateForm(TfrmMain, frmMain); Application.Run; The reason I do this is to stop the main form showing if the login process is cancelled. The main form has an initialization and finalization section. If the login process is cancelled so that the main form never shows, the initialization section is called, but the finalization section does not get called. In fact, the app hangs and has to be terminated via task manager. if not DoLogIn(self, CurrServer, MainUser, ltStartUp) then begin Application.Terminate; exit; end; If I insert a Show command prior to Application.Terminate all works as expected. Does anyone know why Application.Terminate does not terminate the app without a call to "Show" and is there aw away around this (other than calling Show)?
-
Finalization section not called unless main form shown
Mark Williams replied to Mark Williams's topic in VCL
Ok. Thanks that makes sense. I was testing whether mainform was assigned in the onCreate event of the first form created. -
Finalization section not called unless main form shown
Mark Williams replied to Mark Williams's topic in VCL
I have Application.ShowMainForm:=false, but it still flickers after call to terminate. I cannot figure out why it is now doing this. It wasn't when I originally posted and as far as I can see my code is the same. But as mentioned above, there is apparently no mainform. -
Finalization section not called unless main form shown
Mark Williams replied to Mark Williams's topic in VCL
The "exit" does nothing. I was under the misapprehension that the current function would run to its conclusion even after terminate call. Unfortunately, I have been fiddling around trying to find out how to solve the problem and now I can't recreate it! My main form flashes on the screen at the point of application.terminate and it closes properly. Can't seem to get it back to its original state. Will just have to live with the form momentarily flashing on screen. Thanks for the help. -
Finalization section not called unless main form shown
Mark Williams replied to Mark Williams's topic in VCL
VCL The form creation code is in the proj file. You can't assign the form as the main form other than in project options. It's readonly. Which has made me realise I don't have a mainform. Maybe that's the problem -
TWebModule response content truncated before return
Mark Williams replied to Mark Williams's topic in Network, Cloud and Web
No worries -
TWebModule response content truncated before return
Mark Williams posted a topic in Network, Cloud and Web
I'm using TWebModule in an ISAPI DLL. I am using it to return data from a database in xml format using IXMLDocument. Last night I had a problem with text data which included characters such as "/". I assume the returned text was being formatted somewhere along the line to insert escape characters with the result that the received xml at the other end is always slightly short so that you end up with a badly formed xml document. So, for example, if the returned text had two "/" characters the xml document when received is short the last two characters. My code on the server side is: Response.Content := doc.XML.Text; Response.ContentLength :=length(doc.XML.text); I also tried determining the ContentLength using "length(Response.Content)", but that made no difference. I edited the data in the database to remove the "/" characters and all worked fine. I have now tried to replicate the problem so that I can seek some advice as to possible causes, but the xml is no longer being truncated. I am puzzled and keen to avoid the issue for the future, but unable to provide any more info because I can't now replicate the problem. Does anyone have any ideas as to what may have caused the problem initially? -
TWebModule response content truncated before return
Mark Williams replied to Mark Williams's topic in Network, Cloud and Web
That makes sense. Many thanks. -
TWebModule response content truncated before return
Mark Williams replied to Mark Williams's topic in Network, Cloud and Web
No video link? However, don't think it would help. There is no exception and in any event my code is wrapped in try...except statements with exceptions outputted to a log file. Also, as mentioned in my post I cannot now reproduce the problem and don't understand why it was happening in the first place. I am just keen to avoid a repeat. -
Thanks again. I have decided to go down a different route altoghether and instead of having separate apps, house them all within the same app.
-
Thanks to all. I probably need to rethink my approach.
-
ISAPI DLL concurrent requests
Mark Williams replied to Mark Williams's topic in Network, Cloud and Web
Thanks for this information. I will go through it all asap. Likely, I will have some questions if you don't mind. Bit of a newbie to ISAPI! -
ISAPI DLL concurrent requests
Mark Williams replied to Mark Williams's topic in Network, Cloud and Web
Spot on! That was the problem. As for WebGarden, I have check and you create a WebGarden by increasing the ApplicationPool's Maximum Worker Processes above 1. I am now running as non-web garden. I have done a lot of research based on your helpful points above and eventually the mist began to clear. I now have a working ISAPI DLL which appears to be thread safe. Unfortunately, I couldn't find any good examples of how to do what I was trying to do. My DLL uses global variables stored in an inifile. I don't want to load the inifile each time a new thread is fired to avoid repeated loading of the ini file.. So I need to load the inifile once when the DLL is first loaded. Also, I need to have a threadsafe logging system and finally I use a MimeTable in the DLL and I don't want to load that for each request. Below is my relevant code (with the detail removed) to show I have gone about the above. It is working as far as I can see, but I would appreciate any tips for improvement or re potential pitfalls. Hopefully, this will then help others who are new ISAPI dll. It took me many hours of head scratching to get this far. The dpr library WebServer; uses Winapi.ActiveX, System.Win.ComObj, System.SysUtils, Web.WebBroker, Web.Win.ISAPIApp, Web.Win.ISAPIThreadPool, WinAPI.Isapi2, WinAPI.Windows, System.SyncObjs, WebModule in 'WebModule.pas' {WebModule1: TWebModule}; {$R *.res} //Read online somewhere that it is best to initialize/Finalize your globals in the following function //rather than in Initialization/Finalization sections of the webmodule} function GetExtensionVersion(var Ver: THSE_VERSION_INFO): BOOL; stdcall; begin Result := Web.Win.ISAPIApp.GetExtensionVersion(Ver); CriticalSection := TCriticalSection.Create; StartServer; end; function TerminateVersion(var Ver: THSE_VERSION_INFO): BOOL; stdcall; begin Result := Web.Win.ISAPIApp.GetExtensionVersion(Ver); CloseServer; CriticalSection.Free; end; exports GetExtensionVersion, HttpExtensionProc, TerminateExtension; begin CoInitFlags := COINIT_MULTITHREADED; Application.Initialize; Application.WebModuleClass := WebModuleClass; {Application.MaxConnections:=100;} //if you want to increase beyond the default 32 for example NumberOfThreads:=Application.MaxConnections; //see comments following code samples. This is necessary even if you do not want to change the MaxConnections Application.Run; end. Stripped down WebModule unit WebModule; interface uses WinApi.Windows, System.SysUtils, System.Classes, Web.CGIHTTP, Web.HTTPApp, Web.ReqMulti, System.SyncObjs, ThreadFileLog, IDGlobalProtocols {, etc...}; type TLogType = (leNone, leCriticalError, leMajorError, leMinorError, leMajorEvents, leDevelopment); type TWebModule1 = class(TWebModule) FDQuery: TFDQuery; FDPhysPgDriverLink: TFDPhysPgDriverLink; FDConnection: TFDConnection; procedure WebModule1HeadAction(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); procedure WebModuleBeforeDispatch(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); procedure WebModuleAfterDispatch(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); private { Private declarations } public { Public declarations } end; var WebModuleClass: TComponentClass = TWebModule1; CriticalSection: TCriticalSection; const EncryptionKey='Q9__M_f_qc1EX8ce98HsQrYG_s4aRGQj5G_Ixu4XTQs___esfxdH_kf_lg5_8lnk'; Procedure StartServer; Procedure CloseServer; implementation Uses web.WebBroker; var //these will be accessible to all threads ServerStarted:boolean; LogType:TLogType; LogFile, PGVendorLib:string; FThreadFileLog : TThreadFileLog; MimeTable:TIDMimeTable; {%CLASSGROUP 'System.Classes.TPersistent'} {$R *.dfm} procedure AddToLog(str: string; whichLogType:TLogType); var Log:string; begin try if (LogType=leNone) or (ord(whichLogType)>ord(LogType)) then //we are not logging these types of events or errors exit; case whichLogType of leCriticalError: Log:='CRITICAL ERROR: '; leMajorError: Log:='MAJOR ERROR: '; leMinorError: Log:='MINOR ERROR: '; leMajorEvents: Log:='MAJOR EVENT: '; leDevelopment: Log:='DEVELOPMENT: '; end; Log:=Log+str; FThreadFileLog.Log(LogFile, Log); Except end; end; Procedure CreateMimeTable; begin try if not assigned(MimeTable) then begin MimeTable:=TIDMimeTable.create; AddToLog('Mime Table Create', leDevelopment); end; except on E: Exception do AddToLog('Error CreateMimeTable: '+e.Message, leMajorError) end; end; Procedure StartServer; var appPath:String; iniFile:TIniFile; begin if serverStarted then //Probably overkill as should only be called once exit; CriticalSection.Enter; try try FThreadFileLog := TThreadFileLog.Create(); appPath:=GetModuleName(HInstance); if pos('\\?\', appPath)>0 then //this is something to do with the Windows function GetModuleFileName and uncpaths appPath:=copy(appPath, 5, MaxInt); iniFile:=TIniFile.Create(changeFileExt(appPath, '.ini')); try if not FileExists(iniFile.FileName) then begin LogFile:='c:\ProgramData\Log.txt'; AddToLog('IniFile does not exist at location: "'+iniFile.FileName, leCriticalError); Exit; end; AddToLog('Server Started', leMajorEvents); LogType:=TLogType(iniFile.ReadInteger('GENERAL', 'log_type', 1)); LogFile:=inifile.ReadString('GENERAL', 'log_file', ''); if (LogType<>leNone) and (LogFile<>'') then begin if not DirectoryExists(ExtractFilePath(LogFile)) and not ForceDirectories(ExtractFilePath(LogFile)) then LogFile:='c:\ProgramData\Log.txt'; end; PGVendorLib:=inifile.ReadString('LOCATIONS', 'pg_vendor_lib', ''); finally iniFile.Free; end; AddToLog('LogType:'+inttostr(ord(logType)), leDevelopment); AddToLog('LogFile: '+LogFile, leDevelopment); AddToLog('FDPhysPGDriverLink.VendorLib: '+PGVendorLib, leDevelopment); AddToLog('Server Started', leMajorEvents); ServerStarted:=true; except try AddToLog('Server could not be loaded: '+SysErrorMessage(GetLastError), leCriticalError); except end; end; finally CriticalSection.Leave; end; end; Procedure CloseServer; begin CriticalSection.Enter; try if assigned(MimeTable) then try MimeTable.Free; except end; if assigned(FThreadFileLog) then try FThreadFileLog.Free; except end; try AddToLog('Server Closed', leMajorEvents); except end; finally CriticalSection.Leave; end; end; procedure TWebModule1.WebModule1HeadAction(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); var fileName:string; begin Handled:=true; try //If this action needed the mimetable it would call CreateMimeTable at this point FileName:=trim(Request.GetFieldByName('Location')); if FileName='' then begin SetResponse(Response, 400, 'Bad Request', 'Head'); exit; end; if fileExists(FileName) then SetResponse(Response, 200, 'OK') else SetResponse(Response, 404, 'NotFound'); except end; end; procedure TWebModule1.WebModuleBeforeDispatch(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); begin FDPhysPgDriverLink.VendorLib:=PGVendorLib; AddToLog('Before Dispatch - PathInfo: '+Request.PathInfo, leDevelopment); AddToLog('Before Dispatch - Method: '+Request.Method, leDevelopment); AddToLog('MaxConnections: : '+Application.MaxConnections.ToString, leDevelopment); AddToLog('ActiveConnections: : '+Application.ActiveCount.ToString, leDevelopment); AddToLog('InactiveConnections: : '+Application.InActiveCount.ToString, leDevelopment); AddToLog('CachedConnections: : '+Ord(Application.CacheConnections).ToString, leDevelopment); end; procedure TWebModule1.WebModuleAfterDispatch(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); begin AddToLog('After Dispatch - Method: '+Request.Method, leDevelopment); end; end. ThreadFileLog unit unit ThreadFileLog; interface uses Windows, ThreadUtilities, System.Classes; type PLogRequest = ^TLogRequest; TLogRequest = record LogText : String; FileName : String; end; TThreadFileLog = class(TObject) private FThreadPool: TThreadPool; procedure HandleLogRequest(Data: Pointer; AThread: TThread); public constructor Create(); destructor Destroy; override; procedure Log(const FileName, LogText: string); end; implementation uses System.SysUtils; procedure LogToFile(const FileName, LogString: String); var c1, c2 : dword; i:integer; s:string; begin try c1 := CreateFile(PChar(FileName), GENERIC_READ or GENERIC_WRITE, 0, nil, OPEN_ALWAYS, 0, 0); i:=0; while c1=INVALID_HANDLE_VALUE do begin if i>200 then //2 secs elapsed and haven't been able to access log exit else begin inc(i); Sleep(10); c1 := CreateFile(PChar(FileName), GENERIC_READ or GENERIC_WRITE, 0, nil, OPEN_ALWAYS, 0, 0); end; end; if c1 <> INVALID_HANDLE_VALUE then begin SetFilePointer(c1, 0, nil, FILE_END); S:=DateTimeToStr(Now)+': '+LogString+sLineBreak+SLineBreak; WriteFile(c1, s[1], length(s)*SizeOf(Char), c2, nil); CloseHandle(c1); end except end; end; constructor TThreadFileLog.Create(); begin FThreadPool := TThreadPool.Create(HandleLogRequest, 1); end; destructor TThreadFileLog.Destroy; begin FThreadPool.Free; inherited; end; procedure TThreadFileLog.HandleLogRequest(Data: Pointer; AThread: TThread); var Request: PLogRequest; begin Request := Data; try LogToFile(Request^.FileName, Request^.LogText); finally Dispose(Request); end; end; procedure TThreadFileLog.Log(const FileName, LogText: string); var Request: PLogRequest; begin New(Request); Request^.LogText := LogText; Request^.FileName := FileName; FThreadPool.Add(Request); end; end. Other possible useful information: The Application The application created in the project file is a TWebApplication not a TApplication. It exposes MaxConnections, ActiveConnections and InActiveConnections, CachedConnection. MaxConnections The maximum no of connections to your DLL. By default this is 32. So up to 32 new threads will be spawned (but note comments re NumberOfThreads below). You can increase or decrease this number as requi ActiveConnections This reports the number of connections currently in use. InactiveConnections If CachedConnections is true new threads are not disposed of after use, but cached for future use. whilst cached they are recorded as InactiveConnections. ISAPIThreadPool unit This is added by default and deals with threading. NumberOfThreads Global variable declared in the ISAPIThreadPool unit. It is set by default to 25. So whilst the default MaxConnections is 32 (so you can theoretically have 32 connections) unless you increase the NumberOfThreads you will in effect be allowed only 25 connections! The example project code deals with this.