COM プログラミング2 - ファイルのアップロード

HTMLからのファイルのアップロードをサポートするCOMプログラムを作成します。 次のメソッドを実装してみましょう。
●SaveAs ....... ファイルを保存
●FormData ..... フォームのTEXTなどのタグを返す
●FileName ..... ファイル名を返す
1999/04/12更新。
=================== fup.html ================
<HTML><BODY>
<FORM ACTION="fup.asp"  ENCTYPE="multipart/form-data" METHOD=POST>
氏名: <INPUT TYPE=TEXT NAME="yourname"><BR>
ファイル1: <INPUT TYPE=file NAME="file1"><BR>
ファイル2: <INPUT TYPE=file NAME="file2"><BR>
<INPUT TYPE=SUBMIT NAME=UPLOAD>
</FORM></BODY></HTML>
=================== fup.html end ============

=================== fup.asp =================
<%
set obj=server.createobject("fileup")
name=obj.FormData("yourname")
f1=obj.FileName("file1")
newf1="d:\temp\fup" & Mid(f1,InstrRev(f1,"\"))
fsize1=obj.SaveAs("file1",newf1)
%>
<HTML><HEAD><TITLE>File Upload Test</TITLE>
<BODY>
<H1>Testing</H1>
<BR>
<%= name %>さん、<%= newf1 %>にアップロードされました<BR>
ファイルサイズ= <%= fsize1 %><BR>
=================== fup.asp end =============
ここでは次のことが学習できます。

ATL COM AppWizardプロジェクトの作成

[ファイル]-[新規作成]で[プロジェクト]ページの[ATL COM AppWizard]を 選択して[OK]を押します。次をチェックして[終了]を押します。

ATLオブジェクトの新規作成

[挿入]-[ATLオブジェクトの新規作成]で[ATLオブジェクト ウィザード]ダイアログを 開きます。

クライアントからデータを受取るのにIRequestインタフェースを使うので、 [ActiveXサーバコンポーネント]をクリックして選択して、[次へ]を押します。

[ATL Object Wizardのプロパティ]ダイアログが開きます。
[名前]ページの[C++ ショート]に名前を入れると自動的に すべてのテキストボックスにデフォルトの名前が入ります。
.H ファイルと.CPPファイルの名前とProg IDの名前を変更します。

[Prog ID]が外部からこのコンポーネントをアクセスするときに指定する名前となります。 ASPのVBScriptのServer.CreateObjectメソッドでオブジェクトを作成するときに使います。 この例では、Set obj = Server.CreateObject("FileUp")になります。
[属性]ページでは次をチェックします。


[ASP]ページでは次をチェックします。要求とはASPのIRequestオブジェクトのことです。


インタフェースにメソッドを追加

クラスビューの中のインタフェース(ここでは IFileUp )を右クリックして [メソッドの追加]を選択します。

SaveAsメソッドを次のように指定します。 [in] BSTR name,[in] BSTR fpath,[out,retval] long *pVal



同じように次のメソッドも追加します。
FormData [in] BSTR name,[out,retval] BSTR *pVal
FileName [in] BSTR fname,[out,retval] BSTR *pVal

メソッドのインプリメント

1. ソースコードをダウンロード fileup.lzh
解凍した次のファイルをプロジェクトのディレクトリにコピーします。
fileupx.cpp (置換します)
fileupx.h  (置換します)
brfc1867.h
brfc1867.cpp
その他すべてのプロジェクトファイルが含まれてますがコピーするのは、
上の4つのファイルだけにします。

=========================================================================
すぐ試したい人や手順どおりやってもうまくいかない場合は、
解凍した全てのファイルをfileupディレクトリにコピーして
fileup.dswをクリックすればプロジェクトが開きますのですぐビルドできます。
但し、このfileup.dswファイルはVisual C++ 6.0対応です。
Visual C++ 5.0ではビルドできません。
=========================================================================


2. プロジェクトにファイルを追加
[プロジェクト]-[プロジェクトに追加]-[ファイル]で追加します。 brfc1867.h brfc1867.cpp 3. Fileupx.cppファイル
FileUpx.cppは、次のようになります。 // FileUpx.cpp : CFileUp のインプリメンテーション #include "stdafx.h" #include "FileUp.h" #include "FileUpx.h" ///////////////////////////////////////////////////////////////////////////// // CFileUp STDMETHODIMP CFileUp::OnStartPage (IUnknown* pUnk) { if(!pUnk) return E_POINTER; CComPtr<IScriptingContext> spContext; HRESULT hr; // IScriptingContext インターフェイスの取得 hr = pUnk->QueryInterface(IID_IScriptingContext, (void **)&spContext); if(FAILED(hr)) return hr; // Request オブジェクトのポインタを取得 hr = spContext->get_Request(&m_piRequest); if(FAILED(hr)) { spContext.Release(); return hr; } // アップロードデータをRequest.BinaryReadで保存 m_rfc1867.Open(m_piRequest); m_bOnStartPageCalled = TRUE; return S_OK; } STDMETHODIMP CFileUp::OnEndPage () { m_rfc1867.Close(); // バッファを開放 m_bOnStartPageCalled = FALSE; // すべてのインターフェイスを解放します m_piRequest.Release(); return S_OK; } STDMETHODIMP CFileUp::SaveAs(BSTR name, BSTR fpath, long *pVal) { USES_CONVERSION; LPCSTR szName = OLE2T(name); LPCSTR szFileName = OLE2T(fpath); int flen; char *tf = m_rfc1867.FileData(szName,&flen); if(tf == NULL || flen < 1) { *pVal = -2; return S_OK; } HANDLE hFile = CreateFile((const char*)szFileName, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if(hFile == INVALID_HANDLE_VALUE) { *pVal = -3; return S_OK; } DWORD dwAmt; if(!WriteFile(hFile, tf, flen, &dwAmt, NULL)) { CloseHandle(hFile); *pVal = -4; return S_OK; } CloseHandle(hFile); *pVal = dwAmt; return S_OK; } STDMETHODIMP CFileUp::FormData(BSTR fname, BSTR *pVal) { USES_CONVERSION; LPCSTR szName = OLE2T(fname); int dlen; char *name = m_rfc1867.FormData(szName,&dlen); if(name == NULL) { *pVal = A2BSTR(""); return S_OK; } int mlen = dlen + 4096; char *tp = new char[mlen]; if (tp == NULL) { delete [] name; return ResultFromScode(E_OUTOFMEMORY); } memcpy(tp,name,dlen); tp[dlen] = 0; int clen = dlen; delete [] name; name = m_rfc1867.FormData(szName,&dlen); while (name) { tp[clen++] = '\t'; if (mlen < (clen + dlen + 1)) { char *tp2 = new char[mlen+dlen + 4096]; if (tp2 == NULL) { delete [] tp; delete [] name; return ResultFromScode(E_OUTOFMEMORY); } memcpy(tp2,tp,clen); delete [] tp; tp = tp2; mlen += dlen + 4096; } memcpy(tp+clen,name,dlen); clen += dlen; tp[clen] = 0; delete [] name; name = m_rfc1867.FormData(szName,&dlen); } *pVal = A2BSTR(tp); delete [] tp; return S_OK; } STDMETHODIMP CFileUp::FileName(BSTR fname, BSTR *pVal) { USES_CONVERSION; LPCSTR szName = OLE2T(fname); char *name = m_rfc1867.FileName(szName); if(name == NULL) { *pVal = A2BSTR(""); return S_OK; } *pVal = A2BSTR(name); delete [] name; return S_OK; }

デバッグの方法

1. デバッグモードでビルド
2. [ビルド] メニュー [デバッグの開始] の [プロセスへアタッチ] を選択
3. [プロセスへアタッチ」ウィンドウで [システム プロセスを表示] をチェックすると、 inetinfo.exeが表示されるので、選択して OK ボタン
4. [プロジェクト]-[設定]で[デバッグ]-[カテゴリ]-[追加するDLL]で debugディレクトリのFileup.dllを選択。 これを忘れるとブレークポイントが設定できない。
5. [ファイル] メニューの [開く] を選択し、Fileupx.cppソースファイルを開く
6. ソースファイルにブレークポイント(F9を押す)を設定
7. ブラウザからFileUpコンポーネントを呼び出す.aspを表示
8. Good luck!

Release Mindependencyでのビルド

配布のために[ビルド]-[アクティブな構成の設定]でRelease Mindependencyを選択してビルドします。 リンク時に次のエラーが発生します。

LIBCMT.lib(crt0.obj) : error LNK2001: 外部シンボル "_main" は未解決です
ReleaseMinDependency/FileUp.dll : fatal error LNK1120: 外部参照 1 が未解決です。
link.exe の実行エラー

FileUp.dll - エラー 2、警告 0

このエラーは、Cランタイム関数がリンクされていないときに発生します。
Cのランタイム関数をプロジェクト内で使っていると発生します。
ReleaseビルドのデフォルトではATL COM AppWizardは、Cランタイムをリンクしません。
これは、_ATL_MIN_CRTプリプロセッサ文字が指定されているためです。
対策としては:
●[プロジェクトの設定] -[C/C++]-[プリプロセッサの定義]で_ATL_MIN_CRTを削除
こうするとCのランタイムがリンクされてエラーが消えます。
これだけでOKです。
もう一つの方法は:
●Cのランタイム関数を使うのをやめる
strlen などの関数を lstrlen などに変更します。

今回は、_ATL_MIN_CRTはそのままにして、Cのランタイム関数を使うのをやめて
この問題に対処します。
brfc1867.cpp ファイルのstrnicmp関数がその犯人です。

	if (strnicmp(disp,(char*)tp+offset,dlen) != 0)
		break;
//	if (CompareString(LOCALE_SYSTEM_DEFAULT,NORM_IGNORECASE,
//		disp,dlen,(char*)tp+offset,dlen) != CSTR_EQUAL)
//		break;

これを次のようにstrnicmp関数の代わりにCompareString関数を
使うように修正します。コメントを付け替えるだけです。
//	if (strnicmp(disp,(char*)tp+offset,dlen) != 0)
//		break;
	if (CompareString(LOCALE_SYSTEM_DEFAULT,NORM_IGNORECASE,
		disp,dlen,(char*)tp+offset,dlen) != CSTR_EQUAL)
		break;


ビルドしてエラーが消えればOKです。

ATLビルド環境の不具合によるrgsファイルの修正(VC++5.0のみ) VC++6.0では不要

VC++5.0のATLビルド環境の不具合のため、CreateObjectメソッドで 失敗することがあります。
Set obj = Server.CreateObject("FileUp.1")だとうまくいきますが、 Set obj = Server.CreateObject("FileUp")だと失敗します。 ATL のレジストリの登録でversion-independent ProgIDのCLSIDが 登録されないことが原因です。 DevStudio\SharedIDE\Template\atlの中の*.rgs ファイルの中を 次のように編集してください。

	[!ProgID] = s '[!TypeName]'
	{
		CLSID = s '{[!ObjectGUID]}'
[!if=(InsertableEnabled, "TRUE")]
		'Insertable'
[!endif]
	}
	[!VersionIndependentProgID] = s '[!TypeName]'
	{
		CLSID = s '{[!ObjectGUID]}'      <=== この行を追加
		CurVer = s '[!ProgID]'
	}


これで次回からビルドするときに正しく登録されるようになります。
プロジェクトのFileUp.rgsファイルを手で編集してもかまいません。
CurVerの行の前にCLSIDの行を次のようにコピーしてください。
	FileUp.1 = s 'FileUp Class'
	{
		CLSID = s '{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}'
	}
	FileUp = s 'FileUp Class'
	{
		CLSID = s '{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}'
		CurVer = s 'FileUp.1'
	}



Home


Copyright 1999 Tatsuo Baba,All rights reserved.