FFI, ODBC, and SqlDriverConnect

John R. Pierce john at pierce.name
Wed Mar 24 14:44:17 UTC 2004


Hi all,

I have been using the "ODBC for Squeak" package on SM to good success, but I
have been wanting to add DSN-less connection support to the package.  As it
turns out from the MSDN documentation you just need to invoke the
SQLDriverConnect API rather than the SQLConnect API.  So, I added this call as
an option to the ODBCConnection class (to open DSN-less connections if your DSN
contains the "Driver" keyword).

Anyhow -- it appears to be working, but I have a few questions (FFI related). 
First, here is the FFI wrapper definition I added to ODBCLibrary.  It looks like
this:

-----------------------------------------

sqlDriverConnect: connectionHandle hwnd: windowHandle connection:
connectionString connectionLength: connectionLengthInteger outConnection:
outConnectionString bufferLength: bufferLengthInteger outConnectionLengthPtr:
outConnectionLengthIntegerPtr driverCompletion: driverCompletionInteger 
	"SQLRETURN	SQLDriverConnect(
     SQLHDBC     ConnectionHandle,
     SQLHWND     WindowHandle,
     SQLCHAR *     InConnectionString,
     SQLSMALLINT     StringLength1,
     SQLCHAR *     OutConnectionString,
     SQLSMALLINT     BufferLength,
     SQLSMALLINT *     StringLength2Ptr,
     SQLUSMALLINT     DriverCompletion);"
	<cdecl: short 'SQLDriverConnect' (SQLHDBC Win32Handle char* short char* short
SQLShort short)>
	^ self externalCallFailed

--------------------------------------------------

(Yikes!  I don't miss those old C days)

I followed Diego's examples in making this FFI wrapper call.  What is
interesting is that this API call requires a couple of output parameters.  I
haven't seen a lot of examples doing that.  For some reason I think you can use
this API to connect with one connection string, but actually get another one in
return (in OutConnectionString).  I'm really lucky in this case because it turns
out -- I don't need this parameter so I just pass in nil in my calling code for
OutConnectionString and BufferLength.  

Does this seem safe or correct?  (The MS API documentation says the output
string should be at a minimum of 1024 preallocated string space but it seems to
work if I pass nil -- am I playing with fire here?).

Lastly, I have a question about the calling code.  Here it is (a message I added
to ODBCConnection):

--------------------------------------------------

ODBCConnection>>connectDsnLess

	| sqlDriverNoPrompt actualOutLengthIntegerPtr fullDsn |

	sqlDriverNoPrompt := 0.

	fullDsn := String streamContents:
		[:stream |
		stream 
			nextPutAll: dsn;
			nextPutAll: ';UID=';
			nextPutAll: user;
			nextPutAll: ';PWD=';
			nextPutAll: password].

	actualOutLengthIntegerPtr := SQLShort externalNew.	
	[self
		checkSQLReturn: (ODBCLibrary default
				sqlDriverConnect: handle
				hwnd: nil
				connection: fullDsn
				connectionLength: fullDsn size
				outConnection: nil
				bufferLength: 0
				outConnectionLengthPtr: actualOutLengthIntegerPtr
				driverCompletion: sqlDriverNoPrompt)] ensure: [actualOutLengthIntegerPtr
free]

--------------------------------------------------

In this message I have to pass in a pointer to a short for the API to tell me
how much space I should have allocated for OutConnection (if I didn't make it
big enough).  Again -- I don't really care about getting this OutConnection
parameter back -- but I have to allocate a short* anyways to get the call to
work.  So I allocated the short* by calling SQLShort externalNew (an
ExternalStructure already provided by Diego).  I suspect I have to clean up
after myself so I put the 'free' message send into an ensure block.  Did I do
this correctly?

All the code seems to function correctly, but I did get one spurious VM crash
(after much testing) so I just have a few doubts that I did something incorrect.
 I guess I am just looking for confirmation here -- but would be happy to learn
something about FFI if I am doing something dreadfully wrong.

Regards,

John

PS - The only other thing I did was modify ODBCConnection>>basicOpen to call
self connectDsnLess if the dsn instVar includes the substring 'driver={' rather
than calling the sqlConnect message on ODBCLibrary.  That change looks like this:

---- <snippet from basicOpen> ----

	(dsn asLowercase includesSubString: 'driver={')
		ifTrue: [self connectDsnLess]
		ifFalse:
			[self
				checkSQLReturn: (ODBCLibrary default "etc..."

---- <end snippet from basicOpen> ----



More information about the Squeak-dev mailing list