C++ を Haskell から呼ぶのが大変だというのが分ったので、Ruby の win32ole.c を参考にC言語で Excel を操作する関数を書いてみました。
これらの関数を Haskell から呼べば HaskellでExcel を操作出来ます(^^)/
- C++ ではシートから行・列を指定してCellsオブジェクトを取得しましたが、C で同じ方法だと取得に成功はするものの文字化け。 シートからRengeとセル名 "C2" を指定してDISPID_PROPERTYGET を指定して invoke 関数を呼びました。
- DISPID_PROPERTYPUT のときは渡すパラメータが別に必要のようです。
- VARIANT の配列を作って渡す方法が分らなくて泣きそうだった・・・。プログラマー's研究所/研究日誌 COMの呼び方に感謝です。
- "Renge" "C2" を指定するのは win32ole のサンプルで知りました。
/* * gcc exltest.c -lole32 -loleaut32 -luuid -o exltest */ #include <stdio.h> #include <malloc.h> #include <windows.h> // 参考 ruby.h #define ALLOCA_N(type,n) (type*)alloca(sizeof(type)*(n)) IDispatch *mallocDispatch(){ return (struct IDispatch *)malloc( sizeof(struct IDispatch) ); } // 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; } 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 ,VARIANTARG *param, int nArgs, USHORT wFlags, VARIANT *result){ IDispatch *pDisp; DISPID dispID; HRESULT hr; unsigned short *ucPtr; UINT puArgErr = 0; EXCEPINFO excepinfo; // http://msdn.microsoft.com/ja-jp/library/x6828bcx%28v=VS.80%29.aspx // Win32OLE 製作過程の雑記 : invoke メソッドの引数 // http://homepage1.nifty.com/markey/ruby/win32ole/win32ole03.html#invoke-param DISPPARAMS dispParams = { NULL, NULL, 0, 0 }; dispParams.rgvarg = param; // 引数の配列への参照を表します。 dispParams.rgdispidNamedArgs = NULL; // 名前付き引数の dispID の配列(未使用) dispParams.cArgs = nArgs; // 引数の数を表します。 dispParams.cNamedArgs = 0; // 名前付き引数の数 (未使用) // 参考:ruby win32ole.c ole_invoke2 関数 if (wFlags & DISPATCH_PROPERTYPUT) { dispParams.cNamedArgs = 1; dispParams.rgdispidNamedArgs = ALLOCA_N( DISPID, 1 ); dispParams.rgdispidNamedArgs[0] = DISPID_PROPERTYPUT; } memset( &excepinfo, 0, sizeof(EXCEPINFO)); pDisp = (IDispatch *)p; // コマンド文字列からディスパッチID取得 ucPtr = BSTRfromCstring( ComString ); hr=pDisp->lpVtbl->GetIDsOfNames((IDispatch *)pDisp, &IID_NULL, &ucPtr, 1, LOCALE_USER_DEFAULT, (DISPID*)&dispID); //printf("GetIDsOfNames nArgs:%d %-10s = %04d hr:%08lx\n", nArgs , ComString, dispID, hr); // ここが肝心の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("Excel.Application")からCLSID({00024500-0000-0000-C000000000000046}) // を求め、CoCreateInstance APIを呼びます。 IDispatch *InstanceNew(char *ComName){ IDispatch *pDisp; BSTR name; CLSID clsid; HRESULT hr=0; pDisp = mallocDispatch(); 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; } IDispatch *CreateNewObject( PVOID *parentDisp, char *ObjName){ VARIANT param, result; DISPID dispID; HRESULT hr = 0; VariantInit(¶m); VariantInit(&result); param.vt = VT_EMPTY; hr = ComInvoke((void **)parentDisp, ObjName, ¶m, 0, DISPATCH_PROPERTYGET | DISPATCH_METHOD,&result); // printf("CreateNewObject ObjName:%-14s hr:%08lx\n",ObjName,hr); VariantClear(¶m); return Variant2Dispatch(&result); } 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; } } // ver = ReadProperty((void **)pExl, "Version"); char *ReadProperty(PVOID *cell, char *PropertyName){ VARIANT param, result; VariantInit(¶m); param.vt = VT_EMPTY; ComInvoke((void **)cell, PropertyName,¶m, 0, DISPATCH_PROPERTYGET | DISPATCH_METHOD, &result); SysFreeString(param.bstrVal); return Variant2String(&result); } // hr = PutProperty((void **)cell, "Value","ほげ"); HRESULT PutProperty(PVOID *cell, char *PropertyName, char *String){ VARIANT result; VARIANTARG param[1]; BSTR bstr; HRESULT hr = 0; bstr = BSTRfromCstring(String); VariantInit(¶m[0]); param[0].vt = VT_BSTR|VT_BYREF; param[0].pbstrVal = &bstr; hr = ComInvoke((void **)cell, PropertyName, param, 1, DISPATCH_PROPERTYPUT, &result); VariantClear(&result); VariantClear(¶m[0]); return hr; } // GetAbsolutePathName メソッドをコールし、パス名を含めたファイル名を取得 char *GetPathName(IDispatch *fDisp, char *fileName){ VARIANT param, result; HRESULT hr = 0; char *name, *fullPathName; VariantInit(¶m); param.vt = VT_BSTR; param.bstrVal = BSTRfromCstring(fileName); hr = ComInvoke((void **)fDisp, "GetAbsolutePathName", ¶m, 1, DISPATCH_METHOD, &result); name = CSTRfromBSTR(result.bstrVal); fullPathName = malloc(strlen(name)+1 ); strcpy(fullPathName, name); free(name); VariantClear(¶m); VariantClear(&result); return fullPathName; } // Scripting.FileSystemObject を作りパス名を含めたファイル名を取得 // in : fileName // out : fullPathName char *getFullPathName(char *fileName){ IDispatch *fileSystemObj; fileSystemObj = InstanceNew("Scripting.FileSystemObject"); return GetPathName(fileSystemObj, fileName); } HRESULT WorkBooksOpen(PVOID *workBooks, char *fullPathName){ VARIANT result; VARIANTARG param[1]; BSTR bstr; HRESULT hr = 0; //http://www.hcn.zaq.ne.jp/no-ji/reseach/990905.htm // プログラマー's研究所/研究日誌 [COM] COMの呼び方 bstr = BSTRfromCstring(fullPathName); VariantInit(¶m[0]); param[0].vt = VT_BSTR|VT_BYREF; param[0].pbstrVal = &bstr; hr = ComInvoke((void **)workBooks, "Open", param, 1, DISPATCH_METHOD, &result); VariantClear(&result); return hr; } IDispatch *GetSheetObject(PVOID *workSheets, long n){ VARIANT result; VARIANTARG param[1]; HRESULT hr = 0; VariantInit(¶m[0]); param[0].vt = VT_I4; param[0].lVal = n; ComInvoke((void **)workSheets, "Item", param, 1, DISPATCH_PROPERTYGET , &result); return Variant2Dispatch(&result); } // cell = getCellObject((void **)sheet, "C2"); IDispatch *GetCellObject(PVOID *sheet, char *cells){ VARIANT result; VARIANTARG param[1]; BSTR bstr; HRESULT hr = 0; bstr = BSTRfromCstring(cells); VariantInit(¶m[0]); param[0].vt = VT_BSTR|VT_BYREF; param[0].pbstrVal = &bstr; ComInvoke((void **)sheet, "Range", param, 1, DISPATCH_PROPERTYGET , &result); return Variant2Dispatch(&result); } // call DispatchMethod((void **)workBooks, "Close"); // call DispatchMethod((void **)pExl, "Quit"); HRESULT DispatchMethod(PVOID *pDisp, char *command){ VARIANT param, result; HRESULT hr = 0; VariantInit(¶m); param.vt = VT_EMPTY; hr = ComInvoke((void **)pDisp, command, ¶m, 0, DISPATCH_METHOD,&result); VariantClear(¶m); VariantClear(&result); return hr; } void ReleaseObject( PVOID *pDisp ){ ((IDispatch *)pDisp)->lpVtbl->Release( (void *)pDisp); } int main(int argc, char* argv[]){ IDispatch *pExl, *workBooks, *ActiveWBook, *workSheets, *sheet, *cell; char *ver, *celldat; HRESULT hr = 0; OleInitialize(0); pExl = InstanceNew("Excel.Application"); ver = ReadProperty((void **)pExl, "Version"); printf("Version:%s\n",ver); //=> Version:9.0 workBooks = CreateNewObject((void **)pExl, "Workbooks"); hr = WorkBooksOpen( (void **)workBooks, getFullPathName("sample2.xls")); ActiveWBook = CreateNewObject((void **)pExl, "ActiveWorkbook"); workSheets = CreateNewObject((void **)ActiveWBook, "Worksheets"); sheet = GetSheetObject( (void **)workSheets, 2); // 2 番目のシート cell = GetCellObject( (void **)sheet, "C2"); // C2 セル hr = PutProperty((void **)cell, "Value","ほげ"); celldat = ReadProperty( (void **)cell, "Value"); printf("celldat:%s\n",celldat); //=> ほげ DispatchMethod((void **)ActiveWBook, "Save"); DispatchMethod((void **)workBooks, "Close"); DispatchMethod((void **)pExl, "Quit"); ReleaseObject((void **)cell); ReleaseObject((void **)sheet); ReleaseObject((void **)workSheets); ReleaseObject((void **)ActiveWBook); ReleaseObject((void **)workBooks); ReleaseObject((void **)pExl); OleUninitialize(); return 0; }