Thursday, May 14, 2009

Powerbuilder Print PDF

I discovered the ever so useful DDE today.


type process_information from structure
long hprocess
long hthread
long dwprocessid
long dwthreadid
end type

type startupinfo from structure
long cb
string lpreserved
string lpdesktop
string lptitle
long dwx
long dwy
long dwxsize
long dwysize
long dwxcountchars
long dwycountchars
long dwfillattribute
long dwflags
long wshowwindow
long cbreserved2
long lpreserved2
long hstdinput
long hstdoutput
long hstderror
end type

function boolean CreateProcess ( &
string lpApplicationName, &
string lpCommandLine, &
long lpProcessAttributes, &
long lpThreadAttributes, &
boolean bInheritHandles, &
long dwCreationFlags, &
long lpEnvironment, &
string lpCurrentDirectory, &
STARTUPINFO lpStartupInfo, &
ref PROCESS_INFORMATION lpProcessInformation &
) library "kernel32.dll" alias for "CreateProcessW"

function boolean CloseHandle(ulong hObject) library "kernel32.dll"
function long WaitForInputIdle(ulong hProcess, long dwMilliseconds) library "user32.dll"



STARTUPINFO lstr_si
PROCESS_INFORMATION lstr_pi
long ll_null, ll_CreationFlags, ll_dde
string ls_null, ls_adobe, ls_pdf_path

ls_pdf_path = 'C:\test.pdf'

If RegistryGet("HKEY_CLASSES_ROOT\Applications\AcroRD32.exe\shell\Read\command", "", ls_adobe) = 1 Then

ls_adobe = Mid( ls_adobe, 2, Len(ls_adobe) - 7 )

// initialize arguments
SetNull(ll_null)
SetNull(ls_null)

//Structure Size
lstr_si.cb = 72

lstr_si.dwFlags = STARTF_USESHOWWINDOW
lstr_si.wShowWindow = 0
ll_CreationFlags = CREATE_NEW_CONSOLE + NORMAL_PRIORITY_CLASS

//Start Adobe Reader
If CreateProcess(ls_null, ls_adobe, ll_null, ll_null, False, ll_CreationFlags, ll_null, ls_null, lstr_si, lstr_pi) Then

//Wait a tic
WaitForInputIdle(lstr_pi.hprocess, 60000)

//Connect to Adobe via DDE
//Note 'parent' at this point is the window object. (code resides in a button)
//I havent tested this, but i imagine you could create a window in memory just for
//passing to this function in order to move it into a generic function.
ll_dde = OpenChannel("Acroview", "Control", Handle( parent ) )

//Do magic
If ll_dde > 0 Then
ExecRemote('[DocOpen("' + ls_pdf_path + '")]', ll_dde)
ExecRemote('[FilePrintSilent("' + ls_pdf_path + '")]', ll_dde)
ExecRemote('[DocClose("' + ls_pdf_path + '")]', ll_dde)
ExecRemote('[AppExit]', ll_dde)

CloseChannel(ll_dde)
End If

//Clean up
CloseHandle(lstr_pi.hprocess)
CloseHandle(lstr_pi.hthread)
End If

End If

Thursday, April 9, 2009

Get Oracle Client Version

I've seen this all over the web using tnsping.exe or sqlplus..both of which are not guaranteed to be on the clients machine.. Some still would try calling a function of oci.dll that gives you the client version..but that function is not supported below i believe version 9 (dont quote me on that..i know its in 10, and i know its not in 8)

So this my contribution, and what i think to be the best way to get the oracle client version programaticalyaly..

Note: No error checking done here..

c#:

//Load oracle
lp_oci_dll = LoadLibraryExA("oci.dll", IntPtr.Zero, 8);

//Gets the path of oci.dll
System.Text.StringBuilder ls_buffer = new System.Text.StringBuilder(1024);
GetModuleFileNameA(lp_oci_dll, ls_buffer, 1024);

//Get oracle version
FileVersionInfo l_file_info = FileVersionInfo.GetVersionInfo(ls_buffer.ToString());
ls_ret = l_file_info.FileVersion;

Sunday, March 29, 2009

Powerbuilder Base 64 Encoding/Decoding

Edit: Added of_decode_base64_to_blob at bottom.

There is a PB example out there for Base64 encoding and decoding, but it relies on calling COM objects. (At least the few I found did..) i.e :


string ls_ret
oleobject lo_xml
oleobject lo_node

lo_xml = create oleobject
lo_xml.connecttonewobject("Microsoft.XMLDOM")

lo_node = lo_xml.createElement("b64")
lo_node.dataType = "bin.base64"

//Encode
lo_node.NodeTypedValue = Blob(as_data)
ls_ret = lo_node.Text

//Decode
//lo_node.Text = as_data
//ls_ret = String( lo_node.NodeTypedValue )


Destroy lo_node
Destroy lo_xml

return ls_ret


Well, I came across a situation where I needed to call PB functions from a PBNI interface. One of the functions called used the above function to decode base64. Well for a reason I've yet to figure out, calling PB from PBNI fails on "create oleobject". Fail. So I rewrote the base64 decoding and encoding to do it manually and remove the dependency of the oleobject. The math could probably be simplified in here..but im too lazy to think.

Note: Both only work with unicode strings (Default for PB strings)
Note2: I used code found here http://www.motobit.com/tips/detpg_Base64/ as a base (youll notice more in the decode section as I got lazier and didnt rename the variables..)


Encode:

public function string of_encode_base64 (string as_data);
string ls_data, ls_ret
string ls_base64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"

string ls_tmp
longlong ll_24bits, ll_tmp, ll_tmp2, ll_tmp3
longlong ll_1, ll_2, ll_3, ll_4, ll_i
long ll_newline_offset

ls_data = as_data
ls_ret = ""

SetNull( ll_tmp3 )

For ll_i = 1 To longlong( Len( ls_data ) + 1 )

//Create 24bit buffer from unicode
If IsNull( ll_tmp3 ) Then

//Reverse first two bytes
ll_tmp = Asc( Mid( ls_data, ll_i, 1 ) )
ll_tmp = (( ll_tmp - ( Int( ll_tmp / 256 ) * 256 ) ) * 256) + ( ll_tmp / 256 )

ll_i++

//Get 3rd byte and store 4th for later
ll_tmp2 = Asc( Mid( ls_data, ll_i, 1) )
ll_tmp3 = Int( ll_tmp2 / 256 )
ll_tmp2 = ( ll_tmp2 - ( ll_tmp3 * 256 ) )

ll_24bits = (256 * ll_tmp) + ll_tmp2
Else
//Reverse next char and add ll_tmp3 to the beginning to create our next 3 byte(24bit) buffer
ll_tmp = Asc( Mid( ls_data, ll_i, 1 ) )
ll_24bits = ( ll_tmp3 * 65536 ) + ( ( ll_tmp - ( Int( ll_tmp / 256 ) * 256 ) ) * 256 ) + ( Int( ll_tmp / 256 ) )
SetNull( ll_tmp3 )
End If

//Get each 6 bit indexes
ll_1 = Int( ll_24bits / 262144 ) //Shift 18 bits right to get first 6bit index
ll_24bits -= ll_1 * 262144 //remove first 6 bits from buffer
ll_2 = Int( ll_24bits / 4096 ) //Shift 12 bits right for 2nd
ll_24bits -= ll_2 * 4096 //Remove it from buffer
ll_3 = Int( ll_24bits / 64 ) //Shift 6 bits for third
ll_4 = ll_24bits - ( ll_3 * 64 ) // remove third and left with last 6 bit index

//Convert To base64
ls_ret += Mid( ls_base64, ll_1 + 1, 1 ) + Mid( ls_base64, ll_2 + 1, 1 ) + Mid( ls_base64, ll_3 + 1, 1 ) + + Mid( ls_base64, ll_4 + 1, 1 )

//Add a new line For Each 72 chars In dest
If Mod( Len( ls_ret ) - ll_newline_offset , 72 ) = 0 Then
ls_ret += "~r~n"
ll_newline_offset += 2
End If

Next

//Pad end
Choose Case Mod( Len( ls_data ) - 1, 3 )
Case 1 //8 bit final
ls_ret = Left( ls_ret, Len( ls_ret ) - 2 ) + "=="
Case 2 //16 bit final
ls_ret = Left( ls_ret, Len( ls_ret ) - 1 ) + "="
End Choose

return ls_ret
end function


Decode:

public function string of_dec_to_hex (long al_number);
// 0 <= n <= 15
// Converts integer from 0 - 15 into character hex representation

string sHexChar
Choose Case al_number
Case 10
sHexChar = 'A'
Case 11
sHexChar = 'B'
Case 12
sHexChar = 'C'
Case 13
sHexChar = 'D'
Case 14
sHexChar = 'E'
Case 15
sHexChar = 'F'
Case Else
sHexChar = String(al_number)
End Choose

Return sHexChar

end function

public function string of_convert_to_hex (long al_number);
// string of_convert_to_hex( long alNumber ), recursive:
// Recursive function to translate number into hex representation
If al_number > 15 Then
Return of_convert_to_hex( al_number / 16 ) + of_dec_to_hex( Mod( al_number, 16 ) )
Else
Return of_dec_to_hex( al_number )
End If

end function

public function long of_hex2long (string as_hex);
string ls_hex
integer i,length
long result = 0

length = len(as_hex)
ls_hex = Upper(as_hex)
FOR i = 1 to length
result += &
(Pos ('123456789ABCDEF', mid(ls_hex, i, 1)) * &
( 16 ^ ( length - i ) ))
NEXT
RETURN result

end function

public function string of_replace (string as_source, string as_replace, string as_with);
int ll_start = 1,ll_len

ll_len = len( as_replace )
ll_start = Pos( as_source, as_replace, ll_start )

Do While ll_start > 0
as_source = Replace( as_source, ll_start, ll_len, as_with )
ll_start = Pos( as_source, as_replace, ll_start + Len( as_with ) )
Loop

return as_source
end function


public function string of_decode_base64 (string as_base64);
string Base64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
string ls_working
long dataLength, groupBegin, ll_mod
string sOut, pOut, ls_tmp

//remove white spaces, If any
ls_working = of_replace(as_base64, "~r", "")
ls_working = of_replace(as_base64, "~n", "")
ls_working = of_replace(ls_working, "~t", "")
ls_working = trim( ls_working )

dataLength = Len( ls_working )
If Mod(dataLength, 4) <> 0 Then
return ""
End If

//Now decode each group:
For groupBegin = 1 To dataLength Step 4
long numDataBytes, CharCounter, thisData, nGroup
char thisChar
//Each data group encodes up To 3 actual bytes.
numDataBytes = 3
nGroup = 0

For CharCounter = 0 To 3
/*
Convert each character into 6 bits of data, And add it To
an integer For temporary storage. If a character is a '=', there
is one fewer data byte. (There can only be a maximum of 2 '=' In
the whole string.)
*/

thisChar = Mid(ls_working, groupBegin + CharCounter, 1)

If thisChar = "=" Then
numDataBytes = numDataBytes - 1
thisData = 0
Else
thisData = Pos(Base64, thisChar, 1) - 1
End If
If thisData = -1 Then
return ""
End If

nGroup = 64 * nGroup + thisData
Next

//Hex splits the long To 6 groups with 4 bits
string ls_group
ls_group = of_convert_to_hex( nGroup )


//Add leading zeros
Do While Len(ls_group) < 6
ls_group = "0" + ls_group
Loop

//Converts two characters at a time accounting for Unicode and Little Endian
//Saves the third char for the next run
If ls_tmp <> "" Then
pOut = Char( of_hex2long( Mid( ls_group, 1, 2 ) + ls_tmp) )
pOut = pOut + Char( of_hex2long( Mid( ls_group, 5, 2 ) + Mid( ls_group, 3, 2) ) )
ls_tmp = ""
Else
pOut = Char( of_hex2long( Mid( ls_group, 3, 2 ) + Mid( ls_group, 1, 2) ) )
ls_tmp = Mid( ls_group, 5, 2 )
End If

//add numDataBytes characters To out string
sOut = sOut + Left(pOut, numDataBytes)
Next

return sOut
end function


Edit:
I got through with my Quiznos pretty quick today, so i decided to take a quick look at the comments about decoding base64 to a blob.

NOTE: I didn't spend very much time going over this (Im lazy!). I did however test it succesfully with two jpegs that where converted to base64 strings using C#'s Convert.ToBase64String

NOTE2: Encoding I did not even glance at. Your on your own there.


public function blob of_decode_base64_to_blob (string as_base64);
string ls_base_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
string ls_working
long ll_data_len, ll_start_from, ll_mod
string ls_tmp, ls_hex

long ll_num_bytes, ll_count, ll_this_pos, ll_dec
char lc_char

blob lb, lb_out
long ll_pos
ll_pos = 1

//remove white spaces, If any
ls_working = gnv_app.of_conv(as_base64, "~r", "")
ls_working = gnv_app.of_conv(as_base64, "~n", "")
ls_working = gnv_app.of_conv(ls_working, "~t", "")
ls_working = trim( ls_working )

ll_data_len = Len( ls_working )
If Mod(ll_data_len, 4) <> 0 Then
return lb
End If

lb = Blob(space(ll_data_len))

//Now decode each group:
For ll_start_from = 1 To ll_data_len Step 4

//Each data group encodes up To 3 actual bytes.
ll_num_bytes = 3
ll_dec = 0

For ll_count = 0 To 3
/*
Convert each character into 6 bits of data, And add it To
an integer For temporary storage. If a character is a '=', there
is one fewer data byte. (There can only be a maximum of 2 '=' In
the whole string.)
*/

lc_char = Mid(ls_working, ll_start_from + ll_count, 1)

If lc_char = "=" Then
ll_num_bytes = ll_num_bytes - 1
ll_this_pos = 0
Else
ll_this_pos = Pos(ls_base_chars, lc_char, 1) - 1
End If
If ll_this_pos = -1 Then
return lb
End If

ll_dec = 64 * ll_dec + ll_this_pos
Next

//Hex splits the long To 6 groups with 4 bits
ls_hex = of_convert_to_hex( ll_dec )

//Add leading zeros
Do While Len(ls_hex) < 6
ls_hex = "0" + ls_hex
Loop

//Converts two characters at a time accounting for Little Endian
//Saves the third char for the next run
If ls_tmp <> "" Then
//Add first two byte
BlobEdit(lb, ll_pos, of_hexlong( Mid( ls_hex, 1, 2 ) + ls_tmp))
ll_pos += 2
//Add next two bytes
BlobEdit(lb, ll_pos, of_hexlong( Mid( ls_hex, 5, 2 ) + Mid( ls_hex, 3, 2) ) )
ll_pos += 2

ls_tmp = ""
Else
//Add two bytes
BlobEdit(lb, ll_pos, of_hexlong( Mid( ls_hex, 3, 2 ) + Mid(ls_hex, 1, 2)) )
ll_pos += 2

//Save third
ls_tmp = Mid( ls_hex, 5, 2 )
End If

//Adjust position as needed
ll_pos = ll_pos - (3 - ll_num_bytes)
Next

lb_out = BlobMid(lb, 1, ll_pos)

return lb_out
end function

Sunday, January 18, 2009

Powerbuilder authenticate Active Directory/LDAP

Looks like there isn't much out there on this. Most use a third party dll/ole, some even go as far to say it can't be done.. This here is pure PB that makes a single API call.


type guid from structure
long data1
integer data2
integer data3
character data4[4]
end type

type prototypes
function long ADsOpenObject(string path, string userName, string password, long flags, ref GUID iid, ref long ppObject) library "activeds.dll"
end prototypes

public function long of_is_authenticated (string as_domain, string as_user_name, string as_password)
/*
Authenticates against Active directory
as_domain = Domain to connect to. exp. "yourplace.com"
as_user_name = User name to authenticate.
as_password = Password for user.

Returns Values:
0 = Authenticated
-1 = Unknown username or bad password
-2 = Password expired
-3 = Account disabled
-4 = Domain not found
-5 = Logon ID already in use
-6 = Unknown Error
*/

GUID ls_iid
//oleobject la_pp //sometimes this causes an issue..
long la_pp
long ll_ret


// IUnknown interface = 00000000-0000-0000-C000-000000000046
ls_iid.data4[1] = char(192)
ls_iid.data4[4] = char(17920) //LittleEndian

ll_ret = ADsOpenObject("LDAP://" + as_domain, as_user_name, as_password, 1, ref ls_iid, ref la_pp)

//la_pp.disconnectobject( ) //How do we clean up?
//destroy la_pp

Choose Case ll_ret
case 0
//Success
case -2147023570
//Unknown username or bad password
ll_ret = -1
case -2147943730
//Password expired
ll_ret = -2
case -2147943731
//Account disabled
ll_ret = -3
case -2147943755
//Domain not found
ll_ret = -4
case -2147943763
//Logon ID already in use
ll_ret = -5
case else
//Unknown Error
ll_ret = -6
End Choose

return ll_ret
end function


Tada..

This is compatiable with PB 10. In the newer versions of PB it is easier to use the byte datatype with the guid instead of the backwards chars..

There are a few more error codes i only took the common ones. Take a look here on how to find the numerous others: http://msdn.microsoft.com/en-us/library/aa746386(VS.85).aspx

[UPDATE:]
I've run into an issue with powerbuilder 11.5 using this code..it doesn't seem to like the oleobject..any ideas are welcome..

Sunday, December 28, 2008

Hmm

Blogging takes too much time away from CoD5..

Friday, November 28, 2008

Powerbuilder byte datatype

One of the more frustrating problems with programming with windows api in powerbuilder is the lack of the byte datatype. Although as of powerbuilder 10.5 and up it is now included, those of us stuck with a version below that have to work a little harder to get the same results. This is how I was able to work around this..

char is the smallest datatype that powerbuilder handles and it is two bytes long. So, when ever the need for defining a byte array rises you would have to cut the size in half when defining the char array. For example..if funtion calls for a byte array byte[6], you would declare: char lc_char[3].

Now the hard part..say for this byte array the function is expexting the following:
byte[1] = 192
byte[2] = 00

byte[3] = 00
byte[4] = 70

byte[5] = 192
byte[6] = 70

Now, lets take the first two bytes to powerbuilder:
lc_char[1] = char(192)

This works as expected..lets set the next two bytes
lc_char[2] = char(70)

This is where things get tricky..how does powerbuilder decide which byte is filled with 00 and which byte is filled with 70? Short answer Endianness. Powerbuilder uses Little-endian store bytes in memory. We got lucky with the first one, it stored the number we wanted in the right spot. But if we leave it how we have it right now this is the result we would get:
byte[1] = 192
byte[2] = 00
byte[3] = 70
byte[4] = 00

Which is out of order. The easiest way to fix this is with a calculator. Since we now know that the bytes are stored backwards all we have to do is find the equivalent of 70 backwards. To do this we convert 70 to the hex equivalent(easily done with windows calc. just change the view to scientific then type the number you want to convert click the hex option): h46. So, we have h00 h46. Flip to get h46 h00. back to calc starting in hex mode type "4600", then hit dec. h4600 = 17920.

So, back to our char array. if we want to set the third and fourth byte to 00 70, we do this:
lc_char[2] = char(17920)

Now were in business. A quick example of the last two bytes in the array:
what we want: 192 70
Convert to hex: C0 46
Reverse: 46 C0
Convert to dec: 18112

lc_char[3] = char(18112)

Fun stuff.

Saturday, September 20, 2008

Matrix Show


I made (attempted to make) a cool screen saver check it out here: Google Code