WebSurfer's Home

トップ > Blog 1   |   Login
Filter by APML

SHDocVw.dll と AxSHDocVw.dll の作り方と使い方

by WebSurfer 23. June 2012 16:11

ActiveX の WebBrowser コントロール (SHDocVw.dll) を .NET Windows Forms アプリで扱おうとしてかなりハマったので、再びそのようなことがないよう備忘録を残しておきます。

AxSHDocVw.dll を使用した Windows Forms アプリ

.NET Framework 2.0 以降では、SHDocVw.dll のマネージラッパーである WebBrowser コントロール が提供されていますので、.NET Windows Forms アプリで SHDocVw.dll を扱うことはあまりないかもしれませんが、公開されてないイベントを利用したい場合などは困ります。

マネージラッパーの WebBrowser を拡張する方法(Extended .NET 2.0 WebBrowser Control 参照)もあるようですが、今回の目的(target="_blank" のリンクをクリックすると、セッションを維持して、上の画像のように別の Form1 に表示)程度なら、直接 SHDocVw.dll を扱ったほうが簡単そうに思えたので、そうしてみました。

以下の記事を読む前に、shdocvw.dll コンポーネントに関する予備知識として、MSDN ライブラリの Internet Explorer のアーキテクチャ に目を通しておくことをお勧めします。

まず、Windows Forms アプリで ActiveX コントロールをホストするには、AxHost クラス から派生するラッパーコントロールを生成する必要があります。

それには、SDK に含まれている Aximp.exe (Windows フォーム ActiveX コントロールインポーター) を使って、ActiveX コントロール用の COM タイプライブラリに属する型定義を Windows フォームコントロールに変換します。

具体的には、以下の画像のようにコマンドプロンプトから aximp.exe を起動し、shdocvw.dll(ActiveX コントロール用の COM タイプライブラリ。即ち IE のコンポーネント)から、Microsoft Web Browser コントロール用の共通言語ランタイムプロキシ (SHDocVw.dll) および Windows フォームプロキシ (AxSHDocVw.dll) を作成します。

コマンドプロンプトから aximp.exe を起動し SHDocVw.dll と AxSHDocVw.dll を生成

紛らわしいのが、IE のコンポーネントの shdocvw.dll と、Aximp.exe が生成する共通言語ランタイムプロキシ SHDocVw.dll の名前が同じという点です。名前が同じなだけで、中身は別物ですので注意してください。実は、自分が最初にハマったのがここです。(笑)

共通言語ランタイムプロキシの SHDocVw.dll を生成するので、IE のコンポーネントの shdocvw.dll が存在するフォルダで作業すると、「AxImp Error: 出力ファイル 'SHDocVw.dll' への書き込みエラーです。」というエラーが出てうまくいきません。

オプションで /out: e:\AxSHDocVw.dll というように、プロキシを出力したいフォルダを指定し(この例では e:\)、ファイル名を AxSHDocVw.dll とすれば、以下の画像のように期待通りプロキシが生成されます。なお、この例では /source オプションを追加しているので、AxSHDocVw.dll のソースと PDB ファイルが追加されています。

生成された SHDocVw.dll と AxSHDocVw.dll

以上で Microsoft Web ブラウザコントロール用の共通言語ランタイムプロキシ (SHDocVw.dll) および Windows フォームプロキシ (AxSHDocVw.dll) が作成できました・・・が、実は、このように手動で作る必要はなかったのでした。(汗)

Visual Studio のウィザードで作ることが可能で、その方法は以下の通りです。

まず、Visual Studio のツールボックスに Microsoft Web Browser を追加します。

Visual Studio のツールボックスに Microsoft Web Browser を追加

ツールボックスの空白部分をクリックして出てくるダイアログで[アイテムの選択(I)...]をクリックして[ツールボックス アイテムの選択]ダイアログボックスを表示します。

上の画像のように、[COM コンポーネント]タブをクリックし、一覧から Microsoft Web Browser を探してチェックを入れます。

[OK]をクリックすると、ツールボックスに WebBrowser コントロールが追加されます("Microsoft Web Browser" というテキストで表示されます)。

ツールボックスから Microsoft Web Browser を Form にドラッグ&ドロップすると自動的に SHDocVw.dll と AxSHDocVw.dll 作って、参照設定に加えてくれます。(下の「2012/7/1 注記追加」を参照)

参照設定に追加された AxSHDocVw と SHDocVw

あとは、Microsoft サポートのページ How to use the WebBrowser control NewWindow2 event in Visual Basic .NET に書いてあるとおり、NewWindow2 イベントのハンドラのコード(下記)を記述すれば、target="_blank" のリンクをクリックすると新たに Form1 生成されて表示され、その中の axWebBrowser に pdf が表示されます。また、セッションも引き継がれます(IE7 以前のブラウザはダメかも・・・未検証です)。

private void axWebBrowser1_NewWindow2(object sender, 
    AxSHDocVw.DWebBrowserEvents2_NewWindow2Event e)
{
    // target="_blank" のリンクをクリックしたとき、
    // 以下のコードがないと、新たに IE が開きそこ
    // に pdf が表示される。セッションは切れる。

    // 以下のコードがあると、新たに Form1 が表示さ
    // れ、その中の axWebBrowser に pdf が表示され
    // る。セッションは引き継がれる。

    Form1 frmWB = new Form1();
    frmWB.axWebBrowser1.RegisterAsBrowser = true;
    e.ppDisp = frmWB.axWebBrowser1.Application;
    frmWB.Visible = true;
}

----- 2012/7/1 注記追加 -----

Visual Studio 2010 のツールボックスから Microsoft Web Browser を Form にドラッグ&ドロップすると自動的に生成されるプロキシ(ラッパー)の名前は、Interop.SHDocVw.dll と AxInterop.SHDocVw.dll になります。(aximp.exe を使って作ったものとは中身も少々違うようです)

SHDocVw.dll と AxSHDocVw.dll は何が違うかというと、AxSHDocVw.dll のソースを見ての想像ですが、前者が ActiveX コントロールのプロパティ、メソッドの COM ラッパー、後者がそれらの COM ラッパーと .NET アプリを仲介するためのプロパティ、メソッド、イベントを提供するクラスのようです。

例えば、AxSHDocVw.dll に Application というプロパティが以下のように定義されています。SHDocVw.dll には return this.ocx.Application; で使われている Application という COM ラッパーが定義されてるということのようです。

namespace AxSHDocVw {
  [System.Windows.Forms.AxHost.ClsidAttribute(
    "{8856f961-340a-11d0-a96b-00c04fd705a2}")]
  [System.ComponentModel.DesignTimeVisibleAttribute(true)]
  [System.ComponentModel.DefaultProperty("Name")]
  public class AxWebBrowser : System.Windows.Forms.AxHost {
        
    private SHDocVw.IWebBrowser2 ocx;
        
    private AxWebBrowserEventMulticaster eventMulticaster;
        
    private System.Windows.Forms.
              AxHost.ConnectionPointCookie cookie;
        
    public AxWebBrowser() : 
            base("8856f961-340a-11d0-a96b-00c04fd705a2") {
    }
    
    [System.ComponentModel.DesignerSerializationVisibility(
      System.ComponentModel.
      DesignerSerializationVisibility.Hidden)]
    [System.Runtime.InteropServices.DispIdAttribute(200)]
    public virtual object Application {
      get {
        if ((this.ocx == null)) {
          throw 
            new System.Windows.Forms.AxHost.
                InvalidActiveXStateException(
                  "Application", 
                  System.Windows.Forms.AxHost.
                    ActiveXInvokeKind.PropertyGet);
        }
        return this.ocx.Application;
      }
    }
    // ・・・中略・・・
  }
  // ・・・中略・・・
}

上記はプロパティの場合ですが、イベントの場合はもっと複雑で、AxSHDocVw.dll には、イベントの宣言、デリゲートの定義、イベントの引数クラスの定義、クライアント・シンクのクラス定義等々が含まれます。詳しくは別の記事 WebBrowser の拡張 に書きましたのでそちらを見てください。

Tags:

.NET Framework

長い日本語の添付ファイル名が文字化け

by WebSurfer 30. October 2011 22:00

.NET Framework 4 の SmtpClient を使用すると、添付ファイルに長い日本語のファイル名を使うと文字化けするという問題があります。(.NET 3.5 以前では問題なし)

原因はファイル名が二重エンコーディングされてしまうからで、そのメカニズムは このブログ に詳しく書いてあります。

この問題は、米国の MSDN フォーラムでも報告されており、原因が .NET Framework 4 のバグであることは Microsoft も認識しているようです。

その時の Microsoft の人の回答(2010/7/23 付けの記事参照)では、次のリリースを待つか、短い名前にするしかないということでした。

とは言え、現実に問題ない PC があるのは確かですので(自分の開発マシンがそうです)、何らかの更新プログラムがリリースされていて、問題が発生しない PC には適用されているはずと思って調べたら、Connect のページ に、Hotfix KB2183292 によってファイル名の二重にエンコーディングの問題も解決されるとの話がありました。

試しに、二重エンコーディングの問題の出ていた PC に、Hotfix KB2183292 をダウンロードしてインストールすると問題が解決しました。

Windows Update でインストールされる .NET Framework 4 用の更新プログラム で Hotfix KB2183292 が置き換えられると書いてありましたので、この更新プログラムによって二重エンコーディングの問題も解決されているのかと思いましたが、どうもそうではないようです。

実際、.NET Framework 4 用の更新プログラム がインストールされている PC でも二重エンコーディングの問題が発生しました。

というわけで、Windows Update で解決されているのではなさそうなので、次期リリース(.NET 4.5 ?)でこの問題が解決されるまでは、長い日本語のファイル名を添付ファイルに使う場合は、何らかの対策が必要なようです。

問題は、Attachment コンストラクタ (String, ContentType) で長い日本語のファイル名を使用すると二重エンコーディングされるということなので、代わりに Attachment コンストラクタ (Stream, ContentType) を使用し、Content-Disposition の filename の方にファイル名を設定することで問題を回避できるはずです。

以下のような感じです。

private string EncodeHeader(string str, Encoding enc)
{
  string ret = 
    System.Convert.ToBase64String(enc.GetBytes(str));
  ret = string.Format("=?{0}?B?{1}?=", enc.BodyName, ret);
  return ret;
}

private void button2_Click(object sender, EventArgs e)
{
  System.Text.Encoding enc = 
    System.Text.Encoding.GetEncoding("utf-8");

  string mailAddress = this.userName + "@" + this.host;

  MailAddress to = 
    new MailAddress(mailAddress, EncodeHeader("日本花子", enc));
  MailAddress from = 
    new MailAddress(mailAddress, EncodeHeader("日本太郎", enc));

  MailMessage mailMessage = new MailMessage(from, to);

  mailMessage.Subject = textBox1.Text;
  mailMessage.Body = textBox2.Text;

  // 添付ファイル
  file = "テスト用の長めのファイル名です.pdf";
  FileStream fs = 
    new FileStream(file, FileMode.Open, FileAccess.Read);

  Attachment data = 
    new Attachment(fs, MediaTypeNames.Application.Octet);
  ContentDisposition disposition = data.ContentDisposition;
  disposition.FileName = 
    EncodeHeader(System.IO.Path.GetFileName(file), enc);

  mailMessage.Attachments.Add(data);

  SmtpClient smtpClient = new SmtpClient();
  smtpClient.Host = host;
  smtpClient.Port = portSmtp;

  try
  {
    smtpClient.Send(mailMessage);
    MessageBox.Show("Your mail was sent.");
  }
  catch (SmtpException ex)
  {
    SmtpStatusCode status = ex.StatusCode;
    MessageBox.Show("SmtpException: " + ex.Message + 
      "\nSmtpStatusCode: " + status.ToString());
  }
  catch (Exception ex)
  {
    MessageBox.Show("Exception: " + ex.Message);
  }
  finally
  {
    mailMessage.Dispose();
  }
}

こうすると、以下のように Content-Type の name は application/octet-stream となり、Content-Disposition の filename にファイル名が設定され、正しいファイル名を取得できます(メーラーによってはダメかもしれませんが)。

Content-Type: application/octet-stream;
 name="application/octet-stream"
Content-Transfer-Encoding: base64

Content-Disposition: attachment;
 filename="=?utf-8?B?44OG44...BkZg==?="

なお、一旦 Cotent-Type の name が二重エンコーディングされたものになってしまうと、Content-Disposition の filename にファイル名を設定しても、何故かそちらは無視されてしまいます。メーラーにもよると思いますが Windows メールはダメでした。

という訳で、上記のコード方法が次期バージョンがリリースされるまでの Workaround としては適当なようです(ただし、filename のところが、一行で 76 文字の制限を越えてしまいますので、メーラーによっては対応が必要かもしれません)。

Tags: ,

.NET Framework

厳密な名前付きアセンブリ

by WebSurfer 13. September 2011 22:37

サンデープログラマーの自分が、作成したアセンブリに厳密名を付けることはないですが、わけあって厳密名の付け方についていろいろ調べましたので、備忘録として書いておきます。

まず、厳密名 (Strong Name) とは何かですが、MSDN ライブラリ 厳密な名前付きアセンブリ によると「単純テキスト名(アセンブリ名)、バージョン番号、カルチャ情報から成るアセンブリの識別子と、公開キーおよびデジタル署名」だそうです。

web.config で、ライブラリへの参照や HTTP モジュールの参照を設定する際以下のようにしますが、これが厳密名を指定したことになるようです。

<add 
  assembly="System.Core, 
  Version=3.5.0.0, 
  Culture=neutral, 
  PublicKeyToken=B77A5C561934E089"/>

<add 
  name="ScriptModule" 
  type="System.Web.Handlers.ScriptModule, 
    System.Web.Extensions, 
    Version=3.5.0.0, 
    Culture=neutral, 
    PublicKeyToken=31BF3856AD364E35"/>

厳密な名前を付ける理由や、それが推奨されるケース/されないケースについては MSDN ライブラリの マネージ アプリケーションに対する厳密な名前による署名 に書いてあるのでそちらを参照してください。手抜きですみません。(笑)

厳密名のうち、アセンブリ名、バージョン番号、カルチャ情報は開発者が自分で設定する(というより、デフォルトで設定されている)ものです。下の画像にそれらが示されています。

Visual Studio のアセンブリ情報の画面

厳密名のなかで、公開キーとデジタル署名、および公開キートークン (PublicKeyToken) が具体的に何かよく分かってなかったので調べてみました。

アセンブリにデジタル署名するには、まず、秘密キー/公開キーペアから成るキーファイルを作成します。次に、アセンブリの一部分のハッシュを作成し、それを秘密キーで暗号化し、公開キーとともにアセンブリに追加(署名)します。

アセンブリをロードする際、アセンブリに含まれる公開キーと秘密キーで暗号化されたハッシュを取り出し、暗号化されたハッシュを公開キーで復号します。次に、ロードするアセンブリ自体のハッシュを作成して、それと復号したハッシュとを比較して一致することを確認します。これにより正しいアセンブリがロードされることを保障できます。加えて、改ざんされていないかどうかもチェックできますので、セキュリティも向上するということのようです。

さて、キーファイルの作成とアセンブリの署名をどのように行うかですが、Visual Studio 2008, 2010(未確認ですが、たぶん 2005 も)には、プロパティページに[署名]タブがありますので、それでキーペアの作成が可能です。キーペアを作成した上でビルドすればアセンブリに署名できます。下の画像を参照してください。ちなみに以前(たぶん Visual Studio 2003 以前)は Sn.exe (厳密名ツール)Al.exe (アセンブリ リンカー) を使って手動で行っていたようです。

Visual Studio の厳密な名前キーの作成画面

上の画面で[アセンブリの署名]チェックボックスにチェックを入れ、[厳密な名前のキー ファイルを選択してください]ボックスの一覧の[<新規作成...>」を選択すると[厳密な名前キーの作成]ダイアログボックスが表示されます。

[厳密な名前キーの作成]ダイアログボックスで、新しいキーファイルの名前を入力し、さらに、必要に応じてパスワードを入力します。パスワードを指定すると、Personal Information Exchange (.pfx) ファイルが作成され、指定しないと Strong Name Key (.snk) ファイルが作成されます。[OK]をクリックすると、以下の画像のようにキーファイルが作成されます。

Visual Studio で作成したキーファイル

.pfx ファイルと .snk ファイルの中身が具体的にどのように違うかは調べていませんが、MSDN ライブラリ [キー ファイルのインポート] ダイアログ ボックス によると、キーファイルをインポートするには .pfk ファイル形式にする必要があるそうです。

アセンブリが正しくロードされるという保障も、改ざんが防止できるというセキュリティ機能も、キーファイルに含まれる秘密キーをアセンブリの提供者(ソフトウェア会社)のみが持っているという前提の上に成り立っています。従って、このキーファイルはソフトウェア会社に固有となるようにし、流出しないよう厳重に管理する必要があるそうです。

上の画像のように、キーファイルを作成してプロジェクトに配置した後は、ビルドすれば自動的にキーファイルに含まれる公開キーと秘密キーを使ってアセンブリに署名されます。

そのようにして厳密名を付けたアセンブリを参照する例を書きます。例えば HTTP モジュールを dll として bin フォルダに配置する場合を考えます。web.config でアセンブリ名、バージョン番号、カルチャ情報、公開キ���トークンを以下のように設定します

<add 
  name="HelloWorldModule" 
  type="MyHttpModule.HelloWorldModule, 
    MyHttpModule, 
    Version=1.0.0.0, 
    Culture=neutral, 
    PublicKeyToken=90cefab791a0b403"/>

アセンブリ名、バージョン番号、カルチャ情報はすぐ分かりますが、問題は公開キートークン(PublicKeyToken)です。公開キートークンとは、公開キーにハッシュをかけて抽出した値の最後の 8 バイトです。公開キーのサイズは 128 バイトもあるので、それをそのまま参照に使うと、参照しているアセンブリが 1 つ増えるたびに 128 バイトずつサイズが増えてしまうことになります。それではサイズが増えすぎて困るので、そのようにしているらしいです。

公開キートークンを取得するには Sn.exe (厳密名ツール) を利用します。コマンドラインから -T オプションを使用して以下のようにします。

sn.exe を使用して公開キートークンを取得

このようにして web.config で厳密名(アセンブリ名、バージョン番号、カルチャ情報、公開キートークン)を指定すると、アセンブリをロードする際それらを厳密にチェックし、矛盾があると実行時に以下の画像ようなエラーが出ます。

厳密名が異なる場合のエラー画面

最後に、Visual Studio のプロパティページの[署名]タブの[遅延署名のみ(Y)]について簡単に書きます。

これは組織が厳重に保護している(すなわち、一般の開発者にはアクセスが許可されていない)キーファイル(特に秘密キー)を使用しなくても、開発に支障がないようにするためのオプションです。

秘密キーがないと署名(アセンブリのハッシュを取って、それを秘密キーで暗号化した情報のアセンブリへの埋め込み)ができません。それでは開発に支障が出るので、アセンブリに署名をせずに公開キーだけを埋め込み、一時的に署名の検証を無効にして開発を進められるようにします。開発が完了した後、秘密キーにアクセスできる限られた者が、秘密キーを使って署名してリリースします。後で署名するので「遅延署名 (Delay Sign)」と言うらしいです。

詳しくは、第2回 アセンブリのアイデンティティ が参考になると思います。

Tags:

.NET Framework

About this blog

2010年5月にこのブログを立ち上げました。その後 ブログ2 を追加し、ここは ASP.NET 関係のトピックス、ブログ2はそれ以外のトピックスに分けました。

Calendar

<<  November 2019  >>
MoTuWeThFrSaSu
28293031123
45678910
11121314151617
18192021222324
2526272829301
2345678

View posts in large calendar