Here's a small macro I want to call at the start of my macro functions.
It is the equivalent of %if not %length(&x) %then %let x = &val.
I want to use it as it's more readable and will help me to parse my code to build documentation (Which I need to do with regex as I can't install external softwares).
%macro def
/*---------------------
Assign default value
---------------------*/
(_x_data_x_ /* variable name (to be passed raw without &) */
,value /* default value */
);
%if not %length(&&&_x_data_x_) %then %let &_x_data_x_ = &value;
%mend;
Here's how it works:
%macro test(a,b);
%def(b,&a)
%put "&b";
%mend;
%test(%str(,)); /* prints "," */
I chose the unusual parameter name _x_data_x_ because the macro fails when it's fed a value equal to the parameter's name (i.e. if my b parameter was named _x_data_x_).
Can I make it really safe, apart from choosing obscure names ?
That is the correct approach.
You could make the macro more robust by adding checks:
%* Prevent self reference because
%* assignment will not bubble to containing scope;
%if %upcase(%superq(x_data_x)) eq X_DATA_X %then %do;
%put ERROR: x_data_x=%superq(x_data_x) is not allowed;
%abort cancel;
%end;
%* Prevent reference to non-existent because
%* assignment will automatically localize
%* and not bubble to containing scope;
%if not %symexist (%superq(x_data_x)) %then %do;
%put ERROR: x_data_x=%superq(x_data_x) is invalid;
%put ERROR: %superq(x_data_x) does not exist in callee scope;
%abort cancel;
%end;
Related
I have this data (simplified)
When I select int1, int2, str1 in that order, I get a invalid cast error. When I select them in str1, int1, int2 order the sql call completes successfully.
The sql call looks like this (again simplified)
Select int1, int2, str1 from table where int1 = ? (my variable)
There no actual casting of values in this call.
The code is c++ using ODBC connection of Native Client 11 to a MSSQL database.
The variables and bind columns are all static declared and of proper types an lengths (and in the order of the call when I changed the order).
I'm looking for any other ideas of how to find why it fails when I have a char value selection following an int value. The failure occurs at the SqlFetch function call (when the returned data is attempted to be loaded to the bound columns)
The non simplified query returns 140 columns of mixed types and I would like to apply a solution to the whole query.
(to note)
c++ column binds look like this are in the order of the select:
sqlret = SQLBindCol(hstmt_myQuery,item_col,SQL_C_LONG,&int1, (SDWORD) sizeof(int1), &indicator[item_col++]); exec_sql_error(__FILE__, &henv, &hdbc, last_connect_time, "BindCol hstmt_myQuery", hstmt_myQuery, &sqlret);
sqlret = SQLBindCol(hstmt_myQuery,item_col,SQL_C_LONG,&int2, (SDWORD) sizeof(int2), &indicator[item_col++]); exec_sql_error(__FILE__ , &henv, &hdbc, last_connect_time , "BindCol hstmt_myQuery" , hstmt_myQuery , &sqlret );
sqlret = SQLBindCol(hstmt_myQuery,item_col,SQL_C_CHAR,str1, (SDWORD) sizeof(str1), &indicator[item_col++]); exec_sql_error(__FILE__ , &henv, &hdbc, last_connect_time , "BindCol hstmt_myQuery" , hstmt_myQuery , &sqlret );
Evaluation order of parameter are unspecified.
So you have issue with item_col with SQLBindCol(/*..*/, item_col, /*..*/, item_col++);.
increase item_col in a separate statement should solve your issue.
I'm trying to create a function that will cast boolean values to 't' or 'f', or '' if NULL.
CREATE OR REPLACE FUNCTION bool(b BOOLEAN) RETURNS VARCHAR
AS $$
BEGIN
IF b IS NULL THEN
RETURN '';
END IF;
IF b THEN
RETURN 't';
ELSE
RETURN 'f';
END IF;
END;
$$ LANGUAGE PLPGSQL CALLED ON NULL INPUT;
However, the following always returns NULL. What gives?
SELECT bool(NULL)
bool is also a built-in function and type cast (because there is a data type with that name).
You need to either explicitly reference your function by prefixing it with the schema:
select public.bool(null);
or give your function a different name.
Unrelated, but: your function can be simplified to:
CREATE OR REPLACE FUNCTION bool(b BOOLEAN)
RETURNS VARCHAR
AS $$
select coalesce(case when b then 't' else 'f' end, '');
$$
language sql
called on null input;
I am struggling to implement a function from a C dll. It is declared as
int DetectTransactionCode(wchar_t* wi_type, wchar_t* wi_id);
If have declared and called this in my delphi code as
function DetectTransactionCode (var wi_type, wi_id: PWideChar): Integer;
cdecl; external 'WiGroupDetect.dll';
procedure TForm20.Button2Click(Sender: TObject);
var witype,wi_id : widestring;
res : integer;
begin
res := DetectTransactionCode(PWideChar(witype),PWideChar(wi_id));
showmessage(res.tostring);
ShowMessage(witype +' & ' +wi_id);
end;
I am receiving the result however my witype and wi_id cause access violations.
I have also tried :
Function DetectTransactionCode (var witype,wi_id :widestring ) : Integer cdecl;
external 'WiGroupDetect.dll';
procedure TForm20.Button2Click(Sender: TObject);
var witype,wi_id : widestring;
res : integer;
begin
res := DetectTransactionCode(witype,wi_id);
showmessage(res.tostring);
ShowMessage(witype +' & ' +wi_id);
end;
I assume both parameters are out parameters. The 3rd party supplied the following :
Returns 1 for success, 0 for cancellation/failure
Note: Blocking call only returns when:
1 - A wiCode is detected,
2 - The KillDetectionProcess()is called,
3 – Some error or failure occurs, or
4 - Ultimately the timeout (30 minutes) expires.
Parameters:
wi_type Returns the type of token retrieved (i.e. “WIQR” for QR) on success; or “NONE” on cancellation/failure.
wi_id Returns the wiCode detected on success; “CANCELLED” on cancellation; or with additional error information on failure (i.e. “ERROR: ...”).
I have tried changing the parameters to ansistring, unicodestring but still get the same problem. I suspect it has something to with the var parameter but not sure how to overcome it. Any assistance would be appreciated.
I have been given a C sample implementation by them
[DllImport("WiGroupDetect.dll", EntryPoint = "DetectTransactionCode", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode)]
private static extern int DetectTransactionCode([MarshalAsAttribute(UnmanagedType.LPWStr)] StringBuilder strWITYPE, [MarshalAsAttribute(UnmanagedType.LPWStr)] StringBuilder strUID);
Not sure if this changes my Delphi implementation
The two parameters are of type wchar_t* which is a pointer to 16 bit character. Normally that would mean a pointer to null-terminated array of wide UTF-16 characters.
So, the correct translation of the code as presented is:
function DetectTransactionCode (wi_type, wi_id: PWideChar): Integer;
cdecl; external 'WiGroupDetect.dll';
You used var parameters of type WideString. That would match parameters that are pointers to BSTR which is quite different.
I'm assuming the cdecl calling convention, the default for every C compiler that I have encountered. If you have some extra information that says the function is stdcall, so be it. But as written in the question this is cdecl.
It's not clear how the data flows. Are the two string parameters in or out? Is one in and the other out? If either are out parameters then you need some way to know how big a buffer to allocate. The fact that the function does not let you pass the buffer length suggests that the data flows in. That said, in that case the parameters should have been const wchar_t* in the C code. There's a lot of uncertainty here. Do be aware that the function prototype does not completely define the semantics of a function.
Thanks David for your answers they helped. I managed to come right using this code. I am aware that if the string passed back is longer than I have provided for then there will be an error
function DetectTransactionCode (wi_type, wi_id: pwidechar): Integer;
stdcall; external 'WiGroupDetect.dll';
procedure TForm20.Button2Click(Sender: TObject);
var witype,wi_id : widestring;
res : integer;
begin
setlength(witype,10);
setlength(wi_id,100);
res := DetectTransactionCode(pwidechar(witype),pwidechar(wi_id));
showmessage(res.tostring);
setlength(witype,pos(#0,witype)-1);
setlength(wi_id,pos(#0,wi_id)-1);
ShowMessage(trim(witype) +' & ' +trim(wi_id));
end;
does Sas provide mechanism of chain-expressions?
does Sas provide mechanism of In-clause?
Simple examples:
a = '09MAY2010'd;
b = '17MAY2010'd;
if (a<=c<=b) then do; /*code*/ end;
if (c in (a:b)) then do; /*code*/ end;
maybe any good techniques of if/where statements?
your suggestions and advises, please.
Thanks!
Your example, changed a bit:
data _null_;
a = '09MAY2010'd;
b = '17MAY2010'd;
c = '17MAY2010'd;
if (a<=c<=b) then do;
putlog "a<=c<=b";
end;
select (c);
when (a, b) putlog "in a, b";
when ('17MAY2010'd) putlog "'17MAY2010'd";/* not used, only first match is executed */
otherwise;
end;
run;
IN operator used with IF or in WHERE clause requires constants in the list.
Apart from the IN operator, which only accepts constant values inside the paranthesis, there is also an (undocumented) IN function, which can be used with variables, so instead of if c in(a,b) you can use if in(c,a,b) which will work also when a and b are variables.
Another possibility is to use WHICHN or WHICHC functions, which has the same syntax, and which return 0 (FALSE) when a match is not found, and otherwise the number of the (first) match.
I have a dll RL6_dll.dll from a routing program RouteLogix that is used to plan trucks etc.
Now we want to use that from Delphi 2007.
We have a c++ header for the dll and a working example that use it in C++-Builder.
Here is an example from that file:
// Use this routine to find the directory where the data-xxx subdirectories
// are expected.
// char * vszBuf - address of a character array to receive the (null-terminated) path.
// int nBufSize - is the size of the array
// (internally we allow paths up to 256 characters long)
DllFn(void) RL6_GetLocalGeoDir(char *vszBuf, int nBufSize);
My try from Delphi:
procedure TfrmRL6Xml.Button1Click(Sender: TObject);
var
s1: PChar;
IntValue : Integer;
RL6_GetLocalGeoDir: function(vszBuf: pchar; nBufSize: Integer): integer; stdcall;
begin
handle := LoadLibrary('C:\Carp\RL6_app2\rl6dll\RL6_DLL.dll');
if handle <> 0 then
begin
#DllFn := GetProcAddress(handle, 'RL6_PREINIT');
#RL6_GetLocalGeoDir := GetProcAddress(handle, 'RL6_GETLOCALGEODIR');
s1 := ' ';
IntValue := length (s1);
RL6_GetLocalGeoDir (s1, IntValue);
showMessage(s1);
end;
end;
So now I expect s1 contains a string, but instead the functions seems handle IntValue as string. It seems like the s1 and IntValue parameters are exchanged. We have of course tried RL6_GetLocalGeoDir (IntValue, s1) but that didn't work either. Any suggestions how to call it ?
The quest title mentions the Pascal calling convention, but the question body never comes back to that topic. Does the documentation for the DLL say it uses the Pascal calling convention? It's a very rare calling convention to use nowadays. (It was used in the Windows API in the 16-bit days, and although some headers from those times still say PASCAL today, that macro has been redefined to refer to the stdcall calling convention instead.)
You haven't shown the definition of DllFn — neither in the C code nor the Delphi code. In C, I imagine it's a macro that includes the function's calling convention, so go find that definition to confirm what's really being used. In Delphi, it looks like you're using it as a function pointer. I encourage you to use the same function names in your application as the DLL uses. It makes life easier for everyone involved — no more does anyone look at code and wonder what function is really being called.
If you confirm that the DLL really uses the Pascal calling convention, then specifying it in Delphi is as simple as changing the "stdcall" directive on your function declaration to "pascal":
RL6_GetLocalGeoDir: procedure(vszBuf: PAnsiChar; nBufSize: Integer); pascal;
I've also changed the PChar argument to use PAnsiChar because now that some versions of Delphi are Unicode, the PChar type might mean PWideChar, and you don't want that here.
You need to call the procedure with a preallocated buffer, and with the correct declaration, like so:
procedure TfrmRL6Xml.Button1Click(Sender: TObject);
var
s: AnsiString;
IntValue : Integer;
RL6_GetLocalGeoDir: procedure(vszBuf: PAnsiChar; nBufSize: Integer); stdcall;
begin
handle := LoadLibrary('C:\Carp\RL6_app2\rl6dll\RL6_DLL.dll');
if handle <> 0 then
begin
#DllFn := GetProcAddress(handle, 'RL6_PREINIT');
#RL6_GetLocalGeoDir := GetProcAddress(handle, 'RL6_GETLOCALGEODIR');
IntValue := 256;
SetLength(s, IntValue);
RL6_GetLocalGeoDir (PAnsiChar(s), IntValue);
s := PAnsiChar(s);
showMessage(s);
end;
end;
Edit:
Your modified question still contains bad code. You use
var
s1: PChar;
s1 := ' ';
IntValue := length (s1);
This is just wrong, as you don't provide a buffer but a pointer to a string constant in the code segment. Using this will lead to crashes, just try with for example the API function GetWindowsDirectory():
var
P: PAnsiChar;
begin
P := ' ';
GetWindowsDirectory(P, 80);
end;
Running this will result in an access violation in ntddll.dll, write of an address in the code area (for example $0044C6C0).