In a Delphi DLL need to establish the caller -- which may be a simple ".exe" or a DBMS runtime module -- which means it must obtain the command which is running in the process.
I know that CmdLine won't work, and probably ParamStr(0), and cannot use "main window" based techniques as caller will sometimes not have a window. I suspect that
GetModuleHandle is the starting point, but need assistance to get from there to command being executed.
I created a test dll:
library Project2;
uses
System.SysUtils, System.Classes, Vcl.Forms, Vcl.Dialogs, Winapi.Windows;
{$R *.res}
procedure DoStuff; stdcall;
begin
ShowMessage(
'ParamStr(0): '+ParamStr(0)+#13#10+
'GetCommandLine: : '+GetCommandLine);
end;
exports
DoStuff;
begin
end.
And then call it from a test application:
procedure TForm1.Button1Click(Sender: TObject);
var
module: HMODULE;
doStuff: procedure; stdcall;
begin
module := LoadLibrary('D:\Temp\Win32\Debug\Project2.dll');
if module = 0 then
RaiseLastOSError;
try
doStuff := GetProcAddress(module, 'DoStuff');
if #doStuff = nil then
raise Exception.Create('Could not find export "DoStuff"');
DoStuff;
finally
FreeLibrary(module);
end;
end;
And it sees the command line, using both:
ParamStr(0)
GetCommandLine
GetCommandLine obviously shows the entire command line, while ParamStr(0) is (by definition) just the process executable path.
In fact ParamStr(0) will work fine. It is, on Windows, implemented with a call to the API function GetModuleFileName, passing a value of 0 as the module handle. This retrieves the file name associated with the main executable module. This works just the same no matter that the call is made from a DLL or the main executable.
We don't really need to dig into the implementation if we would trust the Delphi documentation. Admittedly this can sometimes be a risky business! The documentation for ParamStr says:
ParamStr(0) returns the path and file name of the executing program (for example, C:\TEST\MYPROG.EXE).
If you need to know the arguments that were passed to the executable process, you can use ParamStr passing indices greater than zero. Or you could call GetCommandLine and parse the command line yourself.
Do beware that GetCommandLine will not always give the same executable file name as GetModuleFileName. The documentation says:
The name of the executable in the command line that the operating system provides to a process is not necessarily identical to that in the command line that the calling process gives to the CreateProcess function. The operating system may prepend a fully qualified path to an executable name that is provided without a fully qualified path.
All this feels a little dirty though. It might be cleaner to export an initialization function from the DLL and require callers to pass whatever information you need.
Related
Let's assume simple scenario like below:
CALL DBMS_XMLSCHEMA.DELETESCHEMA(
schemaurl => 'non_existing_schemaurl',
delete_option => DBMS_XMLSCHEMA.DELETE_INVALIDATE);
-- ORA-06553: PLS-221: 'DELETE_INVALIDATE' is not a procedure or is undefined
Meanwhile when providing integer value or running inside PL/SQL block the error does not occur:
-- 1)
CALL DBMS_XMLSCHEMA.DELETESCHEMA(
schemaurl => 'non_existing_schemaurl',
delete_option => 2);
-- 2)
BEGIN
DBMS_XMLSCHEMA.DELETESCHEMA(
schemaurl => 'non_existing_schemaurl',
delete_option => DBMS_XMLSCHEMA.DELETE_INVALIDATE);
END;
/
db<>fiddle demo
Is it possible to use DBMS_XMLSCHEMA.<constant> combined with CALL(searching for credible/official source)?
According to the SQL Language Reference:
Use the CALL statement to execute a routine (a standalone procedure or
function, or a procedure or function defined within a type or package)
from within SQL.
The key is the last two words - within SQL. It's easy to think of CALL as similar to the SQL*Plus command EXEC, but CALL runs in a SQL context while EXEC runs in a PL/SQL context.
PL/SQL package variables cannot be used in SQL statements (except for PL/SQL WITH functions, but that feature doesn't work in this context). Unfortunately I can't find an authoritative source for that claim (which is why this is a Wiki answer; hopefully someone else can add that source and then remove this sentence.)
The way you are trying to call is possible only in any named or unnamed block of pl/sql.
To call it independently, you need to pass the arguments only.
CALL DBMS_XMLSCHEMA.DELETESCHEMA('none_existing_schemaurl','DELETE_INVALIDATE');
I apologize for this relatively simple question but I am new to Oracle and DBMS Workspace Manager. I have a stored procedure that checks the current workspace and if it differs from the workspace being passed in it will execute the DBMS_WM.GOTOWORKSPACE. All the examples show using EXEC while calling DBMS_WM.GOTOWORKSPACE but when I have the following syntax inside TOAD it says its invalid, if I take the EXEC off no errors shown.
Which is correct?
//Toad Does not like this synatx
IF UPPER(l_current_workspace) != UPPER(i_workspace) THEN
EXEC DBMS_WM.GOTOWORKSPACE (i_workspace);
END IF;
OR
//TOAD is fine with this
IF UPPER(l_current_workspace) != UPPER(i_workspace) THEN
DBMS_WM.GOTOWORKSPACE (i_workspace);
END IF;
I would guess that the examples are in a form something like EXEC DBMS_WM.GOTOWORKSPACE (<>);and not between a conditionalIF...END IF;`
EXEC is not a PLSQL reserved word. It is a macro/shortcut in the client rather than the database server, that expands to wrap the statement it precedes into a BEGIN ... END; block.
It is used very widely on the command-line (SQLPlus and SQLcl) and editors like Toad and SQLDeveloper et al. often support it, but it is supported as a monolithic command only (EXEC as a prelude to the block) rather than inside of the block (Nested EXEC is not supported, nor is an EXEC inside of another BEGIN...END; block).
Since it isn't a reserved word, expanding it when it is in another block is a problem. Parsing it becomes ambiguous -- one could define one's own function called EXEC and it would be perfectly valid to run in a PLSQL block. The editor would then need to make an uncertain call about what to do when it encountered the word. The below example would break if EXEC were expanded instead of being recognized as a procedure in the PLSQL block:
CREATE PROCEDURE EXEC(PARAM_1 IN VARCHAR2)
IS
BEGIN
DBMS_OUTPUT.PUT_LINE(UTL_LMS.FORMAT_MESSAGE('Param is:[%s]',PARAM_1));
END EXEC;
/
BEGIN
EXEC('VOLTRON');
END;
/
So the second example is correct (presuming it is housed in another BEGIN...END; block), and is universally compatible since it doesn't need to be intercepted and modified client-side.
How I could ignore the error msgbox if the uninstall.vsf file does not exists when calling LoadVCLStyle_UnInstall function in this code?
I supposed that using a Try block with an empty Except will be enough as in other languages, but this is not the case.
// Import the LoadVCLStyle function from VclStylesInno.DLL
procedure LoadVCLStyle_UnInstall(VClStyleFile: String); external 'LoadVCLStyleA#{app}\uninstall.dll stdcall uninstallonly';
//E: Occurs when the uninstaller initializes.
function InitializeUninstall: Boolean;
begin
Result := True;
// Initialize the VCL skin style.
try
LoadVCLStyle_UnInstall(ExpandConstant('{app}\uninstall.vsf'));
except
finally
end;
end;
The possibility to check for the file existence beforehand was already mentioned.
The user TLama mentioned that the code in the question is not regular pascal program code, but Inno Setup script code and and that my answer doesn't apply in this case. Because the following text could be of interest for pascal programmers we keep it.
The EXCEPT statement by itself doesn't handle the exception, it marks only the point where program execution should continue after an error has occured. When the exception isn't handled/caught in the EXCEPT ... END block it will be transferd to the next higher EXCEPT statement. (Freepacal reference guide chapter 17)
I also don't think that TRY ... EXCEPT ... FINALLY ... END will work. Either EXCEPT or FINALLY, not both.
If you want to capture the exception you must do something like:
TRY
LoadVCLStyle_UnInstall(ExpandConstant('{app}\uninstall.vsf'));
EXCEPT
On EWhateverException DO ...;
END;
If the exception class for this error isn't defined in the documentation, you can use the following trick to find the exception class name:
TRY
LoadVCLStyle_UnInstall(ExpandConstant('{app}\uninstall.vsf'));
EXCEPT
ON Exception DO WriteLn(ExceptObject.ClassName);
END;
With ON Exception DO .. you can catch any exception, but i don't recomend to use that variant for the definite program.
I have this code:
procedure EstablishCommunication;
var
State : TStates;
Attempts : Byte;
procedure IncAttempts;
begin
Inc(Attempts);
end;
begin
State := stReadDeviceID;
Attempts := 0;
while True do
begin
if Attempts >= MAX_ATTEMPTS then
begin
State := stError;
end;
case State of
stReadDeviceID:
begin
// some code
IncAttempts;
end;
stError:
begin
// Error code
end;
...
...
...
I'd like to put the code that set state to stError within of the procedure IncAttempts, resulting:
procedure EstablishCommunication;
var
State : TStates;
Attempts : Byte;
procedure IncAttempts;
begin
Inc(Attempts);
if Attempts >= MAX_ATTEMPTS then
begin
State := stError;
end;
end;
begin
State := stReadDeviceID;
Attempts := 0;
while True do
begin
case State of
stReadDeviceID:
begin
// some code
IncAttempts;
end;
stError:
begin
// Error code
end;
...
...
...
So, can I move the code to IncAttempts?
Is this a code smell?
If yes, Can you advice me a better way?
I would see this as perfect valid code. I ask myself the following questions when declaring a method inside another. Most of the time I don't do it, but sometimes it's results in better code.
Will the internal function ever need to change as in a descendant class?
Can I override External method without calling the internal method and be OK?
Does the internal function have practical application outside of external method?
Is the internal function complex enough that it should be unit tested outside the scope of there external method?
If any of the above apply don't use an Internal Method.
However if if you don't have any of the above, and it can remove repeated code and/or simplify the design then you can consider using a internal function.
No real problem with that, should work just fine. You are already modifying another local variable Attempts so there is no reason why modifying State should smell more.
I do think you should be careful of using inline functions to much. The code often ends up hard to read/understand.
I would say that the new code have some whiff...
It all depends on how many states you manage in current code, and if the number of states could change in the future. Beware of how and when you set the state, and beware of how and when you check the state.
In the two code snippets you show, there is a minor difference:
In the first, original code, the current state is preserved through the iteration, and the new error-state is set in the beginning of the iteration, and it is always checked.
In the second, refactored code, the state is changed in the middle of the iteration, and it is only altered if the state is stReadDeviceID.
Now, if the last line in this while True do-iteration is if State = stError then Break;, then your first code will run the iteration one more time, changing the state to stError in the beginning if the iteration. Your second code will exit at the end of the current iteration, and the code in the stError-section of the case-statement will never be executed...
If you want to go all the way, and study the GoF's State Design Pattern, then take a look at these pages:
http://en.wikipedia.org/wiki/State_pattern (no Delphi code...)
http://sourcemaking.com/design_patterns/state (with Delphi code!)
http://www.dofactory.com/Patterns/PatternState.aspx (no Delphi code...)
http://conferences.embarcadero.com/article/32129#_Toc12157322 (with Delphi code!)
The documentation for QStringList QCoreApplication::arguments() (Qt) states that:
Usually arguments().at(0) is the
program name, arguments().at(1) is the
first argument, and arguments().last()
is the last argument. See the note
below about Windows.
It further elaborates:
On Windows, [...] the arguments() are
constructed from the return value of
GetCommandLine(). As a result of this,
the string given by arguments().at(0)
might not be the program name on
Windows, depending on how the
application was started.
Referring to the last bold part, I am curious about which situation does this applies in. The Qt documentation doesn't explain it nor does GetCommandLine's (WINAPI) documentation.
This can happen when your program is spawned via CreateProcess - have a look at the description for the lpCommandLine parameter. Basically it is up to the program calling CreateProcess to fill in that first parameter, so it can happen that the value isn't filled in the usual way.
From the link:
"If both lpApplicationName and lpCommandLine are non-NULL, the null-terminated string pointed to by lpApplicationName specifies the module to execute, and the null-terminated string pointed to by lpCommandLine specifies the command line. The new process can use GetCommandLine to retrieve the entire command line. Console processes written in C can use the argc and argv arguments to parse the command line. Because argv[0] is the module name, C programmers generally repeat the module name as the first token in the command line."
Similarly, this can happen if your program is started from another using the spawn family of functions. There, the documentation states:
"At least one argument, either arg0 or argv[0], must be passed to the child process. By convention, this argument is a copy of the pathname argument. However, a different value will not produce an error."