COM を学ぶ(5) : Mingw で MS-Access,SQL server に接続

小さい mdb ですけど 10000回連続読み込んでも、メモリ消費は増えませんでした。
参考にしたところ。

#include <stdio.h>
#include <malloc.h>
#include <windows.h>

/*  コンパイル
 *  gcc comtest.c -lole32 -loleaut32 -luuid -o comtest
 *
 *
 */

// COM は内部でBSTRを使用しています。
BSTR BSTRfromCstring(char* cstring ){
    int    cstringlen, out_size;
    BSTR   wstr;

    cstringlen = strlen(cstring);
    out_size   = MultiByteToWideChar(CP_ACP, 0, cstring, cstringlen, NULL, 0);
    wstr       = SysAllocStringLen(NULL, out_size);
    MultiByteToWideChar(CP_ACP, 0, cstring, cstringlen, wstr, out_size);
    return wstr;
}

char* CSTRfromBSTR(BSTR bstr){
    int    out_size;
    char   *cstring;

    out_size = WideCharToMultiByte(CP_ACP, 0, (OLECHAR*)bstr, -1, NULL, 0, NULL, NULL);
    cstring = (char*)malloc((out_size+1) * sizeof(char));
    WideCharToMultiByte(CP_ACP, 0, (OLECHAR*)bstr, -1, cstring, out_size, NULL, NULL);
    return cstring;
}
// VARIANTは大きな共用体です。
// http://marupeke296.com/IKDADV_CPP_VARIANT.html VARIANT型を知ってみる
long Variant2Long(VARIANT *pVariant){
    long num;

    if(V_ISBYREF(pVariant))
        num = (long)*V_I4REF(pVariant);
    else 
        num = (long)V_I4(pVariant);

    return num;
}
// COM が新しいオブジェクトのポインタを返したとき
IDispatch *Variant2Dispatch(VARIANT *pVariant){
    IDispatch *pDispatch;

    if (V_ISBYREF(pVariant))
        pDispatch = *V_DISPATCHREF(pVariant);
    else
        pDispatch = V_DISPATCH(pVariant);

    return pDispatch;
}
// 常にインタフェーステーブルへアクセスする。
// Open,Close などのコマンドからテーブルのディスパッチIDを求め
// 実行する。
HRESULT ComInvoke( PVOID *p, char *ComString ,VARIANT *param, USHORT wFlags, VARIANT *result){
    IDispatch   *pDisp;
    DISPID      dispID;
    HRESULT     hr;
    unsigned    short *ucPtr; 
    UINT        puArgErr = 0;
    EXCEPINFO   excepinfo;

    // DBアクセスは引数がないか、あっても1個。デフォルトは引数なし。
    // param->vt に VT_EMPTY が 設定されていれば cArgs を 1 にして
    // rgvarg にコマンド引数(VARIANT型)をセットします。
    // 参考:invoke メソッドの引数
    // http://homepage1.nifty.com/markey/ruby/win32ole/win32ole03.html#invoke-param
    DISPPARAMS  dispParams = { NULL, NULL, 0, 0 };
    if( param->vt != VT_EMPTY){
        dispParams.cArgs  = 1;
        dispParams.rgvarg = param;
    }

    memset( &excepinfo, 0, sizeof(EXCEPINFO));
    pDisp = *p;

    // コマンド文字列からディスパッチID取得
    ucPtr = BSTRfromCstring( ComString );
    pDisp->lpVtbl->GetIDsOfNames((IDispatch  *)pDisp, &IID_NULL, &ucPtr, 1, LOCALE_USER_DEFAULT, (DISPID*)&dispID);
    // ここが肝心のInvokeを実行する部分。
    VariantInit(result);
    hr = pDisp->lpVtbl->Invoke(
             pDisp,                    // 参考: Ruby付属の「OLE View」
             dispID,                   // arg1 - I4 dispidMember        [IN]
             &IID_NULL,                // arg2 - GUID riid              [IN]
             LOCALE_SYSTEM_DEFAULT,    // arg3 - UI4 lcid               [IN]
             wFlags,                   // arg4 - UI2 wFlags             [IN]
             &dispParams,              // arg5 - DISPPARAMS pdispparams [IN]
             result,                   // arg6 - VARIANT pvarResult     [OUT]
             &excepinfo,               // arg7 - EXCEPINFO pexcepinfo   [OUT]
             &puArgErr );              // arg8 - UINT puArgErr          [OUT]
     // printf("Invoke %-10s dispID:%4d hr:%08x puArgErr:%d\n",ComString, dispID, hr,puArgErr);
     SysFreeString(ucPtr);
     return hr;
}
// ProgID("ADODB.Connection")からCLSID({00000514-0000-0010-8000-00AA006D2EA4})
// を求め、CoCreateInstance APIを呼びます。
IDispatch *InstanceNew(char *ComName,HRESULT *hr){
    static IDispatch  *pDisp;
    BSTR    name;
    CLSID   clsid;

    name = BSTRfromCstring( ComName );

    *hr = CLSIDFromProgID(name, &clsid);
    // HRESULTは最上位ビットで OK ,NG を表現します。
    // FAILED は hr が 0 より小さいかどうかチェックするマクロ。
    if(FAILED(hr)) {
        *hr = CLSIDFromString(name, &clsid);
    }
    *hr = CoCreateInstance(&clsid, NULL, CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER, &IID_IDispatch, (void **)&pDisp);
    SysFreeString(name);
    return pDisp;
}
void ADODBopen(char *connect,PVOID *pDisp,HRESULT *hr){
    VARIANT    param, result;
    DISPID     dispID;

    VariantInit(&param);
    param.vt      = VT_BSTR;                  // 引数はBSTR
    param.bstrVal = BSTRfromCstring(connect); // 接続文字列をBSTRに変換して共用体に設定 
    *hr = ComInvoke((void **)pDisp, "Open", &param, DISPATCH_PROPERTYPUT | DISPATCH_METHOD, &result);
}

void ADODBclose(PVOID *pDisp,HRESULT *hr){
    VARIANT    param, result;
    DISPID     dispID;

    VariantInit(&param);
    param.vt      = VT_EMPTY; // 引数なし
    *hr = ComInvoke((void **)pDisp,"Close",&param, DISPATCH_PROPERTYPUT | DISPATCH_METHOD,&result);
}

IDispatch *ExecuteSql(PVOID *connDisp, char *SqlString, HRESULT *hr){
    static IDispatch  *resDisp;
    VARIANT    param, result;
    DISPID     dispID;

    VariantInit(&param);
    param.vt      = VT_BSTR;                    // 引数はBSTR
    param.bstrVal = BSTRfromCstring(SqlString); // SQL文字列をBSTRに変換して設定
    *hr           = ComInvoke((void **)connDisp, "Execute" , &param,
                               DISPATCH_PROPERTYPUT | DISPATCH_METHOD,&result);
    resDisp       = Variant2Dispatch(&result);
    return resDisp;
}
// rs.invoke("Fields") のように親オブジェクトから子オブジェクトを生成。
// Fields オブジェクトを取得するとカラム数、Item が取得できるようになる。
// Fields.invoke("Count")  #=> 5
// カラム名を指定して Item を指定するとカラムの値が取得できるようになる。
// Fields.invoke("Item","Day").invoke("Value")  #=> "1923/09/01 00:00:00"
IDispatch *CreateNewObject( PVOID *parentDisp, char *ObjName, HRESULT *hr){
    static IDispatch  *newDisp;
    VARIANT    param, result;
    DISPID     dispID;

    VariantInit(&param);
    param.vt = VT_EMPTY;
    *hr      = ComInvoke((void **)parentDisp, ObjName, &param, DISPATCH_PROPERTYGET | DISPATCH_METHOD,&result);
    newDisp  = Variant2Dispatch(&result);
    return newDisp;
}
// カラムの番号を指定して field オブジェクトを取得する。
// rs.invoke("Fields",1) #=> field
// field オブジェクトからはフィールド名、タイプなどが取得出来る。
// field.invoke("Name")  #=> "Day"
IDispatch *CreatefieldObject( PVOID *rsDisp, long col, HRESULT *hr){
    static IDispatch  *field;
    VARIANT    param, result;
    DISPID     dispID;

    VariantInit(&param);
    param.vt      = VT_I4;
    param.lVal    = col;
    *hr = ComInvoke((void **)rsDisp, "Fields", &param, DISPATCH_PROPERTYGET | DISPATCH_METHOD,&result);
    field         = Variant2Dispatch(&result);
    return field;
}

long getLongProperty(PVOID *Object, char *PropertyName, HRESULT *hr){
    static     long   num;
    VARIANT    param, result;
    DISPID     dispID;

    VariantInit(&param);
    param.vt      = VT_EMPTY;
    *hr = ComInvoke((void **)Object, PropertyName, &param, DISPATCH_PROPERTYGET | DISPATCH_METHOD,&result);
    num = Variant2Long(&result);
    return num;
}

char *getStringProperty(PVOID *Object, char *PropertyName ,HRESULT *hr){
    VARIANT    param, result;
    DISPID     dispID;

    VariantInit(&param);
    param.vt      = VT_EMPTY;
    *hr=ComInvoke((void **)Object, PropertyName, &param, DISPATCH_PROPERTYGET | DISPATCH_METHOD,&result);
    return CSTRfromBSTR(result.bstrVal);
}

int getBoolProperty(PVOID *Object, char *PropertyName ,HRESULT *hr){
    VARIANT    param, result;
    DISPID     dispID;

    VariantInit(&param);
    param.vt      = VT_EMPTY;
    *hr=ComInvoke((void **)Object, PropertyName, &param, DISPATCH_PROPERTYGET | DISPATCH_METHOD,&result);
    return (V_BOOL(&result) ? (-1) : 0);
}

char *Date2String(DATE date){
    char *buf;
    SYSTEMTIME st;

    VariantTimeToSystemTime(date, &st);
    buf = (char*)malloc(20 * sizeof(char));
    sprintf(buf,"%04d/%02d/%02d %02d:%02d:%02d",
                       st.wYear,st.wMonth,st.wDay,st.wHour,st.wMinute,st.wSecond);
    return buf;
}
char *Number2String(long num){
    char *buf;
    buf = (char*)malloc(30 * sizeof(char));
    sprintf(buf,"%d",num);
    return buf;
}
char *Double2String(double num){
    char *buf;
    buf = (char*)malloc(30 * sizeof(char));
    sprintf(buf,"%f",num);
    return buf;
}
char *Variant2String(VARIANT *result){
    switch(V_VT(result)){
        case VT_I2:  // short
            return Number2String((long)V_I2(result));
            break;
        case VT_I4:  // long
            return Number2String((long)V_I4(result));
            break;
        case VT_R4:  // float
            return Double2String(V_R4(result));
            break;
        case VT_R8:  // double
            return Double2String(V_R8(result));
            break;
        case VT_BOOL: //(True -1,False 0)
            return (V_BOOL(result) ? "True" : "False");
            break;
        case VT_BSTR:
            return CSTRfromBSTR(V_BSTR(result));
            break;
        case VT_DATE:
            return Date2String( V_DATE(result));
            break;
    }
}
// Ruby だとこんな感じ。"Name"は紛らわしいけれど"Name"というカラム名。
// puts Fields.invoke("Item","Name").invoke("Value")
// #=> "関東大震災"
char *readItem(PVOID *Fields, char *ColName, HRESULT *hr){
    IDispatch  *itemDisp;
    VARIANT    param, result;
    DISPID     dispID;

    VariantInit(&param);
    param.vt      = VT_BSTR;
    param.bstrVal = BSTRfromCstring(ColName);
    *hr=ComInvoke((void **)Fields, "Item"  ,  &param, DISPATCH_PROPERTYGET | DISPATCH_METHOD,&result);
    itemDisp      = Variant2Dispatch(&result);
    VariantClear(&param);
    param.vt      = VT_EMPTY;
    *hr=ComInvoke((void **)&itemDisp, "Value"  ,  &param, DISPATCH_PROPERTYGET | DISPATCH_METHOD, &result);
    itemDisp  ->lpVtbl->Release( (void*)itemDisp );
    return Variant2String(&result);
}
 
int getEofOrBof(PVOID *rs,HRESULT *hr){
    return (getBoolProperty((void **)rs, "EOF",hr) || 
            getBoolProperty((void **)rs, "BOF",hr));
}

void putProperty(PVOID *pDispatch, char *command,HRESULT *hr){
    VARIANT    param, result;
    DISPID     dispID;

    VariantInit(&param);
    param.vt      = VT_EMPTY;
    *hr = ComInvoke((void **)pDispatch,command,&param, DISPATCH_PROPERTYPUT | DISPATCH_METHOD,&result);
}

int main(int argc, char* argv[]){

    IDispatch  *conn, *rs, *Fields, *field, *itemDisp;
    DISPID     dispID;
    VARIANT    param, result;
    HRESULT    hr=0;
    BSTR       bstr;
    char       *cstr, *Name, *Day, *Magnitude, *NumOfDeaths,*YesNo,*DeadOrAlive;
    long       fCount, type;
    int i;
    //char  dsn[]  = {"Provider=SQLOLEDB.1;Password=hoge;User ID=user;Initial Catalog=DBname;Data Source=tcp:192.168.1.123,1433"};
    char       dsn[]  = {"DRIVER={Microsoft Access Driver (*.mdb)};Dbq=C:\\card\\COM\\C\\sample1.mdb"};
    char       sqlstring[]= {"SELECT * FROM earthquake;"};

    OleInitialize(NULL);
    conn   = InstanceNew("ADODB.Connection",&hr);
    ADODBopen( dsn,(void **)&conn,&hr);
    rs     = ExecuteSql( (void **)&conn, sqlstring, &hr);
    Fields = CreateNewObject( (void **)&rs,"Fields", &hr);
    fCount = getLongProperty( (void **)&Fields, "Count", &hr);
    // 全カラム名を表示
    for(i=0;i<fCount;i++){
        // rs.invoke("Fields",1) #=> fieldOBJct
        field  = CreatefieldObject( (void **)&rs, i, &hr);
        // field.invoke("Name")  #=> "Day"
        cstr   = getStringProperty( (void **)&field, "Name", &hr);
        printf("#=> %s\n", cstr);
        free(cstr);
        field->lpVtbl->Release( (void*)field);
    }
    // #=> Name #=> Day #=> Magnitude #=> NumOfDeaths #=> YesNo #=> DeadOrAlive
    for(;;){
        if (getEofOrBof( (void **)&rs, &hr)) break;
        Name        = readItem( (void **)&Fields, "Name",        &hr);
        Day         = readItem( (void **)&Fields, "Day",         &hr);
        Magnitude   = readItem( (void **)&Fields, "Magnitude",   &hr);
        NumOfDeaths = readItem( (void **)&Fields, "NumOfDeaths", &hr);
        YesNo       = readItem( (void **)&Fields, "YesNo",       &hr);
        DeadOrAlive = readItem( (void **)&Fields, "DeadOrAlive", &hr);
        //printf("%-20s %20s  %10s %10s %-5s %3s\n",
        //         Name,Day,Magnitude, NumOfDeaths,YesNo,DeadOrAlive);
        free(Name);
        free(Day);
        free(Magnitude);
        free(NumOfDeaths);
        free(YesNo);
        free(DeadOrAlive);
        putProperty((void **)&rs, "MoveNext", &hr);
    }
    // 関東大震災            1923/09/01 00:00:00    7.900000     142807 True   -1
    // 北海道東方沖地震      1994/10/04 00:00:00    8.100000          0 False   0
    // 阪神淡路大震災        1995/01/17 00:00:00    7.200000       6418 False  -1
    // 新潟県中越地震        2004/10/23 00:00:00    6.800000         37 False  -1

    ADODBclose((void **)&conn,&hr);

    conn  ->lpVtbl->Release( (void*)conn);
    rs    ->lpVtbl->Release( (void*)rs );
    Fields->lpVtbl->Release( (void*)Fields );

    OleUninitialize();
    return 0;
}