Archive: Need help with a dll call : ODBC


Need help with a dll call : ODBC
Hello,

I need to create an ODBC driver during the installation process. I want to use the SQLConfigDriver function from the odbccp32.dll file.

The function syntax is the following :

Summary

SQLConfigDriver loads the appropriate driver setup DLL and calls the ConfigDriver function.
Syntax

BOOL SQLConfigDriver (
HWND hwndParent,
WORD fRequest,
LPCSTR lpszDriver,
LPCSTR lpszArgs,
LPSTR lpszMsg,
WORD cbMsgMax,
WORD* pcbMsgOut);

Arguments

hwndParent [Input]

Parent window handle. The function will not display any dialog boxes if the handle is null.

fRequest [Input]

Type of request. fRequest must contain one of the following values:

ODBC_CONFIG_DRIVER: Changes the connection pooling timeout used by the driver.

ODBC_INSTALL_DRIVER: Installs a new driver.

ODBC_REMOVE_DRIVER: Removes an existing driver.

This option can also be driver-specific, in which case the fRequest for the first option must start from ODBC_CONFIG_DRIVER_MAX+1. The fRequest for any additional option must also start from a value greater than ODBC_CONFIG_DRIVER_MAX+1.

lpszDriver [Input]

The name of the driver as registered in the system information.

lpszArgs [Input]

A null-terminated string containing arguments for a driver-specific fRequest.

lpszMsg [Output]

A null-terminated string containing an output message from the driver setup.

cbMsgMax [Input]

Length of lpszMsg.

pcbMsgOut [Output]

Total number of bytes available to return in lpszMsg. If the number of bytes available to return is greater than or equal to cbMsgMax, the output message in lpszMsg is truncated to cbMsgMax minus the null-termination character. The pcbMsgOut argument can be a null pointer.
Returns

The function returns TRUE if it is successful, FALSE if it fails.
So I decided to create a macro to execute this function with the system.dll.

The code of the macro is the following :
!macro LIBODBC_CONFIG_ODBC_DRIVER
!ifdef LIBODBC_UNINST_SUPPORT
!undef UnODBCConfigDriver
!define UnODBCConfigDriver "!insertmacro LIBODBC_CONFIG_ODBC_DRIVER_UnCall"
Function un.ODBCConfigDriver
!else
!undef ODBCConfigDriver
!define ODBCConfigDriver "!insertmacro LIBODBC_CONFIG_ODBC_DRIVER_Call"
Function ODBCConfigDriver
!endif

; Saves variables, gets the function parameters
Exch $R0 ; input : LOGFILE
Exch 2
Exch $R1 ; input : ODBCDriverName
Exch
Exch $R2 ; input : Type of Request
Push $R3 ; output : TRUE/FALSE


!ifdef LIBODBC_UNINST_SUPPORT
${UnPrint} ""
${UnPrint} "--------------------------------------"
${UnPrint} "--------------------------------------"
${UnPrint} "-- ConfigAnODBCDriver"
${UnPrint} "ODBCDriverName : $R1"
${UnPrint} "Type of Request : $R2"
${UnPrint} "--------------------------------------"
!else
${Print} ""
${Print} "--------------------------------------"
${Print} "--------------------------------------"
${Print} "-- ConfigAnODBCDriver"
${Print} "ODBCDriverName : $R1"
${Print} "Type of Request : $R2"
${Print} "--------------------------------------"
!endif

${Select} "$R2"
${Case} "ODBC_INSTALL_DRIVER"
StrCpy $R2 1
${Case} "ODBC_REMOVE_DRIVER"
StrCpy $R2 2
${Case} "ODBC_CONFIG_DRIVER"
StrCpy $R2 3
${EndSelect}

SetPluginUnload alwaysoff

SetOutPath $PLUGINSDIR ; go to temporary directory
;File "$SYSDIR\odbccp32.dll" ; copy dll there
File "C:\WINDOWS\system32\odbccp32.dll"

dumpstate::debug
System::Call 'odbccp32.dll::SQLConfigDriver \
(i 0, i $R2, t "$R1", t "", t "", t "", i 0, i 0) i'
dumpstate::debug

SetPluginUnload manual ; let the installer unload the System dll
System::Free 0

;WriteIniStr "$R0" "ODBC.data" "ODBC_DRIVER_IS_CONFIGURED" "$R3"

!ifdef LIBODBC_UNINST_SUPPORT
${UnPrint} "ODBCDriverConfigured : $R3"
${UnPrint} "--------------------------------------"
!else
${Print} "ODBCDriverConfigured : $R3"
${Print} "--------------------------------------"
!endif

Pop $R3
Pop $R2
Pop $R1
Pop $R0
FunctionEnd
!macroend

!macro LIBODBC_CONFIG_ODBC_DRIVER_Call LOGFILE ODBCDRIVERNAME FREQUEST
!echo `$ {ODBCDriverExists} "${LOGFILE}" "${ODBCDRIVERNAME}" "${FREQUEST}"$\r$\n`
Push `${ODBCDRIVERNAME}`
Push `${FREQUEST}`
Push `${LOGFILE}`
Call ODBCConfigDriver
!macroend

!macro LIBODBC_CONFIG_ODBC_DRIVER_UnCall LOGFILE ODBCDRIVERNAME FREQUEST
!echo `$ {UnODBCDriverExists} "${LOGFILE}" "${ODBCDRIVERNAME} "${FREQUEST}""$\r$\n`
Push `${ODBCDRIVERNAME}`
Push `${FREQUEST}`
Push `${LOGFILE}`
Call un.ODBCConfigDriver
!macroend


And I'm calling it this way in the main section of the installer :
${ODBCConfigDriver} "$PLUGINSDIR\${UNINSTALL_INI_FILE}" \
"${ODBC_DRIVER_NAME}" "ODBC_INSTALL_DRIVER"


Mains problems are that I don't known how to get the output from the dll call (0 or 1) and that it doesn't seems to work ://.

I try to find documentation about the use of dll with system.dll but i don't find anything interresting (or helping me with my problem).

Thanks for your help,

Geoffrey

This will put the return value of the function into $0:

System::Call "dll::func() i .r0"

I've read the documentation and made the tutorial about System plugin and dll call and i've understand a bit more how it works.

But I'm still blocked with the pointer.

I've the following code :


Name "ODBC/System Plugin Example"
OutFile "ODBC.exe"
ShowInstDetails show

SetPluginUnload alwaysoff

Section "ODBCTest"

!define ODBC_INSTALL_DRIVER 1
!define ODBC_REMOVE_DRIVER 2
!define ODBC_CONFIG_DRIVER 3

SetOutPath $TEMP

; BOOL SQLConfigDriver (
; HWND hwndParent,
; WORD fRequest,
; LPCSTR lpszDriver,
; LPCSTR lpszArgs,
; LPSTR lpszMsg,
; WORD chMsgMax,
; WORD * pcbMsgOut);

System::Call 'odbccp32::SQLConfigDriver(i, i \
${ODBC_INSTALL_DRIVER}, t "DRIVER_TEST", t, \
t .r0, i 500, *i .r1) i .r2'
DetailPrint "Output message from the driver setup : $0"
DetailPrint "Available byte : $1"
DetailPrint "Succeeded or not : $2"

; RETCODE SQLInstallerError(
; WORD iError,
; DWORD* pfErrorCode,
; LPSTR lpszErrorMsg,
; WORD cbErrorMsgMax,
; WORD* pcbErrorMsg);
System::Call 'odbccp32::SQLInstallerError(i, i*i .r0, \
t .r1, i 500, i 500, *i .r2) i .r3'
DetailPrint "iError (Installer Error Code) : $0"
DetailPrint "pfErrorCode : $1"
DetailPrint "pcbErrorMsg : $2"
DetailPrint "Return code : $3"

SetPluginUnload manual
; do nothing
System::Free 0

SectionEnd


It try to create the DRIVER_TEST without any parameters.

It return 0 (FALSE) but I can't get any error message from the SQLInstallerError function. I also don't know why SQLConfigDriver doesn't display any Output message($0).

Finally, I've got a question.

If $0 is equal to something BEFORE the call, does the value be passed into the call (with r0) as an input value ?


SQLConfigDriver
SQLInstallerError

SQLInstallerError probably fails because you've used an invalid syntax. A parameter can't have two types (i*i). It's either a pointer or it's not. If the input for the pointer is not important, you can still use a dot, but you can't have the input parameter a non-pointer.

If you've used r0 in the input part of the parameter value, $0 will be passed as the input. If you've used a dot, nothing will be passed. The meaning of nothing depends on the parameter type, but it's usually zero which is also NULL.


I've correct the type of pfErrorCode but it still doesn't display anything.

When the output is a pointer, how is it possible to display where the pointer points to ?


When the output is a pointer, the real pointer that gets to the function points to a temporary buffer created by the System plug-in. Once the call returns, that buffer is converted to a string and copied into the output location you've specified.

In your call you have cbErrorMsgMax sent twice. You also pass zero to iError which seems to be the input to the error itself.


Thanks kichik, things are going to work :)

Now I achieve to get the error message, but one thing i don't understand. In the SQLInstallerError doc, they say about the iError parameters : "Error record number. Valid numbers are from 1 through 8". But the only value that worked for me is "1". What means these numbers ?


For info, here's the piece of code that have changed :


; BOOL SQLConfigDriver (
; HWND hwndParent,
; WORD fRequest,
; LPCSTR lpszDriver,
; LPCSTR lpszArgs,
; LPSTR lpszMsg,
; WORD chMsgMax,
; WORD* pcbMsgOut);
System::Call 'odbccp32::SQLConfigDriver(i , i \
${ODBC_INSTALL_DRIVER}, t "DRIVER_TEST", t, \
t .r0, i 500, *i .r1) i .r2'
DetailPrint "Output message from the driver setup : $0"
DetailPrint "Available byte : $1"
DetailPrint "Succeeded or not : $2"

; RETCODE SQLInstallerError(
; WORD iError,
; DWORD* pfErrorCode,
; LPSTR lpszErrorMsg,
; WORD cbErrorMsgMax,
; WORD* pcbErrorMsg);
System::Call 'odbccp32::SQLInstallerError(i 1, \
*i .r4, t .r5, i 500, *i .r6) i .r7'
DetailPrint "iError (Installer Error Code) : $4"
DetailPrint "pfErrorCode : $5"
DetailPrint "pcbErrorMsg : $6"
DetailPrint "Return code : $7"


About the memory allocation for the buffer, is the command System::Alloc useless ? (because in my case, i store a value in a buffer for which memory haven't been allocated for !)

I don't know what the value SQLInstallerError accepts mean. You'll have to search MSDN and find out.

System::Alloc is not useless. For example, there's no pointer to structure type. Structures can't be described in a string that can be put in a variable.


So System:Alloc is (only) used when your need a pointer to a structure type ?

I'm progressing, it seems that i need to call another function first (before SQLConfigDriver), it's SQLInstallDriverEx, that I called like that :


!define ODBC_INSTALL_INQUIRY 1
!define ODBC_INSTALL_COMPLETE 2

!define S "\0"
!define MY_DRIVER_NAME "ANTS TEST Driver"
!define MY_DRIVER_ATTRIBUTES "Driver='$SYSDIR\aodbc.dll'${S}Setup='$SYSDIR\aodbc.dll'${S}Antshome='C:\Databases\antsdb'${S}"

; BOOL SQLInstallDriverEx (
; LPCSTR lpszDriver,
; LPCSTR lpszPathIn,
; LPSTR lpszPathOut,
; WORD cbPathOutMax,
; WORD * pcbPathOut,
; LPDWORD lpdwUsageCount);
StrCpy $R0 "'${MY_DRIVER_NAME}${S}${MY_DRIVER_ATTRIBUTES}${S}'"
DetailPrint "lpszDriver : $R0"
StrCpy $R1 "$DESKTOP"
StrLen $R3 "$R1"
DetailPrint "lpsz length : $R3"
System::Call 'odbccp32::SQLInstallDriverEx(t R0,t R1, t .R2, i R3, i .R4, i ${ODBC_INSTALL_COMPLETE}, i .R5) i .R6'
DetailPrint "Previously installed place for the driver : $R2"
DetailPrint "Number of bytes : $R4"
DetailPrint "Driver usage count : $R5"
DetailPrint "Succeeded or not : $R6"


But now i've got an error telling me that there's an "invalid keyword-value pairs".

It comes from lpszDriver that must contains the driver description and a list of keyword-value pairs describing the driver each times separated by a null byte. Does the system plugin handle the null byte (i've used /0 as told in MSDN doc) ?

As far as I know, the System plug-in doesn't support the \0 syntax. You'll have to allocate a buffer for the string and fill it in manually with System::Copy. You can also allocate a structure with strings in it, but then you'll have to know the length of each string ahead. I guess a nice little function can be created to generate such strucutre creation string.

Some examples come up when you search for "system null" in the forum:

http://forums.winamp.com/showthread....ht=system+null
http://forums.winamp.com/showthread....ht=system+null


To be sure that i've understand well, what i have to do is to use a structure of X elements, where each element is the string before the "\0". So in my case my structure would have 4 string (type t) :
"ANTS TEST Driver"
"Driver=$SYSDIR\aodbc.dll"
"Setup=$SYSDIR\aodbc.dll"
"Antshome=C:\Databases\antsdb"
And what I must do is specified the length of each string ahead.

Like that
Systemm::Call '*(&t17 "ANTS TEST Driver", &t24 "Driver=$SYSDIR\aodbc.dll", ... )

One question, if I use $SYSDIR, the real length I have to specify is the length value of $SYSDIR (18 if SYSDIR=C:\Windows\system or 20 if SYSDIR=C:\Windows\system32) or the length of $SYSDIR (8) ?


Yes, that's that. Just don't forget the final null terminator (&i1 0 in those examples).

You'll have to use the length of the value of $SYSDIR, of course. You can use StrLen to calculate that.


I've try to reproduce the way to do of the regwritemutlisz script but couldn't achieve to make it work :

Here my new piece of code :


Name "ODBC/System Plugin Example"
OutFile "ODBC.exe"
ShowInstDetails show

SetPluginUnload alwaysoff

Var MY_DRIVER_NAME
Var MY_DRIVER_PARAM1
Var MY_DRIVER_PARAM2
Var MY_DRIVER_PARAM3

Section "ODBCTest"
SetOutPath $TEMP

!define ODBC_INSTALL_INQUIRY 1
!define ODBC_INSTALL_COMPLETE 2

!define SQLInstallDriver "odbccp32::SQLInstallDriverEx(t , t , t , i , i , i , i) i"
!define SQLInstallerError "odbccp32::SQLInstallerError(i, *i, t, i, *i) i"


StrCpy $MY_DRIVER_NAME "ANTS TEST Driver"
StrCpy $MY_DRIVER_PARAM1 "Driver=$SYSDIR\aodbc.dll"
StrCpy $MY_DRIVER_PARAM2 "Setup=$SYSDIR\aodbc.dll"
StrCpy $MY_DRIVER_PARAM3 "Antshome=C:\Databases\antsdb"


; BOOL SQLInstallDriverEx (
; LPCSTR lpszDriver,
; LPCSTR lpszPathIn,
; LPSTR lpszPathOut,
; WORD cbPathOutMax,
; WORD * pcbPathOut,
; LPDWORD lpdwUsageCount);

; create a buffer
System::Call "*(&t${NSIS_MAX_STRLEN}) i.r1"

; fill the buffer with our strings
StrCpy $2 $1 ; initial position

StrLen $9 "$MY_DRIVER_NAME" ; length of the driver name
IntOp $9 $9 + 1 ; plus NULL
System::Call "*$2(&t$9 '$MY_DRIVER_NAME')" ; place the string
IntOp $2 $2 + $9 ; advance to the next position

StrLen $9 "$MY_DRIVER_PARAM1" ; length of the first parameter
IntOp $9 $9 + 1 ; plus NULL
System::Call "*$2(&t$9 '$MY_DRIVER_PARAM1')" ; place the string
IntOp $2 $2 + $9 ; advance to the next position

StrLen $9 "$MY_DRIVER_PARAM2" ; length of the second parameter
IntOp $9 $9 + 1 ; plus NULL
System::Call "*$2(&t$9 '$MY_DRIVER_PARAM2')" ; place the string
IntOp $2 $2 + $9 ; advance to the next position

StrLen $9 "$MY_DRIVER_PARAM3" ; length of the second parameter
IntOp $9 $9 + 1 ; plus NULL
System::Call "*$2(&t$9 '$MY_DRIVER_PARAM3')" ; place the string
IntOp $2 $2 + $9 ; advance to the next position

System::Call "*$2(&t1 '')" ; place the terminating string
IntOp $2 $2 + 1 ; avdance to the last position

IntOp $2 $2 - $1 ; total length
; other parameters
StrCpy $R1 "$DESKTOP"
StrLen $R3 "$R1"
IntOp $R3 $R3 + 1

Dumpstate::debug
System::Call "${SQLInstallDriver} (r1, R1, .R2, R3, .R4, ${ODBC_INSTALL_COMPLETE}, .R5) .R6"
Dumpstate::debug
DetailPrint "Previously installed place for the driver : $R2"
DetailPrint "Number of bytes : $R4"
DetailPrint "Driver usage count : $R5"
DetailPrint "Succeeded or not : $R6"


; RETCODE SQLInstallerError(
; WORD iError,
; DWORD * pfErrorCode,
; LPSTR lpszErrorMsg,
; WORD cbErrorMsgMax,
; WORD * pcbErrorMsg);
System::Call "${SQLInstallerError}(1, .r4, .r5, 500, .r6) .r7"
DetailPrint "iError (Installer Error Code) : $4"
DetailPrint "pfErrorCode : $5"
DetailPrint "pcbErrorMsg : $6"
DetailPrint "Return code : $7"


SetPluginUnload manual
; do nothing
System::Free 0
SectionEnd


I can't figure out what i do wrong.

And I've got a second problem, maybe a NSIS bug. If I replace r1 by R0 (or r0), for exemple : System::Call "*(&t${NSIS_MAX_STRLEN}) i.R0", I've got a Windows crash (GPF) of odbc.exe.

Geo

While you've passed a pointer as the first parameter to SQLInstallDriverEx, you've defined the type as a string. This means a string congaing the address to your buffer is passed.

As for the GPF, I doubt it's a bug with the System plug-in. The System plug-in gives you a lot of freedom. This freedom can be easily used to generate a GPF. Your code is not polished yet, which is probably the cause to the GPF. If you have a simple piece of code demonstrating a bug, I'd love to see it, but this one seems to be a script problem.


Hi again,

I've try to do my best to get a piece of code demonstrating the bug.

I'm on WXP-SP2 and use NSIS 2.06 (i've not upgrade to the latest version because i'm in a production environement with absolutely no time to test for the moment).

EDIT : the gpf was due to bad instruction use in the nsis script, my fault...

I'm going to correct the type in my dll call and try it out.

Thx