ひきこもりプログラマ

C++のこととか。

put_ValueでIDispatch error #3105

2012-02-15 | Software

C++プログラムからADO 2.7を使ってSQL Serverのデータを更新する簡単なお仕事です。MDACのバージョンは6.1.7600.16385。

しかしadUseClient+adOpenStatic+adLockOptimisticの鉄板の組み合わせなのにput_Valueしようとすると「IDispatch error #3105; 複数ステップの操作でエラーが発生しました。各状態の値を確認してください。」エラーが。しかもテーブルによって,あるいは列によってはエラーにならなかったりするという。

接続文字列をいじってみたりテーブルの更新順を変えてみたりあれこれやったのですが結局原因は不明のまま。しょうがないのでadUseServer+adOpenKeysetに変えました。ADO.NETでは廃止された組み合わせなんですが(参考: Data Points: Migrating from ADO to ADO.NET, Part 2 http://msdn.microsoft.com/en-us/magazine/cc163940.aspx)大丈夫なんでしょうか…。


ADOでSQL Serverのdatetime型がVT_EMPTYになる

2012-01-12 | Program

C++プログラムからADO 2.7を使ってSQL Serverからデータをとってくる簡単なお仕事です。MDACのバージョンは6.1.7600.16385。

しかしなぜかタイトルのような現象が発生。それも,datetime列は複数あるのですが,特定の列だけです。同じデータベースで簡単なテストプログラムを作って試してみると,当然のことながら再現しません。まさかと思ってSELECT句の列名の並び順を変更してみました。

変更前
SELECT 列1,列2,列3,...,日付A,列X,列Y,列Z,...,日付B
変更後
SELECT 列X,列Y,列Z,...,日付B,列1,列2,列3,...,日付A

こうするとびっくり,VT_EMPTYになっていた日付Bは読めるようになったのですが,かわりにさっきまで読めていた日付AがVT_EMPTYに。しょうがないので

最終形
SELECT 日付A,日付B,列X,列Y,列Z,...,列1,列2,列3,...

としてみましたが釈然としません…。

(追記1: datetimeだけじゃなくvarcharやdoubleでも発生しました)

(追記2: adUseServer+adOpenForwardOnlyの組み合わせで開いていたのですが,これをadUseClientにするかadOpenStaticにしたところ問題は出なくなりました)


MFCプロパティシートの適用ボタンクリックの処理

2011-11-04 | Program

CPropertySheet/CPropertyPageを使ったダイアログで適用ボタンをクリックすると全ページのCPropertyPage::OnApply()が呼ばれます。この挙動,各ページが自分のところに表示されているデータを保存するような仕組みになっていると期待したものだと思うのですが,そうではなくて,アプリケーションのある1か所で全ページのデータをまとめて管理したいときはどうすればいいのか。

最初に考えたのは,各ページのOnApply()からプロパティシートの特定の関数(たとえばOnApplyPage(CPropertyPage*)みたいな)を呼び出して「最後のページがOnApplyPageを呼び出したらそこでデータを保存」という方法でしたが,なんとなく苦し紛れすぎる気がしたので却下。

次に試したのが,メッセージ返送(リフレクション)を使う方法。つまり

	BEGIN_MESSAGE_MAP(CMyPropertySheet, CMFCPropertySheet)
	ON_NOTIFY_REFLECT(PSN_APPLY, OnApplyReflect)
	END_MESSAGE_MAP()

こうです。通常リフレクションは子から親に投げられるメッセージをさらに子に投げ返すのですが,親子関係が逆でも動作するみたいです。このときOnApply()は呼ばれなくなるのでOnApplyReflect()内で明示的に呼んでやる必要があります。しかしPSN_APPLYはOKボタンをクリックしたときにも投げられることを忘れていて,そしてそのときにOnApply()がFALSEを返したときどうやればプロパティシートが閉じないようにできるのかわからなかったのでこれも頓挫。

そして初心に帰ってMSDNの「[適用] ボタンの処理を読み返すと。

[適用] ボタンの操作を反映するには、プロパティ シートからそのオーナー (またはアプリケーションのほかの外部オブジェクト) に対して、プロパティ ページの現在の設定値を適用するように通知する必要があります。 同時に、[適用] ボタンを無効にするために、変更内容が外部オブジェクトに適用されるすべてのページに対して CPropertyPage::SetModified( FALSE ) を呼び出します。

このプロセスの例については、MFC の「標準のサンプル」の PROPDLG を参照してください。

そしてサンプルには。

	BEGIN_MESSAGE_MAP(CModalShapePropSheet, CPropertySheet)
	//{{AFX_MSG_MAP(CModalShapePropSheet)
	ON_COMMAND(ID_APPLY_NOW, OnApplyNow)
	//}}AFX_MSG_MAP
	END_MESSAGE_MAP()

…正直その発想はなかった。


Configuring DCMTK for one click build

2011-09-07 | Program

DCMTK Visual Studio files generated by CMake have absolute paths and that was bothering for my team-development. I wanted to add DCMTK projects to our own software’s solutions and make it possible to build them by a single click, right after you get a copy of source codes from repository. Here’s some steps I had to take before I put DCMTK files to our revision control system.

  1. List all projects

    Make a text file "projects.txt" and put all project names. The outputs of DIR /AD /B command can help.

    dcmdata
    dcmimage
    dcmimgle
    dcmjpeg
    dcmjpls
    dcmnet
    dcmpstat
    dcmqrdb
    dcmrt * only with recent versions
    dcmsign
    dcmsr
    dcmtls
    dcmwlm
    oflog
    ofstd
  2. By default, generated Visual Studio project files do not contain header files. I think they should.

    for /f %I in (projects.txt) do sed -i -e "s/ADD_LIBRARY(\(.\+\))/FILE(GLOB headers ..\/include\/dcmtk\/${PROJECT_NAME}\/*.h)\nADD_LIBRARY(\1 ${headers})/" %I\libsrc\CMakeLists.txt

    In recent versions ADD_LIBRARY may be declared as DCMTK_ADD_LIBRARY instead. sed.exe is available at http://gnuwin32.sourceforge.net/packages/sed.htm

  3. I prefere all libraries built with multithread support and dependency to VC/MFC dlls, so modify CMakeLists.txt in the root DCMTK source directory and replacce /MT (/MTd) with /MD (/MDd).
    SET(CMAKE_C_FLAGS_DEBUG "/MDd /Z7 /Od")
    SET(CMAKE_C_FLAGS_RELEASE "/MD /O2")
    SET(CMAKE_C_FLAGS_MINSIZEREL "/MD /O2")
    SET(CMAKE_C_FLAGS_RELWITHDEBINFO "/MDd /Z7 /Od")
    SET(CMAKE_CXX_FLAGS_DEBUG "/MDd /Z7 /Od")
    SET(CMAKE_CXX_FLAGS_RELEASE "/MD /O2")
    SET(CMAKE_CXX_FLAGS_MINSIZEREL "/MD /O2")
    SET(CMAKE_CXX_FLAGS_RELWITHDEBINFO "/MDd /Z7 /Od")

    You can replace /Z7 with /Zi if you prefere debug information stored in *.pdb files.

  4. Additional include directories are redundant. Modify CMakeLists.txt again.
    #-----------------------------------------------------------------------------
    # Include directories
    #-----------------------------------------------------------------------------
    
    SET(DCMTK_INCLUDE_DIR
     ${DCMTK_BINARY_DIR}/config/include
    ) INCLUDE_DIRECTORIES(${DCMTK_INCLUDE_DIR})
  5. I want structured exceptions to be handled correctly so replace /EHsc with /EHa.
        IF(VS_VERSION GREATER 7)
          SET(CMAKE_C_FLAGS "/nologo /W3 /Gy /EHa")
          SET(CMAKE_CXX_FLAGS "/nologo /W3 /Gy /EHa")
        ENDIF(VS_VERSION GREATER 7)
  6. You can remove additional include directories pointing to DCMTK outputs, since additional dependencies are already specified as relative path to each *.lib files.

    for /f %I in (projects.txt) do sed -i -e "s/\${[a-z]\+_BINARY_DIR} \?//g" %I\apps\CMakeLists.txt

    for /f %I in (projects.txt) do sed -i -e "s/\${[a-z]\+_BINARY_DIR} \?//g" %I\tests\CMakeLists.txt

  7. Generate Visual Studio files with CMake. Set binaries directory same as source code directory, for I need both header files and source files.
  8. Modify all project files.

    for /r %I in (*.vcxproj) do trimproj.vbs %~fI

    trimproj.vbs is as below.

    Option Explicit
    
    If WScript.Arguments.Count = 0 Then
    	WScript.Echo("Please specify *.vcxproj file.")
    	WScript.Quit
    End If
    Dim dom
    Set dom = WScript.CreateObject("MSXML2.DOMDocument")
    Dim vcxproj
    vcxproj = WScript.Arguments(0)
    Dim loaded
    loaded = dom.load(vcxproj)
    If Not loaded Then
    	WScript.Echo(dom.parseError.reason)
    	WScript.Quit
    End If
    ' Removing these configurations makes them default
    RemoveNodes dom, "/Project/PropertyGroup/IntDir"
    RemoveNodes dom, "/Project/PropertyGroup/TargetName"
    RemoveNodes dom, "/Project/PropertyGroup/TargetExt"
    RemoveNodes dom, "/Project/ItemDefinitionGroup/ClCompile/DebugInformationFormat"
    RemoveNodes dom, "/Project/ItemDefinitionGroup/ClCompile/ProgramDataBaseFileName"
    RemoveNodes dom, "/Project/ItemDefinitionGroup/Link/ImportLibrary"
    RemoveNodes dom, "/Project/ItemDefinitionGroup/Link/ProgramDataBaseFile"
    RemoveNodes dom, "/Project/ItemDefinitionGroup/ProjectReference/LinkLibraryDependencies"
    ' I don't need CustomBuild
    RemoveNodes dom, "/Project/ItemGroup/CustomBuild"
    RemoveNodes dom, "/Project/ItemGroup/None"
    ' note: "/Project/ItemDefinitionGroup/Link/AdditionalLibraryDirectories" can't be removed
    ' because it may have other non-DCMTK libraries such as zlib.
    
    SetRelativePaths dom, "/Project/ItemDefinitionGroup/ClCompile/AdditionalIncludeDirectories", vcxproj
    SetRelativePaths dom, "/Project/ItemDefinitionGroup/ResourceCompile/AdditionalIncludeDirectories", vcxproj
    SetRelativePaths dom, "/Project/ItemDefinitionGroup/Link/AdditionalLibraryDirectories", vcxproj
    SetRelativePaths dom, "/Project/ItemDefinitionGroup/Midl/AdditionalIncludeDirectories", vcxproj
    
    ' Microsoft changed $(ConfigurationName) to $(Configuration) upon Visual Studio 2010 release.   Why?
    SetConstantValue dom, "/Project/PropertyGroup/OutDir", "$(Configuration)\"
    ' Unless you specify pdb file name of static libraries they are all created as vc100.pdb
    SetConstantValue dom, "/Project/ItemDefinitionGroup/ClCompile/ProgramDataBaseFileName", "$(OutDir)$(ProjectName).pdb"
    
    SetRelativePath dom, "/Project/ItemGroup/ProjectReference/@Include", vcxproj
    SetRelativePath dom, "/Project/ItemGroup/ClCompile/@Include", vcxproj
    SetRelativePath dom, "/Project/ItemGroup/ClInclude/@Include", vcxproj
    
    dom.save(vcxproj)
    
    Sub RemoveNodes(dom, xpath)
    	Dim nodes, node
    	Set nodes = dom.documentElement.selectNodes(xpath)
    	For Each node In nodes
    		node.parentNode.removeChild(node)
    	Next
    End Sub
    
    Sub SetConstantValue(dom, xpath, value)
    	Dim nodes, node
    	Set nodes = dom.documentElement.selectNodes(xpath)
    	For Each node In nodes
    		node.text = value
    	Next
    End Sub
    
    Sub SetRelativePaths(dom, xpath, project_path)
    	Set nodes = dom.documentElement.selectNodes(xpath)
    	Dim file_sys
    	Set file_sys = WScript.CreateObject("Scripting.FileSystemObject")
    	Dim nodes, node
    	Set nodes = dom.documentElement.selectNodes(xpath)
    	For Each node In nodes
    		Dim paths, i
    		paths = Split(node.text, ";")
    		For i = 0 To UBound(paths)
    			paths(i) = GetRelativePath(project_path, Replace(paths(i), "/", "\"))
    		Next
    		node.text = Join(paths, ";")
    	Next
    End Sub
    
    Sub SetRelativePath(dom, xpath, project_path)
    	Dim nodes, node
    	Set nodes = dom.documentElement.selectNodes(xpath)
    	For Each node In nodes
    		Dim absolute_path
    		absolute_path = Replace(node.text, "/", "\")
    		node.text = GetRelativePath(project_path, absolute_path)
    	Next
    End Sub
    
    ' Please note that this function is case-sensitive while it should not be.
    Function GetRelativePath(from_str, to_str)
    	Dim file_sys
    	Set file_sys = WScript.CreateObject("Scripting.FileSystemObject")
    	If file_sys.GetAbsolutePathName(to_str) <> to_str Then
    		GetRelativePath = to_str
    	Else
    		Dim relative
    		relative = "."
    		Dim parent
    		parent = file_sys.GetParentFolderName(from_str)
    		Dim found
    		found = InStr(to_str, parent)
    		Do While found = 0
    			relative = relative & "\.."
    			parent = file_sys.GetParentFolderName(parent)
    			found = InStr(to_str, parent)
    		Loop
    		GetRelativePath = Replace(to_str, parent, relative)
    	End If
    End Function

Windows XPのCryptDecryptでNTE_DOUBLE_ENCRYPTエラー

2010-11-14 | Program
  1. データAを鍵Xで復号してデータBを作成
  2. データBをB1,B2,B3に分割
  3. B1,B2をそれぞれB3で復号

という処理が,Windows 7では動いていたのに,Windows XPで実行するとB1の復号時に「データは既に暗号化されています。」と言われて「???」状態。ぐぐってもドキュメントを読んでもさっぱりわからなかったので,似たような処理でうまくいっているところを見ながら少しずつ似せていったところ,Bを作成したあとB1ではなくB2を先に復号すれば大丈夫であることが判明。B1がBの先頭と一致していたので,Bそのものとみなされて「それはさっき復号したばかりのデータでっせ」と言われたのではないかと。

この問題,サポート情報かリリースノートに書いてあるかもしれませんが探す気力はなし。どなたかご存知でしたら教えてください。