I have an application that opens the autocad app in the following way:
XlApp := GetActiveOleObject('AutoCAD.Application');
Now I want to do the same but with the ZWCAD app, how could I do it?
GetActiveOleObject does not open an application. It returns an interface reference to a COM/OLE automation object of a running application.
If the application is not runnning, you can create/instantiate one using CreateOleObject.
Later, having a reference, you can manage the application externally, from your application. Like opening/managing some documents, do some processing and/or show it to the user.
Note, not all applications supports COM/OLE automation. You have to check official documentation or developer's guides. Usually it contains interface description, like properties and methods you can invoke. Also, Delphi offers tlibimp tool to import the available interfaces from a dll file.
After a quick check, it seems ZWCAD supports COM automation, so may try the following code:
var O: Variant;
begin
O := CreateOleObject('ZWCAD.Application');
try
// Work with object
O.Visible := True;
finally
O := Unassigned;
end;
end;
Or, if you simply want to open the ZWCAD, you can use
ShellExecute(0, 'open', 'c:\path\to\zwcad.exe', nil, nil, SW_SHOWNORMAL);
Related
I have a problem with opening pdf file from Delphi.
I need to open pdf with parameters, because I want to create help manual for my program. I try to use shellExecute, but this function needs path for reader pdf.
procedure TForm3.Button2Click(Sender: TObject);
var e,s:String;
begin
s:='/A nameddest=somePlaceInPDF pathToMyFile.pdf';
e:='AcroRd32';
ShellExecute(handle,'open',pchar(e),pchar(s),nil,sw_show);
end;
The program runs, but it is not a solution for me. Some users can use other pdf reader. Do you know a way to skip adding a reader path?
The other way is
if ShellExecute(handle,'open',pchar(e),pchar(s),nil,sw_show) < 32 then
begin
ShellExecute(0,0,'rundll32.exe','shell32.dll,OpenAs_RunDLL pathToMyFile.pdf',0,SW_SHOW);
end;
I think, that I need to some method, which pull the path from the pdf reader. Is this the best solutions for this problem?
If you wish to pass parameters to an executable, then you are going to ignore any associations and require the presence of a specific executable. Because specific parameters will only be valid for one specific executable. That is, parameters for Acrobat won't be understood by Foxit, and vice versa.
In which case you should invoke it with CreateProcess. To locate the executable for Acrobat Reader, refer to this question: How to get Adobe Reader full path(including executable file name)? There will be similar approaches for other PDF programs.
The real point of ShellExecute is that it understands the system and user preferences for file associations. The shell knows which application should be used to open different file types, and where to find that application.
As a broad rule, if you know the location of the executable, use CreateProcess. If you know the location of a document and want the system to find the executable, use ShellExecute(Ex).
So, pass the full path to the PDF file to ShellExecute and let the system find and open the associated application.
ShellExecute(0, 'open', PChar(PdfFileName), nil, nil, SW_SHOW);
If you want to have proper error handling use ShellExecuteEx. You might also replace 'open' with nil and let the system choose the default action.
Open a PDF with the default system application
ShellExecute(Handle, nil, 'pathToMyFile.pdf', nil, nil, SW_SHOW);
If lpOperation is nil the default verb is used.
Open a PDF file with parameters
The way to open a PDF file with parameters from command line on Windows is:
"C:\Program Files\Adobe\Reader 11.0\Reader\AcroRd32.exe" /A "zoom=1000=OpenActions" "C:\Documents and Settings\winUser\Desktop\wss-v1.1-spec-errata-os-SOAPMessageSecurity.pdf"
The above opens the PDF file with a x1000 zoom (not very useful indeed).
To achieve the same result with ShellExecute do the following:
var
application, appParams, fileName,
shellParams: string;
begin
application := 'AcroRd32.exe';
appParams:= '/A "zoom=1000=OpenActions"';
fileName := 'C:\Documents and Settings\winUser\Desktop\wss-v1.1-spec-errata-os-SOAPMessageSecurity.pdf';
shellParams := Format('%s "%s"', [appParams, fileName]);
ShellExecute(Handle, nil, PChar(application), PChar(shellParams), nil, SW_SHOW);
end;
Here is PDF Open Parameters for reference.
Finally (with no try) notice that PDF Open Parameters are specific of an application so a different reader may ignore them in the best case; in the worst, the application will simply refuse to start.
I'd recommend to use open parameters only after being sure that the correct application is available on the client; if not, use the first method.
This is a best answer to my question
procedure TForm3.Button2Click(Sender: TObject);
var s, result:String;
path: array[0..250] of char
begin
s:='/A nameddest=somePlaceInPDF "pathToMyFile.pdf"';
FindExecutable('pathToMyFile.pdf',nil,path);
result := trim(StrPas(path));
ShellExecute(handle,nil,pchar(result),pchar(s),nil,sw_show);
end
pdf files could be associated with another program, so "FindExecutable" is not a reliable way to find the installed Acrobat Reader program.
I use the registry key : HKEY_CLASSES_ROOT\Software\Adobe\Acrobat\Exe
procedure TfrmFsYtd.btnPdfHelpTestClick(Sender: TObject);
var strAcro, strParam:string;
Registry: TRegistry;
begin
// Get the users' installed Adobe Reader from the registry >>
Registry:=TRegistry.Create;
Registry.RootKey:=HKEY_CLASSES_ROOT;
Registry.OpenKey('Software\Adobe\Acrobat\Exe',False);
strAcro :=Registry.ReadString('');
Registry.Free;
// Use the installed Adobe Reader to open your pdf- help- file at a specific page >>
strParam := ' /A page=4 "'+ProgPath+'FsYtd_Manual.pdf"';
ShellExecute(Handle, 'open', PChar(strAcro), PChar(strParam),nil, SW_SHOWNORMAL);
end;
It's always been strange that there's never been a Description property on the TService in Delphi's VCL. Even to this day, Delphi XE2 doesn't have it yet. It's such a simple and common thing, that I'm wondering why it's not there.
I know how to create it myself, but my point is I shouldn't have to. I was wondering if there's any technical reason why Description of a service doesn't come built-in to Delphi's VCL? Because it seems so simple for them to implement.
Setting it requires ChangeServiceConfig2 API function which was introduced with XP & Win2003, the service class in Delphi was written before that, and for a long time, Windows NT4 and 2000 were the baseline for the Delphi RTL.
Also for some unknown reason, Borland (and successors) have been adverse to using dynamic binding on Windows API functions, preferring either static bindings to DLLs or late but non-optional bindings (don't ask me why, it makes no sense to me), and using the previous function would have required either having Win2003 as minimum version or using dynamic binding.
So I don't think it was a deliberate decision, but is more a consequence of company policy on dynamic bindings and plain old code maintainance neglect/oversight.
You can use like that.
procedure TMyService.ServiceAfterInstall(Sender: TService);
var
Reg: TRegistry;
begin
Reg := TRegistry.Create(KEY_READ or KEY_WRITE);
try
Reg.RootKey := HKEY_LOCAL_MACHINE;
if Reg.OpenKey('\SYSTEM\CurrentControlSet\Services\' + Name, false) then
begin
Reg.WriteString('Description', 'All details you can write to here.');
Reg.CloseKey;
end;
finally
Reg.Free;
end;
end;
I have a windows service created with Delphi 7, with StartType = stSystem.
Now I need to launch an application to make some things for me.
This application has a MainForm and other GDI resources.
The parameter passed to the application assigns values for some controls (like edits and memos) and then click at a button....
I'm trying this:
var
token: cardinal;
si: TStartupInfo;
pi: TProcessInformation;
begin
if not LogonUser('admintest', '', 'secret123', LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, token) then
RaiseLastOSError;
try
if not ImpersonateLoggedOnUser(token) then
RaiseLastOSError;
fillchar(si, sizeof(si), 0);
si.cb := sizeof(si);
si.lpDesktop := PChar('winsta0\default');
if not CreateProcessAsUser(token, nil, '"c:\...\myapp.exe" -doCrazyThings', nil, nil, false, NORMAL_PRIORITY_CLASS or CREATE_NEW_CONSOLE, nil, nil, si, pi) then
RaiseLastOSError;
CloseHandle(pi.hThread);
waitForSingleObject(pi.hProcess, INFINITE);
CloseHandle(pi.hProcess);
finally
CLoseHandle(token);
end;
end;
When I run my service executable as a normal application (-noservice), it starts as a Forms.Application and creates a MainForm with a button "Start".
*The button runs the same code that service run, but it doesn't works and it's rasing the eror code 1314 at createprocessasuser.*
Why? What is the diference between SYSTEM service and a normal application launched by a administrator?
My environment is a Windows 7 Pro x64
What am I doing wrong?
How can I solve this?
Can someone post an example?
Error 1314 is ERROR_PRIVILEGE_NOT_HELD, which means your calling thread is missing a required privilege to run CreateProcessAsUser(). You don't need to, nor should you be, impersonating the user token in order to launch a new process in the user's desktop. You should be letting the thread use the service's credentials, not the user's credentials, when calling CreateProcessAsUser(). It will make sure the new process is run inside the user's account and desktop for you, so get rid of the call to ImpersonateLoggedOnUser() and see if CreateProcessAsUser() starts working.
Update: read the documentation:
Typically, the process that calls the CreateProcessAsUser function must have the SE_INCREASE_QUOTA_NAME privilege and may require the SE_ASSIGNPRIMARYTOKEN_NAME privilege if the token is not assignable. If this function fails with ERROR_PRIVILEGE_NOT_HELD (1314), use the CreateProcessWithLogonW function instead. CreateProcessWithLogonW requires no special privileges, but the specified user account must be allowed to log on interactively. Generally, it is best to use CreateProcessWithLogonW to create a process with alternate credentials.
On Vista and later, windows services run in session 0. Interactive users exist in session 1 and up. This means that windows services cannot show user interface and indeed cannot easily start processes in the interactive session.
Now, there are ways to launch interactive processes from a service. If you are dead set on launching an interactive process from your service, then that article tells you all your need to know. But such techniques are very tricky and absolutely not to be recommended. The recommendation is that you find a different way to communicate between your service and the interactive desktop.
The normal approach is to run a standard desktop app, perhaps started using HKLM\Software\Microsoft\Windows\CurrentVersion\Run. And use some form of IPC to communicate between the desktop app and the service.
You should be creating the service to do the background work, and the GUI application should only call the service. IE you always have a service running.
Consider using the DataSnap stuff for the back end. An MVC approach is not pure in Delphi like it is in some other languages. The controller goes wherever it is convenient. Datasets are mostly a compromise, and the only really fast way to do data is with DBexpress and a whack of components on the client to keep it all happy. But it works and it is worth learning.
Services cant have gui controls. No TForm descendents allowed. TService only. New project under Delphi Projects / Service Application. You get a project with a unit / form that is pretty much the same as a data module. ie, no visual controls allowed. Reasons are pretty obvious. You need to learn records, object design etc. to use services. Datasnap is your best bet for that. It does most of the hard work for you.
Dr. Bob has a pretty good book on it for XE2/3. If you are new to object oriented design you need to learn that thoroughly first.
Here is the code i use to do this kind of thing
procedure CreateNewProcess;
var
CmdLine: AnsiString;
ErrorCode: Cardinal;
ConnSessID: Cardinal;
Token: Cardinal;
App: AnsiString;
FProcessInfo: _PROCESS_INFORMATION;
FStartupInfo: _STARTUPINFOA;
begin
ZeroMemory(#FStartupInfo, SizeOf(FStartupInfo));
FStartupInfo.cb := SizeOf(FStartupInfo);
FStartupInfo.lpDesktop := nil;
ConnSessID := WTSGetActiveConsoleSessionId;
if WTSQueryUserToken(ConnSessID, Token) then
begin
if CreateProcessAsUser(Token, PAnsiChar(App), PAnsiChar(CmdLine),
nil, nil, false, 0, nil, nil, FStartupInfo, FProcessInfo) = False
then
begin
ErrorCode := GetLastError;
try
RaiseLastOSError(ErrorCode);
except on E: Exception do
EventLog.LogError(e.ClassName +': '+ e.Message);
end;
end;
end;
end;
Hope this helps
if you want the service to wait for the new process to terminate before continuing add this
if CreateProcessAsUser(Token, PAnsiChar(App), PAnsiChar(CmdLine),
nil, nil, false, 0, nil, nil, FStartupInfo, FProcessInfo) = False
then
begin
ErrorCode := GetLastError;
try
RaiseLastOSError(ErrorCode);
except on E: Exception do
EventLog.LogError(e.ClassName +': '+ e.Message);
end;
end
else
WaitForSingleObject(FProcessInfo.hProcess, INFINITE);
although and infinite wait is probably not advisable.
We have a client server software that needs to be updated. I need to check if the file is currently being accessed. Is this possible if so how Delphi code if possible. The only place I can see if the file is open is under the shared folders open files. I have tried this code but just shows that the file is not opened.
function TfrmMain.FileInUse(FileName: string): Boolean;
var H_File : HFILE;
begin
Result := False;
if not FileExists(FileName) then
begin
showmessage ('Doesnt Exist');
exit;
end;
H_File := CreateFile(PChar(FileName), GENERIC_READ or GENERIC_WRITE, 0,
nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
Result := (H_File = INVALID_HANDLE_VALUE);
showmessage('Opened');
if not Result then
CloseHandle(H_File);
end;
There is a great deal of information you can access over the WBEM sub-system provided by Windows. I believe there are good WBEM components out there, but you could also import the "Microsoft WMI Scripting" COM Type Library (though this takes a little work to figure out how it works).
If you query for Win32_ServerConnection objects, you get a list of items currently in use, much like you can view using the 'Computer Management' tool from the Administrative Tools.
Not necessarily an answer, but I am currently doing something similar - because the main executable might be updated during working hours though I have created a intermediary application that checks to see if a locally cached copy of the file is up to date, I then run this locally cached copy.
I found this similar item, someone proposes to use the NetFileEnum function
How can I perform the equivalent of shellexecute() in Lazarus for a Mac?
{ Here is code to do it. Use the TProcess object! }
uses Process;
...
procedure DoProcess;
Var
Proc : TProcess;
Begin
Proc := TProcess.Create(nil);
try
Proc.CommandLine := '/Applications/MyApp.app';
PRoc.Options := Proc.Options + [poWaitOnExit];
Proc.CommandLine := Proc.CommandLine + ' -someparam';
PRoc.Execute;
finally
Proc.free;
end;
End;
I don't know whether Lazarus libraries do already have this functionality wrapped, but if not you could write a conditionally compiled version of ShellExecute() using the info in the Launch Services Programming Guide.
If you want to use ShellExecute to open a document with its preferred application, then you can use the OpenDocument procedure from the LCLIntf unit.
The Lazarus conversion tool also uses this replacement for ShellExecute, see the Lazarus wiki. Internally it uses open as mentioned by RobS.
I've successfully used Shell('open ' + Filename) in OS X 10.4 and 10.3 which seems to work rather nicely for most filetypes.
I stumbled across open at the shell prompt and now miss it in cygwin/linux etc.
fork hurts on Mac. BSDs use vfork, not fork.