SAP Crystal Reports

/ .NET

Windowsアプリの帳票ツールとしてはそれなりにメジャーかと思われるクリスタルレポートのお話。

かれこれ10年以上前に初版開発した業務用パッケージアプリで Visual Studio 2005 付属版のクリレポを使って以来、今なお現役という状況。
アプリ自体は Windows Forms から WPF に移行したり、データベースが Oracle から PostgreSQL に変わったり、と色々手が入ったものの、帳票に関してはこれといった変更も行われずに過ごしてきました。
最近になってようやく最新版に刷新するか、という流れになってきたので早速調査。

ちなみに、VS2005 付属のクリレポでは VC++ 2005 のランタイムが使われているのですが、同ランタイムは2016年にサポート切れ、提供も終了とのこと。
確かに「最新のサポートされる Visual C++ のダウンロード」に 2005 は掲載されていませんが、VC++ 2005 ランタイム自体は普通にダウンロードページが生きてたりします。
まぁ、サポートが切れているのは間違いなさそうなので、今後の利用は控えるべきでしょう。

クリレポの話に戻して、Wikipediaによれば、Crystal Decisions 社から Business Objects 社を経て、現在は SAP 社が取り扱っているようで、「SAP Crystal Reports」という名称になっておりました。
様々なバージョンがある単体版はさておき、Visual Studio への統合はどうなっているのかしらん、と心配だったのですが、どうやら継続して無償提供されているようで、「SAP Crystal Reports, developer version for Microsoft Visual Studio」として提供されている模様。
最新情報を得るためにはこちらのページも参照しておいたほうが良いかもしれません。
ちなみにダウンロードページの直リンはこちら

Visual Studio 上でレポートデザイナを使うためには VS IDE 用のパッケージを、実行環境にはランタイムモジュールをそれぞれ導入する必要があります。
VS IDE 用のパッケージは Visual Studio 2015 への導入のみ確認しました。(VS2017 で利用可能かは未確認)
また、アプリのインストーラにランタイムを同梱するためのマージモジュールも提供されています。(マージモジュールを利用すると個別にランタイムを入れる必要がなくなり、インストーラが一本化できます)

で、本題はここから。

新しいマージモジュールをインストーラに組み込んでみたところ、どうにもうまくインストールできません。
表示されるエラーはこんな感じ。

モジュール C:\Program Files (x86)\SAP BusinessObjects\Crystal Reports for .NET Framework 4.0\Common\SAP BusinessObjects Enterprise XI 4.0\win32_x86\crtslv.dll で登録に失敗しました。HRESULT -2147024770。サポート担当者に問い合わせてください。

マージモジュールを利用したインストーラ作成について書かれているページを見ると、VC++ 2005 ランタイムのマージモジュールをセットアッププロジェクトに含めるように書かれているのですが、実際に VC++ 2005 のマージモジュール (Microsoft_VC80_CRT_x86.msm など) を含めてみても効果なし。
埒が明かないので、Dependency Walkerを引っ張り出して、SAPクリレポランタイムが参照している DDL を調べてみたところ、VC++ 2015 のランタイムを参照していることが判明。
セットアッププロジェクトに VC++ 2015 ランタイムのマージモジュール (Microsoft_VC140_CRT_x86.msm と Microsoft_VC140_MFC_x86.msm) を追加してあげると、ようやくインストールが成功しました。(_x86、_x64 どちらを利用するかはセットアッププロジェクトの TargetPlatform に合わせるべし)
SAPクリレポランタイムのバージョンは現時点で 13.0.22 のようですが、ここに至るまでに何度もバージョンアップが重ねられていると思われるので、SAPクリレポを使用する場合は今後のバージョンの変化に注意が必要かもしれません。

実行環境が整ったところで、旧デザイナで作成したレポートファイルをSAPクリレポ配下で使えるか試したところ、特に問題なく利用できる様子。
FAQによれば、レポートファイル (.rpt) の互換性は保たれているとのこと。
データソースに DataSet をセットする方式、かつ Windows Forms 用のビューアしか確認していませんが、ビューアの体裁を合わせるためにプロパティ変更が多少必要だったものの、レポート出力のロジック自体は特に変更の必要なく動いているようなので、これなら普通に移行できそうです。
ちなみに、コードは少し書きにくいですが、動的参照 (Reflection) を使うと新/旧どちらのランタイムでも動かせて便利だったりします。


ODP.NETのGAC登録

/ .NET, Database

ODP.NETを利用している業務系アプリのWindows 8.1、Windows 10への対応作業中。(主に動作確認レベルですが)
アプリの導入手順としてOracle Clientをインストールしておけば動くはずなのですが、なぜか動きません。
原因はODP.NETがGAC(グローバルアセンブリキャッシュ)に登録されていないため。
ODP.NET(Oracle.DataAccess.dll)を静的参照している場合、それと分かるエラーメッセージなどが出ずにいきなり落ちてしまったりするので結構ハマりがち。
ちなみに少し面倒だけれどもリフレクションを使って動的参照の形で作るといきなり落ちるようなことは防げます。

.Net Frameworkのバージョンが2.0時代の古いアプリなので、Win8.1/10の場合、先にWindowsの機能に「.NET Framework 3.5(2.0)」を追加しておかないと、Oracle Clientのインストーラが.NET 2.0用のODP.NET DLLをGACに登録してくれません。
このような場合に後からGAC登録するには以下のコマンドを実行すれば良いとのこと。(パスは適宜編集してください)

Skylake問題などもあって、企業向けシステムのWindows 10対応が少し早まっているようです。
まぁ、そもそもWindows 7で動いていれば普通に動きそうなものなのですが、High DPIなどの対応まで考慮するとそのままでは厳しい部分もあったりなかったり。


■追記 2018-06-20
かれこれ2年以上経過しているエントリですが、意外と参照頻度が高く、関連するネタがあったので追記しておきます。
Windows 10 に .NET 2.0 用の ODP.NET をインストールする場合、本エントリの通り、事前に「.NET 3.5 の有効化」を施しておかないと自動的に GAC 登録してくれない、という話がありますが、つい最近「.NET 3.5 の有効化」を施しているにもかかわらず GAC 登録してくれないパターンに遭遇。
Oracle Database 12c Release 2 の 32bit 版クライアント win32_12201_client.zip にて事象確認。
ちなみに前バージョンの Oracle Database 12c Release 1 の winnt_12102_client32.zip では正常にGAC登録されるので、12.2 インストーラのバグ(処理漏れ)っぽい印象。
対策としては本エントリ記載の手動による GAC 登録を行えば OK です。
もはや ODP.NET を導入する際は手動 GAC 登録を必須とする運用にするしかないかもしれません…


The Big Red X

/ .NET

image
何だか格好良さげなタイトルですが、Windowsフォームアプリの例外関連の話。
上のような画面を見たことのある開発者の方もいるかと思いますが、フォームやコントロールの描画処理で何らかの例外が発生すると描画が中止されて赤い×マーク(バツマーク、バッテン)が表示されます。
通常はユーザコントロールなどの内部処理で落ちた場合などに発生するケースが多いと思うのですが、この×マーク、一旦表示されると元の表示状態に戻すのがなかなか難しく、普通は画面を開き直したり、アプリ再起動くらいしか手段がありません。
この問題を解決すべく、海外のWebサイトを徘徊した結果、見つけた回避策が以下の方法。

OnPaintイベントでわざとエラーを発生させて×マークを表示した後、ボタンクリックで再描画を行うサンプルです。
実際のアプリではOnThreadExceptionで×マークの原因となったスレッド例外を捕捉したら再描画、といった感じになるでしょうか。
ハイライト表示した箇所が強制再描画のくだりですが、リフレクションでSetStateなる内部メソッドを呼び出してエラー状態を解消するという、なかなかの荒業。
SetStateや0x400000の意味などはソースを直接覗くしか知るすべがないようです。
とりあえずVBで書いていますが、参考にさせていただいたこちらのサイトにC#で書かれたサンプルも掲載されています。
もちろん、根本的な描画時エラーを取り除いて×マーク自体を出現させないようにすることが先決ですので、どうしても解決できない場合の参考ということで…


Installer

/ .NET

image
業務で.NETデスクトップアプリの配布用インストーラを作成することがあります。
少し前までは.NET Framework 2.0ベースのアプリ開発が主流で、インストーラもVisual Studio 2005のセットアップウィザードを利用して作成していたわけですが、Windows 7がようやく主流になった昨今は.NET 4ベースのアプリをVS2010で作るようになってきています。
これに合わせてインストーラもVS2010のセットアップウィザードプロジェクトテンプレートを使おうと思ったのですが、実際に試してみるとインストール後の各ファイルのタイムスタンプがビルド時の日時ではなくインストーラ作成時の日時に変わってしまう不具合に遭遇。
こちらのフォーラムでも触れられているように、Windows XP上のVS2010でインストーラを作るとなぜか事象発生しないことは判明したのですが、いまさらXPを使うわけにもいかないので他の方策を検討することに。

最初に調べたのは最新のVS2013とひとつ前のVS2012ではどうなっているか、ということ。
しかし、2012以降のVisual Studioには従来のセットアッププロジェクトテンプレートは含まれておらず、ディスコン扱いになっている様子。
代わりに付いてくるInstall Shieldのサブセット版を試してみるも細かい設定ができないようでこれも却下。
諸事情により有償のフルセット版を使うこともできないため、一時はVM上のXPを使ってビルドするなどして凌いできたわけですが、少し前にようやく問題解決のソリューションを発見。

その名も「Visual Studio 2013 Installer Projects」というアドインです。
VS2013の拡張機能として登録するだけで、Windows Installer形式のインストーラが作成できます。
Windows 8.1上のVS2013に入れて試した限りでは、従来とほぼ同様の操作でインストーラの設定ができて、インストールしたファイルのタイムスタンプ問題も発生しない模様。
英語版しかないものの、作成したインストーラはこれまでどおり日本語表示されるので、特に問題なく乗り換えられそうです。
モノ自体はMicrosoftの中の人が作っているようですし、今後登場するVS2015への対応も期待できそうなので、当面これでやっていけそうです。


Office文書のPDF変換

/ .NET, C#

Offce2007以降、PDF形式へのエクスポートが標準でサポートされるようになりました。
というわけで、.NETアプリケーションからOfficeファイルのPDFエクスポートを実行する方法を紹介したいと思います。

静的な参照設定を行う場合は、必要なOfficeアプリのObject Libraryを参照設定しておいて、普通にVBAオブジェクトを.NETアプリから操作すれば良いわけですが、これだとそもそもOffice2007以降がインストールされていないPCでは参照エラーで正常動作しません。

そこで登場するのが遅延バインディング(Late Binding)という方法。
論より証拠、Excel→PDF変換のサンプルコードを載せておきます。

Word、PowerPointについても同様のイメージで実装できますが、Officeアプリによってそれぞれライブラリ仕様が異なりますので、流用できるのは基本的な流れ(アプリインスタンス作成→ファイルオープン→エクスポート→クローズ)だけになります。
そのため、少し面倒ですが、各OfficeアプリのMSDN Developer Referenceなどを参照してプロパティやメソッドの利用方法を調べつつ、実装していく必要があります。
VBAのオブジェクトをインテリセンス無しに記述して操作することになるので、とにかくパラメータの数や位置に注意が必要です。
リファレンス無しではまず作れないわけですが、どうも英語の資料しかなさげなのが厳しいところです。

ここでは単純にOK/NGを返すだけのメソッドになっていますが、実際にはエラー処理などの追加が必要になるかと思います。
また、パスワードロックされているOffice文書を開いた場合など、変換処理中にOfficeのウィンドウが表示されることがあるのですが、そのウィンドウが前面表示されないために操作感が悪くなってしまうケースが見受けられました。
これについては、Win32APIのFindWindow+SetForegroundWindowメソッドで最前面表示する手法を使うことによって回避できるようです。