Like I alluded to in the post Using win32 GUI from posix code using mixedmode. , there are more than one way to write mixed mode programs.

There are atleast two distinct approaches that we can take when we write code that transgresses both posix and win32 worlds.

1.       SUA code with inline calls to win32 functions

2.       SUA Application that links to a custom Win32 dll which exposes functions and data whose datatypes are consistent with that of SUA datatypes.

From a technical standpoint, both these approaches are the same.  Even in the second approach we still make calls to win32 functions within posix code; but the difference lies in the fact that in the second approach we make calls to custom written functions that act as wrappers to the win32 function/ code that we want to access from posix code. 

I’ll discuss the first approach in detail in this post. The second approach will have to wait for the next post J.

SUA Code with inline calls to win32 functions:

‘inline’ here doesn’t refer to inline functions , rather it refers to the practice of calling win32 functions from within  posix/sua code. In this paradigm, calls to Win32 functions are interspersed in the SUA Applications code. In other words calls to Win32 functions will be in the same source file as that of SUA code. This is an intuitive and straight forward approach and increases readability and enables easier debugging as developers need not develop additional libraries as is the case with the other approach.  Also not e that, this is the only approach possible when only static libraries of the Win32 library is available.

                If I were to break down this approach into simple steps, this is how it will look.

1.       Identify the function in the Win32 library that you want to call in SUA/POSIX code.

2.       Identify headers that need to be included to call the win32 call identified in step 1.

3.       Include the header in the SUA/POSIX code and make calls to the WIN32 function .

Note:  When some Windows hearders are included in a SUA/POSIX application, the application may not compile because of conflict in data type definitions. This happens because particular datatype/function prototypes/macros  may be defined in different fashion in the Windows header from that of SUA headers.  I’ll discuss at the end of this post how to identify and resolve header conflicts.

4.       If you have to use the data that is returned by the Win32 call in SUA/POSIX, make sure care is taken to covert data from the type that the Win32 call returned to the type that SUA application is expecting.

 

Lest look a sample code that was written following the above approach.  Below is a small SUA application that connects to SQL server, creates a table and fills the table with data.  

Function calls that start with SQL* are win32 calls that reside in the odbc32.dll which is a win32 dll.

#include <stdio.h>

#include "CommonHeader.h"

 

#define ROWS 3

#define NEWBALANCEVALUE 2000

#define TIMESTOSQLFETCHSCROLL 5

#define SQLTEXTLEN 1024

 

/* buffers for inserting values into the table */

#define INPUTCOUNT 9

int accno_array[]={1,2,3,4,5,6,7,8,9};

float bal_array[]={1000,2000,3000,4000,5000,6001,7000,800,9000};

 

 

SQLRETURN  CreateAndFillTables(SQLHENV henv,SQLHDBC hdbc)

{    

      SQLRETURN      retcode;

      SQLINTEGER     ACCOUNT_IDArray[ROWS];

      SQLINTEGER     ACCOUNT_IDIndArray[ROWS];

      SQLINTEGER     BalanceArray[ROWS];

      SQLINTEGER     BalanceIndArray[ROWS];

      SQLUSMALLINT   RowStatusArray[ROWS], RowNum;

      SQLHSTMT       hstmt;

      SQLCHAR              cursorName[NAME_LEN];

      SQLINTEGER     cursorLen;

      SQLINTEGER     NewBalance=NEWBALANCEVALUE;

      SQLINTEGER     NewBalanceInd;

      char SQL_Text[SQLTEXTLEN];

      int CursorTypeValue = 0;

      int i=0,k=0;

 

      if(SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt)!=0)

      {

            printf("Error In Setting The Connection Handler\n");

            return MYFAIL;

      }

      retcode=SQLSetStmtAttr(hstmt, (SQLINTEGER)SQL_ATTR_CONCURRENCY, (SQLPOINTER)SQL_CONCUR_VALUES,0);

      if(retcode== SQL_ERROR)

      {

            return MYFAIL;

      }

      else if(retcode==SQL_SUCCESS_WITH_INFO)

      {

            printf("Required cursor not set, Cursor type set is %d, but wanted %d \n\n",CursorTypeValue,SQL_CONCUR_VALUES);

      }

      retcode=SQLSetStmtAttr(hstmt, SQL_ATTR_CURSOR_TYPE,(SQLPOINTER)SQL_CURSOR_KEYSET_DRIVEN,0);// , 0);

      if(retcode== SQL_ERROR)

      {

            return MYFAIL;

      }

      else if(retcode==SQL_SUCCESS_WITH_INFO)

      {

            printf("Required cursor not set, Cursor type set is %d, but wanted %d\n\n",CursorTypeValue,SQL_CURSOR_KEYSET_DRIVEN);

      }

      if(SQLSetStmtAttr(hstmt, SQL_ATTR_ROW_BIND_TYPE, SQL_BIND_BY_COLUMN, 0)!=0)

      {

            return MYFAIL;

      }

      if(SQLSetStmtAttr(hstmt, SQL_ATTR_ROW_ARRAY_SIZE, (SQLPOINTER)ROWS, 0)!=0)

      {

            return MYFAIL;

      }

      if(SQLSetStmtAttr(hstmt, SQL_ATTR_ROW_STATUS_PTR, RowStatusArray, 0)!=0)

      {

            return MYFAIL;

      }

 

      if(SQLSetCursorName(hstmt, "MY_CURSOR", SQL_NTS))

      {

            return MYFAIL;

      }

 

      // Bind arrays to the ACCOUNT_IDArray

      if(SQLBindCol(hstmt, 1, SQL_C_SLONG, ACCOUNT_IDArray, 0, ACCOUNT_IDIndArray)!=0)

      {

            return MYFAIL;

      }

 

     

      if(SQLBindCol(hstmt, 2, SQL_C_SLONG, BalanceArray, 0, BalanceIndArray)!=0)

      {

            return MYFAIL;

      }

 

      // delete the existing table

      SQLExecDirect(hstmt, "drop table accounts", SQL_NTS);// ignore errors if any

 

      /* create the table accounts */

      if(SQLExecDirect(hstmt, "create table accounts(account_id  int not null unique,bal decimal(11,2))", SQL_NTS)!=0)

      {

            return MYFAIL;

      }

 

      /* insert the values in the table*/

      for(i=0;i<INPUTCOUNT;i++)

      {

            sprintf(SQL_Text,"insert into accounts values (%d,%f)",accno_array[i],bal_array[i]);

            if(SQLExecDirect(hstmt, SQL_Text, SQL_NTS)!=0)

            {

                  return MYFAIL;

            }

      }

 

 

      // Execute a statement to retrieve rows from the Customers table.

      if(SQLExecDirect(hstmt, "select * from ACCOUNTS", SQL_NTS)!=0)

      {

            return MYFAIL;

      }

 

 

      // Fetch and display the rows, from the table

      while(1)

      {

            if(SQLFetchScroll(hstmt, SQL_FETCH_NEXT, 0))

                  break;

            for(k=0;k<ROWS;k++)

            {

                  printf("ACCOUNT_ID: %d Balance: %d \n",ACCOUNT_IDArray[k],BalanceArray[k]);

            }

      }

 

      SQLCloseCursor(hstmt);

      return MYPASS;

FAILED:

      SQLCloseCursor(hstmt);

      return MYFAIL;

}

 

 

int main(int argc, char **argv)

{

      char dsn[256]      = "ODBC-TEST";

      char user[256]     = "sa";

      char passwd[256] = "111_aaa";

      SQLHENV henv;

      SQLHDBC hdbc;

 

      if (argc > 1)

      {

            strcpy(dsn, argv[1]);

      }

      if(argc > 2)

      {

            strcpy(user, argv[2]);

      }

      if(argc > 3)

      {

            strcpy(passwd, argv[3]);

      }

 

      if(MYFAIL == ConnectToDB(dsn, user, passwd, &henv, &hdbc))

      {

            printf("Could not establish connection\n");

            return 1;

      }

 

      CreateAndFillTables(henv, hdbc);

 

      return 0;

     

}

 

 

You would see lots of dataypes alien to posix used here. These are types that are defined in sql headers like sql.h, sqltypes.h etc. 

Below is the code in commonheader.h. I’ve included this so that if you want to try building this application, you have all the sources.

#include <interix/mixedmode.h>

 

#include <stdio.h>

#include <odbc/sql.h>

#include <odbc/sqlext.h>

#include <odbc/sqltypes.h>

#include <odbc/odbcss.h>

#include <stdlib.h>

#include <odbc/odbcinst.h>

#include <errno.h>

#define NAME_LEN 50

 

#define MYFAIL -1

#define MYPASS 0

 

/* displays the last error message */

void ShowError(SQLHSTMT hstmt,SQLSMALLINT HandleType)

{

      SQLCHAR       SqlState[6], SQLStmt[100], Msg[SQL_MAX_MESSAGE_LENGTH];

      SQLINTEGER    NativeError;

      SQLSMALLINT   i, MsgLen;

      SQLRETURN     rc1, rc2;

      i = 1;

      while ((rc2 = SQLGetDiagRec(HandleType, hstmt, i, SqlState, &NativeError,Msg, sizeof(Msg), &MsgLen)) != SQL_NO_DATA)

      {

            printf("SqlState %s,NativeError %d ,Msg %s \n",SqlState,NativeError,Msg);

            i++;

      }

}

/* establishes the connection with the DB */

int ConnectToDB(char *DSN,char * UserId ,char *passwd,SQLHENV *henv,SQLHDBC *hdbc)

{

      SQLRETURN      retcode;

 

      if(SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, henv))

      {

            printf("SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv) failed\n");

            return MYFAIL;

      }

      if(SQLSetEnvAttr(*henv, SQL_ATTR_ODBC_VERSION, (SQLPOINTER)SQL_OV_ODBC3,0))

      {

            printf("SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION, (SQLPOINTER)SQL_OV_ODBC3,0) failed\n");

            return MYFAIL;

      }

      if(SQLAllocHandle(SQL_HANDLE_DBC, *henv, hdbc))

      {

            printf("SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc) failed\n");

            return MYFAIL;

      }

 

 

      if((retcode =SQLSetConnectAttr(*hdbc, (SQLINTEGER)SQL_ATTR_ODBC_CURSORS,(SQLPOINTER)SQL_CUR_USE_IF_NEEDED,0))!=SQL_SUCCESS)//SQL_CUR_USE_ODBC

      {

            printf("Error In Using Cursor Library\n");

            return MYFAIL;

      }

      //Setting Connection Attribute

      if((retcode=SQLSetConnectAttr(*hdbc,(SQLINTEGER)SQL_LOGIN_TIMEOUT, (SQLPOINTER  )5, 0))!=SQL_SUCCESS)

      {

            printf( "Error In Setting The Connection Handler\n");

            return MYFAIL;

      }

      //Connecting to SQL-SERVER

      if((retcode =SQLConnect(*hdbc,(SQLCHAR *)DSN,SQL_NTS, (SQLCHAR *) UserId,SQL_NTS,(SQLCHAR *) passwd,SQL_NTS ))!=SQL_SUCCESS_WITH_INFO)

      {

            printf( "Error In Connecting To The SQLSERVER \n");

            return MYFAIL;

      }

      return MYPASS;

 

}

void CloseConnection(SQLHENV henv,SQLHDBC hdbc)

{

      //Free the Connection Handle                                                                               

      if( hdbc)

            SQLFreeHandle( SQL_HANDLE_DBC, hdbc );   

      //Free the Environment  Handle

      if( henv)

            SQLFreeHandle( SQL_HANDLE_ENV, henv );

}

 

 

The help files that come with ‘Utilities and SDK for SUA’ has detailed help on how to build applications that access SQL and Oracle databases. You can find that in the section ‘Developing database connectivity applications’.  I strongly recommend you to read this if you want to develop database applications that talk to either SQL server or Oracle.

 

Header Conflicts:

                A hurdle that one will have to overcome when following the above approach is header conflicts. ‘Header conflicts’ are a result of conflicting type definitions in SUA headers and Windows headers. For example, ULONG is defined in mixedmode.h a SUA/POSIX header. When mixedmode.h and a windows header that defines ULONG is included in a SUA/POSIX application, then a header conflict results. Header conflicts can be identified by looking at the compile error. 

Below are sample header conflict errors

With CC/C89

 

c:\winnt\sua\usr\include\odbc\sqltypes.h(122) : warning C4142: benign redefinition of type

 

With GCC

In file included from /usr/include/odbc/sql.h:25,

                 from CommonHeader.h:4,

                 from sample.c:2:

/usr/include/odbc/sqltypes.h:122: error: conflicting types for `ULONG'

/usr/include/interix/mixedmode.h:33: error: previous declaration of `ULONG'

 

 

How to resolve header conflicts?

1.       Construct a new header file with the types required by the Win32 function and include that instead of the standard windows header. Often, a type that is not used results in header conflicts.

2.        Change the Win32 header to resolve header conflicts.  While changing the header it is advisable to make a copy if the headers and change the copy.  This method of resolving header conflicts is advisable when the win32 header that resulted in type re declaration error is the first level header included in SUA/POSIX code and the number of re declaration error is limited to few type conflicts.

 

I strongly recommend this if you are developing sql applications – the help file also suggests this.

 

3.       Use a different programming approach –  by segregating the scope of data types of SUA and Windows, thereby removing any possibility of conflict.  

 

This post should have given you a brief idea of one of the approaches in mixed-mode programming, In the next post, I’ll discuss the second approach.