c#

C# で作る AIR Native Extension


Adobe AIR3 から ANE という ネイティブ拡張機能が実装されたのでWindowsで試してみました。

全部で4ページにまとめます。

今回で終わりです。


開発環境
 Windows XP SP3
 Visual Studio 2010 Express Edition( C# / VC++ )
 FlashBuilder4.6( FlexSDK4.6 )


ane-lab(AIR Native Extension is Lab codes) - Google Project Hosting にC#で作ったサンプルをコミットさせて頂きましたので、参考にして下さい。
間違ってるかもしれないので、決して鵜呑みにしないように。


4.FlashBuilderでAIRアプリを作るよ!最後だよ!


FlashBuilder4.6を使用してFlexデスクトップアプリケーション(AIR)を仕上げましょう。
先回作成したANEファイルから、ネイティブな処理を呼び出せるようになります。


Flexプロジェクトにライブラリプロジェクトを参照させ、ANEファイルをlibsディレクトリにコピーします。


air-swc-append.png


air-ane-append.png


ANEファイルをコピーしたら、Flexプロジェクトのビルドパスにある「ネイティブエクステンション」にANEファイルを追加します。



air-ane-append-project.png




ANEファイルを追加すると、ApplicationList-app.xmlファイルに

<extensions>
        <extensionID>com.chocbanana.win.ApplicationListExtension</extensionID>
    </extensions>
</application>

という記述が追加されます。

ここまで終ったらActionScriptのコーディングです。
以下参照。


[ ApplicationList.mxml ]
-------------------------------
<?xml version="1.0" encoding="utf-8"?>
<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009"
                       xmlns:s="library://ns.adobe.com/flex/spark"
                       xmlns:mx="library://ns.adobe.com/flex/mx">
   
    <fx:Script>
        <![CDATA[
            import com.chocbanana.win.ApplicationListExtension;
           
            import mx.collections.ArrayCollection;
           
           
            private var ane:ApplicationListExtension = new ApplicationListExtension();
           
            [Bindable]
            private var ar:ArrayCollection = new ArrayCollection();
           
            protected function btn_applicationList_clickHandler(event:MouseEvent):void
            {
                // TODO Auto-generated method stub
                var _ar:Array = ane.getApplicationList();
                ar.removeAll();
               
                for(var i:int=0; i<_ar.length; i++){
                    var appName:String = _ar[i] as String;
                    ar.addItem(appName);
                }
            }
        ]]>
    </fx:Script>
   
    <fx:Declarations>
        <!-- 非ビジュアルエレメント (サービス、値オブジェクトなど) をここに配置 -->
    </fx:Declarations>
    <s:Panel x="0" y="0" width="100%" height="100%" fontWeight="bold"
             title="Show Installed Applications">
        <s:VGroup x="0" y="0" width="100%" height="100%" horizontalAlign="center"
                  verticalAlign="middle">
            <s:List id="lst_ApplicationList" width="80%" height="80%" dataProvider="{ar}"
                    fontWeight="normal"/>
            <s:Button id="btn_applicationList" label="アプリケーション一覧の取得"
                      click="btn_applicationList_clickHandler(event)" fontWeight="bold"/>
        </s:VGroup>
    </s:Panel>
</s:WindowedApplication>
[ EOF ]
-------------------------------

Flexプロジェクトをビルドして下さい。
証明書は最初に作成したと思うので、もう既にありますね?


ではリリースビルドです。
リリースビルドのエクスポートをクリックして、[ 署名済みネイティブインストーラー ] を選択して下さい。
証明書のパスワードを入力して、[ 終了 ] をクリックすると、ネイティブなインストーラー(EXEファイル)が生成されます。



air-releasebuild.png


air-releasebuild02.png


インストーラーが作成されたら、それをダブルクリックして、インストールして下さい。

1番最初 に作成したC#のDLLはこのインストーラーの中や、ANEファイルの中には含まれていませんの で、アプリケーションを実行すると、異常終了します。

インストールされたアプリケーションは、C:\ProgramFiles\ApplicationListにありますので、そのディレクトリを開いて、 C#のDLLのみを手動でコピーします。


air-dll-copy.png


再度、AIRアプリケーションを実行してみましょう。
無事に実行が終ると、インストールされているアプリケーションの一覧が表示されると思います。


air-native-application.png




お疲れ様でした。


Appendix - インストーラーについて -

ANEを利用したAIRアプリケーションでは、今現在FlashBuilderにてC#のDLLを含めてインストーラー(配布形式)にまとめることが出来 ません。
で、これをどうしたら良いか…という点なのですが、AIRアプリケーションにはサイレントインストールさせるためのインストールオ プションがあると思います。

で、このインストールオプションと、Windowsのもう1つのインストール形式である、MSIパッケージを組み合わせれば、全てを同梱した形で配布出来 るようになると思います。

これを行うには、SharpDevelopとWixを利用すると、msiパッケージを作成出来るので、そちらで(XMLを書いて)msiパッケージとして 1まとめにし、配布する…というのが妥当だと思います。

あ、高価なインストーラー作成アプリを使うなら、この項目は必要ないです。
参考程度に留めておいて下さい。

C# で作る AIR Native Extension


Adobe AIR3 から ANE という ネイティブ拡張機能が実装されたのでWindowsで試してみました。

全部で4ページにまとめます。

今回はその3ページ目、あと一息です。


開発環境
 Windows XP SP3
 Visual Studio 2010 Express Edition( C# / VC++ )
 FlashBuilder4.6( FlexSDK4.6 )


ane-lab(AIR Native Extension is Lab codes) - Google Project Hosting にC#で作ったサンプルをコミットさせて頂きましたので、参考にして下さい。
間違ってるかもしれないので、決して鵜呑みにしないように。



3.FlashBuilder でSWCとAIRアプリを作るよ!


FlashBuilder4.6使用して、Flexライブラリ(SWC)とFlexデスクトップアプリケーション(AIR)を作成していきます。
2つ同時に作業した方が分かりやすいかもしれません。

これで、SWCからadtコマンドでANEファイルを生成し、ネイティブな処理を呼び出せるようになります。


ではまずAIRアプリのレイアウト及び、証明書の作成から。
先に証明書を作成しておかないと、SWCを作るときにメンドイのです。

画面レイアウトと、証明書が出来たらSWC(Flexライブラリ)を作ります。


air-app-layout.png




air-app-syomeisyo.png




Flexライブラリプロジェクトを作りましょう。

プロジェクトのプロパティ設定で、[ Flexライブラリコンパイラー ] -> [ Aodbe AIRライブラリを含める ] のチェックがONになっていることを確認して下さい。

binディレクトリに VC++で作成したDLLと、先程作ったFlexアプリの証明書を入れておきます。

次に、srcディレクトリに ASのパッケージと、assetsディレクトリ。assetsディレクトリ内にdescriptor.xmlを作成します。

内容は以下の通りで。

[ assets/descriptor.xml ]
------------------------
<extension xmlns="http://ns.adobe.com/air/extension/3.1">
    <id>com.chocbanana.win.ApplicationListExtension</id>
    <versionNumber>1.0</versionNumber>
    <platforms>
        <platform name="Windows-x86">
            <applicationDeployment>
                <nativeLibrary>libApplicationList.dll</nativeLibrary>
                <initializer>initializer</initializer>
                <finalizer>finalizer</finalizer>
            </applicationDeployment>
        </platform>
    </platforms>
</extension>
[ EOF ]
------------------------

このXMLファイルが書けたら、binディレクトリにコピーしておいて下さい。



[ com.chocbanana.win.ApplicationListExtension.as ]
------------------------
package com.chocbanana.win{
   
    import flash.events.Event;
    import flash.events.EventDispatcher;
    import flash.events.IEventDispatcher;
    import flash.events.StatusEvent;
    import flash.external.ExtensionContext;
   
    public class ApplicationListExtension extends EventDispatcher {

        private var _ExtensionContext:ExtensionContext;
       
        public function ApplicationListExtension(target:IEventDispatcher=null) {
            //TODO: implement function
            super(target);
            _ExtensionContext = ExtensionContext.createExtensionContext("com.chocbanana.win.ApplicationListExtension", null);
        }

        public function dispose():void{
            _ExtensionContext.dispose();
        }
        public function getApplicationList():Array {
            return _ExtensionContext.call("GetApplicationList") as Array;
        }
    }
}
[ EOF ]
------------------------

ASファイル内で、FREのContextを作成し、callメソッドで、DLLの関数を呼びます。
戻り値はFREObectで指定した戻り値の型ですね。

で、これのビルドを行うとSWCファイルが出来ると思います。

SWCファイルが出来たら、それをそのまま選択して、右クリック、コピーして貼り付けして下さい。
名前の拡張子をzipにします。

そのzipファイルをダブルクリックして、解凍すると、中にlibrary.swfファイルがあるので、それもbinディレクトリに含めます。
解凍したzipファイルと、フォルダは要らないので、削除しておいて下さい。



air-swc-package.png




ここから、ADTコマンドを使って、ANEファイルを作ります。
FlashBuilderの [ 実行 ] -> [ 外部ツール ] -> [ 外部ツールの構成 ] を選択して、以下の様に。


air-adt-command.png





[ ADTコマンドの引数(改行は挟まず、スペースで) ]
------------------------
-package -storetype pkcs12 -keystore test.p12 -target ane libApplicationList_Extension.ane descriptor.xml -swc  libApplicationList_Extension.swc -platform Windows-x86 library.swf  libApplicationList.dll
------------------------

実行すると証明書のパスワードを聞いてくるので、入力してEnter。
libApplicationList_Extension.aneファイルがbinディレクトリに生成されます。

生成されたANEファイルをコピーして、AIRアプリのlibsディレクトリにコピーして下さい。

ひとまずはココまで。
次回はAIRアプリの仕上げです。

C# で作る AIR Native Extension


Adobe AIR3 から ANE という ネイティブ拡張機能が実装されたのでWindowsで試してみました。

全部で4ページにまとめます。

今回はその2ページ目です。


開発環境
 Windows XP SP3
 Visual Studio 2010 Express Edition( C# / VC++ )
 FlashBuilder4.6( FlexSDK4.6 )


ane-lab(AIR Native Extension is Lab codes) - Google Project Hosting にC#で作ったサンプルをコミットさせて頂きましたので、参考にして下さい。
間違ってるかもしれないので、決して鵜呑みにしないように。



2.VC ++でDLL作るよ!


Visual Studio 2010 Express Edition VC++ を使用して、AIRライブラリ(SWC)からコールするためのDLLを作成していきます。
VC++でCLRライブラリプロジェクトを選択してください。



vc-project.png




先回作成したC#のDLLとtlbファイル、及びAIR SDKに同梱されているヘッダーファイルとlibファイルをプロジェクトに追加します。



cs-dll.png




air-lib.png




project-files.png




で、ヘッダーファイル2つとメインのCPPファイルを書きます。


[ Stdafx.h ]
------------------------------
// stdafx.h : 標準のシステム インクルード ファイルのインクルード ファイル、または
// 参照回数が多く、かつあまり変更されない、プロジェクト専用のインクルード ファイル
// を記述します。

#pragma once
#include "FlashRuntimeExtensions.h";

[ EOF ]
------------------------------


[ libApplicationList.h ]
------------------------------
// libApplicationList.h
#pragma once
#include <stdlib.h>
#include <wchar.h>
#include <windows.h>

#using <mscorlib.dll>
#using "ApplicationList_CS.dll"

using namespace System;
using namespace System::Runtime::InteropServices;
using namespace System::Collections;
using namespace ApplicationList_CS;

namespace libApplicationList {

    public ref class Class1
    {
        // TODO: このクラスの、ユーザーのメソッドをここに追加してください。
    };
}
[ EOF ]
------------------------------

[ libApplicationList.cpp ]
------------------------------
// これは メイン DLL ファイルです。

#include "stdafx.h"
#include "libApplicationList.h"


extern "C" __declspec(dllexport) void initializer(void** extData, FREContextInitializer* ctxInitializer, FREContextFinalizer* ctxFinalizer);
extern "C" __declspec(dllexport) void finalizer(void** extData);

void ConvertToUni(String^ _str, char* szBuf, const int bufLen)
{
    IntPtr ptr = Marshal::StringToHGlobalUni(_str);
    const char* str = static_cast<const char*>(ptr.ToPointer());
    WideCharToMultiByte(CP_UTF8, 0, (wchar_t*)str, -1, szBuf, bufLen, NULL, NULL);
    Marshal::FreeHGlobal(ptr);
}

FREObject GetApplicationList(FREContext ctx, void* funcData, uint32_t argc, FREObject argv[]){
    FREObject resultObj;
    FREObject elementObj;

    ApplicationList_CS::AppList_CS^ cs = gcnew ApplicationList_CS::AppList_CS();
    ArrayList^ list = gcnew ArrayList();
    list = cs->getApplicationNames();

    // Application Name buffer
    const int bufLen = 1024;
    char szBuf[bufLen];

    const int count = list->Count;

    FRENewObject((const uint8_t*)"Array", 0, NULL, &resultObj, NULL);
    FRESetArrayLength(resultObj, count);

    for(int i=0; i<count; i++){
        String^ str = (String^)list[i];
        ConvertToUni(str, szBuf, bufLen);
        FRENewObjectFromUTF8(strlen(szBuf)+1, (const uint8_t *)szBuf, &elementObj);
        FRESetArrayElementAt(resultObj, i, elementObj);
    }

    return resultObj;
}
void contextFinalizer(FREContext ctx)
{
    return;
}

void contextInitializer(void* extData, const uint8_t* ctxType, FREContext ctx, uint32_t* numFunctions, const FRENamedFunction** functions)
{
    *numFunctions = 1;
    FRENamedFunction*  func= (FRENamedFunction*)malloc(sizeof(FRENamedFunction) * (*numFunctions));

    func[0].name = (const uint8_t*) "GetApplicationList";
    func[0].functionData = NULL;
    func[0].function = &GetApplicationList;

    *functions = func;
}

void initializer(void** extData, FREContextInitializer* ctxInitializer, FREContextFinalizer* ctxFinalizer)
{
    *ctxInitializer = &contextInitializer;
    *ctxFinalizer = &contextFinalizer;
}

void finalizer(void** extData)
{
   
}
[ EOF ]
------------------------------

ポイントは

contextInitializerのFRENamedFunctionでActionScriptからCallされる名前を定義しているのと、ネイ ティブの処理として

Marshal::StringToHGlobalUni(_str);
WideCharToMultiByte(CP_UTF8, 0, (wchar_t*)str, -1, szBuf, bufLen, NULL, NULL);

を使用している点でしょう。

WindowsではOS内部の値はUnicodeで格納されているため、UnicodeからUTF8に変換しないと、AIRに持っていったときに文字化け して表示されます。
ですので、WMIやレジストリなどから”マルチバイト文字”を引っ張ってくるときは要注意です。

あとは、ArrayList^ listに格納された文字列を1つ1つ分解して、FREObjectの構造体に突っ込む作業です。
全部突っ込んだらreturnで戻して終わりです。

AIR側に渡すために各パラメータを細かく分解してから、それぞれ格納し直す作業がメンドイですね…。


では、次回はFlashBuilderでFlexライブラリプロジェクト(SWC)とFlexプロジェクト(デスクトップ AIRアプリ)を作ります。

C# で作る AIR Native Extension



ApplicationList.png


Adobe AIR3 から ANE という ネイティブ拡張機能が実装されたのでWindowsで試してみました。

全部で4ページにまとめます。

今回はその1ページ目です。


開発環境
 Windows XP SP3
 Visual Studio 2010 Express Edition( C# / VC++ )
 FlashBuilder4.6( FlexSDK4.6 )


まぁ、アレです、各プラットフォーム固有の機能が使えたりするというヤツです。
Windowsならね、わざわざAIRでやらなくてもね、C#で全部出来るからね…、あんまり意味無いかもしれませんけどね。 orz
フロントエンドをAIRでまとめて、各プラットフォームごとにANEで固有機能を実装して…という具合にアプリを作るなら、ANE はアリです。

が、結構かなりシビアにキツイです。キツイっス…。orz

レジストリにアクセスしたり、WMI(Windows Management Instrumentation)を使ってハードウェア・ソフト ウェアの情報を取得したり、C#のAPIに丸投げしたり出来ま す。はい。

ane-lab(AIR Native Extension is Lab codes) - Google Project Hosting にC#で作ったサンプルをコミットさせて頂きましたので、参考にして下さい。
間違ってるかもしれないので、決して鵜呑みにしないように。


で、作り方。


作るのは4つ。

C#クラスライブラリ(C#のDLL)
VC++のCLRのクラスライブラリ(VC++ CLR/DLL)
Flexライブラリプロジェクト(SWC)
Flexプロジェクト(デスクトップ AIRアプリ)

です。



1.C#でDLL作るよ!

template.png

最初にキモを抑えて起きましょう。
C#で作ったDLLをVC++から参照して、VC++で作るDLLの中でFlashRuntimeExtensionsを組み込むことにより、AIRアプ リに通知することが出来るようになります。

えー???ですよね。C#からVC++のDLLをImportする…のではなく、その逆です。VC++からC#のDLLを参照しま す。
イヤーな予感がします。はい。


キモはコレ!
VC++から参照出来るように、「アセンブリをCOM参照可能にする」のチェックと、「COM相互運用機能の登録」のチェックをONにしておきます。
これでVC++からCOM経由でC#のクラスやそのメンバを参照出来るようになります。

CS_Setting01.png
CS_Setting02.png

ここで1つ注意。

COM 参照可能にする、とかCOM相互運用機能の登録とありますが、これはWindowsのCOMという機能を利用/経由(.NET/C#がCOMにラップされ る)して、VC++から.NET Objectを操作出来るようにする(マネージドからアンマネージにアクセスする)…というものです。

詳しくは「COM ・ RCW ・ 参照カウント 」などのキーワードで調べてください。

C#(.NET)にはGC(ガベージコレクション)という機能があります。が、COMはMSのドキュメントによると、「COM 自身は、オブジェクトが不要になったと判断しても、メモリから自動的にオブジェクトを削除するわけではありません。不要なオブジェクトの削除は、オブジェ クトのプログラマが行います。プログラマは、参照カウントに基づいて、オブジェクトを削除できるかどうかを判断します。」とあります。(http://msdn.microsoft.com/ja-jp/library/4947zb56%28v=vs.100%29.aspx)


完全にGCと相反していますね…。C#でどうやんのよ、それ…。

えー、通常C#で開発をする場合、変数にスコープがあり、このスコープから抜けるとGCの対象になる(実際にGCが何時行われるかはプログラマからは分か りませんが)…のですが、VC++から参照させるために、C#のライブラリをCOM参照させる必要があるため、C#側のインスタン スを明示的に解放してやる必要があります。

MSのドキュメントにも「すべてのア ウトポ インタパ ラメータを明 示的にNULLに設定する必要があります。」 と記載されています。(http://msdn.microsoft.com/ja-jp/library/ms686638.aspx)& rdquo;すばらしいGoogle翻訳”。

えー、ですので、C#側でも不必要な変数にはNULLを入れてやって下さい。
そうしないと、AIRアプリのプロセスが正常に終了しない場合があります。

あと、string[] とかは使わない方が良いです。使うなら、List<string>とかCollection.Genericを使いましょ う。



では、サンプルです。
OS内にインストールされているアプリケーション一覧を表示します。
これで、えろげとかもバッチリだね!(ぉ

プロジェクト名は ” ApplicationList_CS ” です。


[ App.cs ]
----------------------------------------
using System;
using System.Collections;
using System.Collections.Generic;
using System.Management;
using Microsoft.Win32;

namespace ApplicationList_CS {
    class App {
        //HotFix情報
        private const string root = "root\\CIMV2";
        private const string query = "SELECT * FROM Win32_QuickFixEngineering WHERE ServicePackInEffect <> '' AND InstalledBy <> ''";

        public ArrayList getApplicationNames() {
            List<string> hotfix = new List<string>();
            List<string> app = new List<string>();

            ManagementObjectSearcher searcher = new ManagementObjectSearcher(root, query);

            try {
                foreach (ManagementObject queryObj in searcher.Get()) {
                    hotfix.Add((string)queryObj["HotFixID"]);
                }
            } catch (Exception e) {
                Console.WriteLine(e.StackTrace);
            } finally {
                searcher = null;
            }
            RegistryKey regKey = null;
            RegistryKey _subRegKey = null;
            try {
                //アプリケーション情報
                string reg = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\";
                string regValue = "DisplayName";
                regKey = Registry.LocalMachine.OpenSubKey(reg);
                string[] subkey = regKey.GetSubKeyNames();

                foreach (string keyName in subkey) {

                    _subRegKey = Registry.LocalMachine.OpenSubKey(reg + keyName);
                    string value = (string)_subRegKey.GetValue(regValue);

                    if (value != null) {
                        for (int j = 0; j < hotfix.Count; j++) {
                            if (value.Contains(hotfix[j].ToString())) {
                                ;
                            } else if (value.Contains("KB")) {
                                ;
                            } else {
                                app.Add(value);
                                break;
                            }
                        }
                    }
                    //value = null;
                }
                app.TrimExcess();

                reg = null;
                regValue = null;
                subkey = null;
            } catch (NullReferenceException e) {
                Console.WriteLine(e.StackTrace);
            } finally {
                regKey.Close();
                _subRegKey.Close();
            }

            app.Sort();

            for (int i = 0; i < (app.Count - 1); i++) {
                try {
                    if (app[i].Equals(app[i + 1])) {
                        app.RemoveAt(i);
                    }
                } catch (Exception ex) {
                    Console.WriteLine(ex.Message);
                }
            }
            app.TrimExcess();
            hotfix = null;
            return new ArrayList(app);
        }
    }
}
[ EOF ]
----------------------------------------



[ AppList_CS.cs ]
----------------------------------------
using System;
using System.Collections;

namespace ApplicationList_CS {
    public class AppList_CS {

        private App app = new App();
       
        public ArrayList getApplicationNames() {
            return app.getApplicationNames();
        }
    }
}
[ EOF ]
----------------------------------------


やっちゃダメなダメ・コードも記載しておきます。絶対やっちゃダメだよw
配列多すぎ。orz


[ アプリケーション名・バージョン・HotFix情報を取得する変なC#コード ]
----------------------------------------
private void setApplicationReg() {
            string[] val1 = null;
            string[] val2 = null;
            string[] val3 = null;

            ArrayList application;
            ArrayList applicationVersion;
            ArrayList hotfix;

            application = new ArrayList(); // application
            applicationVersion = new ArrayList(); // applicationVersion
            hotfix = new ArrayList(); // HotFix

            try {
                //HotFix情報
                string root = "root\\CIMV2";
                string query = "SELECT * FROM Win32_QuickFixEngineering WHERE ServicePackInEffect <> '' AND InstalledBy <> ''";
                ManagementObjectSearcher searcher = new ManagementObjectSearcher(root, query);
                int cnt = 0;
                foreach (ManagementObject queryObj in searcher.Get()) { cnt++; }
                val1 = new string[cnt];
                int i=0;
                foreach (ManagementObject queryObj in searcher.Get()) {
                    val1[i] = (string)queryObj["HotFixID"];
                    i++;
                }
            }catch(Exception e){
                string error = e.StackTrace;
                //Console.WriteLine(e.StackTrace);
            }
            try {
                //アプリケーション情報
                string reg = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\";
                string regValue = "DisplayName";
                RegistryKey regKey = Registry.LocalMachine.OpenSubKey(reg);
                string[] subkey = regKey.GetSubKeyNames();
                int max = subkey.Length;
                regKey.Close();

                //アプリケーションの総数
                val2 = new string[max];
                val3 = new string[max];
                RegistryKey _subRegKey = null;
                int i = 0;
                foreach (string keyName in subkey) {
                    _subRegKey = Registry.LocalMachine.OpenSubKey(reg + keyName);
                    if ((string)_subRegKey.GetValue(regValue) != null) {
                        val2[i] = (string)_subRegKey.GetValue("DisplayName");
                        val3[i] = (string)_subRegKey.GetValue("DisplayVersion");
                        i++;
                    }
                    _subRegKey.Close();
                }
            } catch (NullReferenceException e) {
                string error = e.StackTrace;
                //Console.WriteLine(e.StackTrace);
            }
            try {
                for (int i = 0; i < val2.Length; i++) {
                    if(val2[i] != null){
                        for(int j = 0; j < val1.Length; j++){
                            if (val2[i].Contains(val1[j])) {
                                hotfix.Add(val2[i]);
                                val2[i] = null;
                                val3[i] = null;
                                break;
                            }else if(val2[i].Contains("KB")){
                                hotfix.Add(val2[i]);
                                val2[i] = null;
                                val3[i] = null;
                                break;
                            }
                        }
                    }
                }
                foreach(string _val in val2){
                    if( _val == null){
                        ;
                    } else {
                        application.Add(_val);
                    }
                }
                foreach (string _val in val3) {
                    if (_val == null) {
                        ;
                    } else {
                        applicationVersion.Add(_val);
                    }
                }
                application.TrimToSize();
                applicationVersion.TrimToSize();
                hotfix.TrimToSize();

                application.Sort();
                for (int i = 0; i < application.Count; i++ ) {
                    try {
                        if (application[i].Equals(application[i + 1])) {
                            application.RemoveAt(i);
                        }
                    }catch(Exception){
                   
                    }
                }
                application.TrimToSize();

                this.application = new string[application.Count];
                this.applicationVersion = new string[applicationVersion.Count];
                this.hotfix = new string[hotfix.Count];

                for (int i = 0; i < application.Count; i++) { this.application[i] = (string)application[i]; }
                for (int i = 0; i < applicationVersion.Count; i++) { this.applicationVersion[i] = (string)applicationVersion[i]; }
                for (int i = 0; i < hotfix.Count; i++) { this.hotfix[i] = (string)hotfix[i]; }
            } catch (NullReferenceException ne) {
                Console.WriteLine(ne.StackTrace);
            }
        }
----------------------------------------



もうちょっとだけ続くんじゃ。


ダメじゃない方のコードをコンパイルすると、DLLとtlbファイルが出来ますので、その2つをVC++のプロジェクトで使用します。

で、結局このDLLってどうなるのっ?ってことなのですが、「Visual Studioコマンドプロンプト」というのがあります。
このDOS窓で、oleviewと入力すると、COMを使用しているアプリケーションやコンポーネントが見れたりします。

oleview.png


えー、2ページ目に続きますが、今度はVC++を使って、AIRアプリとを繋ぐDLLを作成していきます。


要件
  ユーザーが使用している任意のアプリケーションの起動・終了時間の把握
 

実装内容
 1. Windowsサービスにて実装
 2. ActiveWindowを監視してWindowTitleを取得
 3. ActiveWindow取得時にHookは未使用(Timerによる監視)
 4. WM_CBT/WM_SHELLともに、Windowsサービスでは監視用Windowを持たない為NG
 5. ActiveWindowを取得後、そのProcessが終了した時点でProcess名などのプロパティを取得



[ myProcessWatch.cs ]
-------------
using System;
using System.Collections.Generic;
using System.Collections;
using System.Text;
using System.Runtime.InteropServices;
using System.Diagnostics;

namespace pcLogWatch {
    public class myProcessWatch{
        private static Process process;
        public static ArrayList g_list;

        [DllImport("user32.dll")]
        public static extern IntPtr GetForegroundWindow();

        [DllImport("user32.dll")]
        public static extern int GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId);

        [DllImport("user32.dll")]
        public static extern int GetWindowText(IntPtr hWnd, StringBuilder lpStr, int nMaxCount);

        [DllImport("user32.dll")]
        public static extern int GetWindowTextLength(IntPtr hWnd);

        [DllImport("user32.dll")]
        public static extern bool IsWindowVisible(IntPtr hWnd);

        private static string windowTitle;

        public myProcessWatch() {
            g_list = new ArrayList();
        }
        [System.Security.Permissions.PermissionSet(System.Security.Permissions.SecurityAction.Demand, Name = "FullTrust")]
        private void processExited(object sender, EventArgs e) {
        
            //Java側で実装したサービス・モデル(JavaBean)
            ims.processModel model = new ims.processModel();

            Process process = (Process)sender;
            model.startTime = process.StartTime.ToString();
            model.exitTime = process.ExitTime.ToString();
            model.productName = process.ProcessName;
            model.windowTitle = process.MainWindowTitle;
            g_list.Add(model);
        }

        [System.Security.Permissions.PermissionSet(System.Security.Permissions.SecurityAction.Demand, Name = "FullTrust")]
        internal void getFocusMessage() {
            // アクティブなウィンドウハンドルの取得
            IntPtr hWnd = GetForegroundWindow();

            if (IsWindowVisible(hWnd)) {

                int winLen = GetWindowTextLength(hWnd);

                StringBuilder sb = new StringBuilder(winLen+1);
                GetWindowText(hWnd, sb, sb.Capacity);

                if (windowTitle != sb.ToString()) {
                    int id = 0;
                    // ウィンドウハンドルからプロセスIDを取得
                    GetWindowThreadProcessId(hWnd, out id);

                    try {
                        if (id != Process.GetCurrentProcess().Id) {
                            process = Process.GetProcessById(id);
                            string message = process.MainWindowTitle;

                            if (message != "") {
                                process.EnableRaisingEvents = true;
                                process.Exited += new EventHandler(processExited);
                            }
                        }
                    } catch (Exception e) {
                        Console.WriteLine(e.StackTrace);
                    }

                    windowTitle = sb.ToString();
                }
            }
        }
    }
}
[ EOF ]
-------------


[ Program.cs ]
-------------
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using pcLogWatch;

namespace pcLogWatch {
    class Program : System.ServiceProcess.ServiceBase {

        //データ送信用Timer
        //private static System.Threading.Timer sendDataTimer;
        
        //データ送信用Controller
        //private static Controller ctrl = new Controller();
        
        //WindowTitle監視用Timer
        private static System.Threading.Timer callbackTimer;
        private static myProcessWatch pWatch = new myProcessWatch();

        static void Main(string[] args) {
            System.ServiceProcess.ServiceBase[] ServicesToRun = new System.ServiceProcess.ServiceBase[] { new Program() };
            System.ServiceProcess.ServiceBase.Run(ServicesToRun);
            Console.ReadLine();
        }
        protected override void OnStart(string[] args) {
            base.OnStart(args);

            //デフォルトは100msで監視
            //まぁ500msくらいでも充分でしょうね。
            TimerCallback timerDelegate = new TimerCallback(OnWatchingTimer);
            callbackTimer = new System.Threading.Timer(timerDelegate, null, 0, 100);
            
            //まぁ後はテキトーに別のTimerCallbackを使用して、適度な時間間隔でサーバー側に送信すればおk
            //TimerCallback sendDataDelegate = new TimerCallback(OnDataSendTimer);
            //sendDataTimer = new Timer(sendDataDelegate, null, 0, ((1000 * 60) * int.Parse(Properties.Settings.Default.SendDataTimer)));

        }
        protected override void OnStop() {
            callbackTimer = null;
            //sendDataTimer= null;
            //ctrl = null;
            base.OnStop();
        }
        private void OnWatchingTimer(object o) {
            pWatch.getFocusMessage();
        }
        /*
        private void OnDataSendTimer(object o) {
            if(myProcessWatch.g_list.Count > 0){
                ctrl.sendData(System.Environment.MachineName, bean, myProcessWatch.g_list);
                myProcessWatch.g_list.Clear();
            }
        }
        */
    }
}
[ EOF ]
-------------


というかですね、WM_CBT Hook/WM_SHELL Hookともにやってみたのですが、Timer使った方がキレイに取得出来るんですよね。
だから無理してAPI Hookする必要も無いのかな...と。
クリップボードの監視とかだとHookする必要が出てきますね。


Visual Studio 2010 beta2で連携してみました。

...使えねー。......使いたくねー。
速度的には問題ないと思うんですが、如何せん作りにくいです。もの凄く。
まぁサーバー側がMSから脱却できるってだけでも御の字でしょうね。
何が悲しくてIISなんぞ...。


先ずはサーバーサイド。Apache CXFです。大体コ コと一緒。

必要になるライブラリ一覧(若干余計なものも混じってますが)

[ WEB-INF\lib ]
---------------
aopalliance-1.0.jar
backport-util-concurrent.jar
cglib-nodep-2.1_3.jar
commons-beanutils.jar
commons-digester.jar
commons-fileupload.jar
commons-io.jar
commons-logging.jar
cxf-2.1.5.jar
FastInfoset-1.2.3.jar
flex-messaging-common.jar
flex-messaging-core.jar
flex-messaging-remoting.jar
geronimo-annotation_1.0_spec-1.1.1.jar
geronimo-servlet_2.5_spec-1.2.jar
geronimo-stax-api_1.0_spec-1.0.1.jar
geronimo-ws-metadata_2.0_spec-1.1.2.jar
ibatis-2.3.4.726.jar
jaxb-api-2.1.jar
jaxb-impl-2.1.9.jar
lib.txt
neethi-2.0.4.jar
opensaml-1.1.jar
pjl-comp-filter-1.6.5.jar
postgresql-8.3-603.jdbc4.jar
saaj-api-1.3.jar
saaj-impl-1.3.2.jar
serializer-2.7.1.jar
spring-aop.jar
spring-beans.jar
spring-context-support.jar
spring-context.jar
spring-core.jar
spring-jdbc.jar
spring-orm.jar
spring-tx.jar
spring-web.jar
sql-map-2.dtd
sql-map-config-2.dtd
wsdl4j-1.6.2.jar
wss4j-1.5.7.jar
wstx-asl-3.2.6.jar
xalan-2.7.1.jar
xml-resolver-1.2.jar
XmlSchema-1.4.5.jar
xmlsec-1.4.2.jar
---------------

[ web.xml ]
---------------
<?xml version="1.0"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
    <display-name>Service</display-name>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:/com/chocbanana/ws/cxf.xml</param-value>
    </context-param>

    <!-- Filter config with GZIP -->
    <filter>
        <filter-name>CompressingFilter</filter-name>
        <filter-class>
            com.planetj.servlet.filter.compression.CompressingFilter
        </filter-class>
        <init-param>
            <param-name>debug</param-name>
            <param-value>false</param-value>
        </init-param>
        <init-param>
            <param-name>statsEnabled</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>

    <filter-mapping>
        <filter-name>CompressingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    

    <servlet>
        <servlet-name>context</servlet-name>
        <servlet-class>org.springframework.web.context.ContextLoaderServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet>
        <servlet-name>CXFServlet</servlet-name>
        <servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>
        <load-on-startup>2</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>CXFServlet</servlet-name>
        <url-pattern>/services/*</url-pattern>
    </servlet-mapping>


    <mime-mapping>
        <extension>wsdl</extension>
        <mime-type>text/xml</mime-type>
    </mime-mapping>
</web-app>
[EOF]
---------------

[ cxf.xml ]
---------------
<beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xmlns:jaxws="http://cxf.apache.org/jaxws"
      xsi:schemaLocation="http://www.springframework.org/schema/beans
                     http://www.springframework.org/schema/beans/spring-beans.xsd
                     http://cxf.apache.org/jaxws
                     http://cxf.apache.org/schemas/jaxws.xsd">

    <import resource="classpath:META-INF/cxf/cxf.xml" />
    <import resource="classpath:META-INF/cxf/cxf-extension-soap.xml"/>
    <import resource="classpath:META-INF/cxf/cxf-servlet.xml" />

    <jaxws:endpoint id="sl" implementor="com.chocbanana.ws.SilverLight" address="/SilverLight" />
</beans>
[EOF]
---------------

[ ISl.java ]
---------------
package com.chocbanana.ws;

import javax.jws.WebMethod;

public interface ISl {
    
    @WebMethod
    public String getHello(String str);
}
[EOF]
---------------

[ SilverLight.java ]
---------------
package com.chocbanana.ws;

import javax.jws.WebMethod;

public class SilverLight implements ISl {
    
    @WebMethod
    public String getHello(String str){
        return ("Hello " + str + ", Wellcome to SilverLight world !");
    }
}
[EOF]
---------------

ここまでがサーバー側。問題なしでしょう。



ここからSilverLight。
VisualStudio 2010 beta2.0なので、製品版とは多少異なるかもしれません。あしからず。


SilverLightのプロジェクトを作成。今回はC#で。っていうかVBなんぞで作って(作られて)堪るか。
SilverLightでの最大の注釈。Svcutil.exeでのスタブ作成したコードは使用出来ません。
[ サービス参照の追加 ] より、WSDLのURIを指定して下さい。

さて困りました。スタブを作成して開発していた時は普通に呼べたメソッドがCall出来ません。
(もうこの時点で9割方どーでも良くなりました。Flexで良いやn(ry)

解説するのもメンドクサクナッタノデ、ソースコピペして置きます。
ご参考までに。

[ My Documents\Visual Studio 2010\Projects\sa1\sa1 ]
---------------
App.xaml
App.xaml.cs
Bin
MainPage.xaml
MainPage.xaml.cs
obj
Properties
sa1.csproj
sa1.csproj.user
Service References
ServiceReferences.ClientConfig
---------------


[ MainPage.xaml ]
---------------
<UserControl x:Class="sa1.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400">

    <Grid x:Name="LayoutRoot" Background="White">
        <Button Content="Button" Height="23" HorizontalAlignment="Left" Margin="155,119,0,0" Name="btn_test" VerticalAlignment="Top" Width="75" Click="btn_test_Click" />
        <TextBox Height="23" HorizontalAlignment="Left" Margin="12,90,0,0" Name="txt_box" VerticalAlignment="Top" Width="376" />
    </Grid>
</UserControl>
[ EOF ]
---------------


えー、見て分かるように、Webサービスの呼び出しがEventDrivenです。
普通に txt_box.Text = scl.getHello(txt_box.Text); ってやりたいだけでこのコード量。
泣けます。
readConfig();はちょっとしたテクニック...なのかも。


[ MainPage.xaml.cs ]
---------------
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Data;
using System.Xml;
using System.Xml.Linq;
using System.ServiceModel;

namespace sa1
{
    public partial class MainPage : UserControl
    {
        private ServiceRef.SilverLightClient scl;
        private ServiceRef.getHello hello;

        public MainPage()
        {
            InitializeComponent();
        }

        #region CallWebService
        private void CallWebService(string value)
        {
            scl = this.GetSilverLightClient();
            hello = this.GetHelloClient();
            
            scl.getHelloCompleted += new EventHandler<ServiceRef.getHelloCompletedEventArgs>(client_GetHelloCompleted);

            hello.Body.arg0 = value;
            scl.OpenAsync();
            scl.getHelloAsync(hello);
            scl.CloseAsync();

        }
        private ServiceRef.SilverLightClient GetSilverLightClient() {
            ServiceRef.SilverLightClient scl;
            string uri = readConfig();

            if (uri != "")
            {
                if (uri.StartsWith("https://") == true)
                {
                    scl = new ServiceRef.SilverLightClient("SilverLightPort_Security", new EndpointAddress(uri));
                }
                else
                {
                    scl = new ServiceRef.SilverLightClient("SilverLightPort", new EndpointAddress(uri));
                }
            }
            else
            {
                scl = new ServiceRef.SilverLightClient();
            }
            return scl;
        }
        private ServiceRef.getHello GetHelloClient()
        {
            ServiceRef.getHello hello;
            hello = new ServiceRef.getHello(new ServiceRef.getHelloBody());
            return hello;
        }
        private string readConfig()
        {
            Uri docUri = System.Windows.Browser.HtmlPage.Document.DocumentUri;
            Uri svcUri = new Uri(docUri, "/services/SilverLight");
            return svcUri.ToString();
        }
        private void client_GetHelloCompleted(object sender, ServiceRef.getHelloCompletedEventArgs e)
        {
            try
            {
                txt_box.Text = e.Result.Body.@return.ToString();
            }catch(Exception ex){
                MessageBox.Show(ex.InnerException.Message);
            }
        }
        #endregion

        private void btn_test_Click(object sender, RoutedEventArgs e)
        {
            string value = txt_box.Text;

            if (value == "")
            {
                value = "Guest User";
            }
            this.CallWebService(value);
        }
    }
}
[ EOF ]
---------------

なんかさー、仕様的に、EventDriven持ってくるとかオカシくね?
普通にCallしたいだけなんよ?
あー、あれか、Webサービス呼び出し用のコントローラー作って隠蔽しないとダメなのか。
そっか、そっか、そっか...。orz

■ 追記
えー、隠蔽出来ませんでした。Viewとなる.csに直書きです。もう嫌です。なんでこんなクソ仕様...。orz



このファイル (ServiceReferences.ClientConfig) は [Webサービス参照] 時にService Referencesディレクトリと共に生成されます。
scl = new ServiceRef.SilverLightClient("SilverLightPort", new EndpointAddress(uri));
部分にて、Binding名を参照しております。

■追記
あー、一応HTTPSにも対応ですね。

[ ServiceReferences.ClientConfig ]
---------------
<configuration>
    <system.serviceModel>
        <bindings>
            <basicHttpBinding>
                <binding name="SilverLightServiceSoapBinding" maxBufferSize="2147483647"
                    maxReceivedMessageSize="2147483647">
                    <security mode="None" />
                </binding>
                <binding name="SilverLightServiceBinding_Security" maxBufferSize="2147483647"
                    maxReceivedMessageSize="2147483647">
                    <security mode="Transport" />
                </binding>
            </basicHttpBinding>
        </bindings>
        <client>
            <endpoint address="http://127.0.0.1:8080/services/SilverLight"
                binding="basicHttpBinding" bindingConfiguration="SilverLightServiceSoapBinding"
                contract="ServiceRef.SilverLight" name="SilverLightPort" />
            <endpoint address="https://127.0.0.1:8443/services/SilverLight"
                binding="basicHttpBinding" bindingConfiguration="SilverLightServiceBinding_Security"
                contract="ServiceRef.SilverLight" name="SilverLightPort_Security" />
        </client>
    </system.serviceModel>
</configuration>
[ EOF ]
---------------

出来上がりです。

cxf_silverlight.jpg

その2です。

HTTPS での GZip圧縮が出来たので、掲載しておきます。
WCFクライアントだけね。サーバー側は前 回と変わりません(pjl-comp-filterでGzipフィルタかけてるだけ )ので。
注意点だけ赤色にしておきます。


以下参照。

[ Program.cs ]
-----------------
using System;
using System.Collections.Generic;
using System.Text;
using ims;
using System.ServiceModel;
using System.ServiceModel.Channels;
using Microsoft.ServiceModel.Samples;
using System.Net;

namespace WCF_TEST2 {
    class Program {
        static void Main(string[] args) {
            try {
                show();
            } catch (Exception e) {
                Console.WriteLine("msg: " + e.Message);
                Console.WriteLine("data: " + e.Data);
                Console.WriteLine("src: " + e.Source);
                Console.WriteLine("trace: " + e.StackTrace);
            }
        }

        private static void show() {
            string Address = Properties.Settings.Default.Address;
            string Binding = Properties.Settings.Default.Binding;
            ServiceClient service = null;

            Console.WriteLine("Call HTTPS messge: ");
            //証明書の確認用EvnetHandlerをコール
            ConfirmCertificatePolicy.ConfirmCertificatePolicy_CallEventHandler();

            ICollection<BindingElement> bindingElements = new List<BindingElement>();
            HttpsTransportBindingElement httpsBindingElement = new HttpsTransportBindingElement();
            string proxyEnabled = Properties.Settings.Default.ProxyEnabled;
            if (proxyEnabled == "TRUE") {
                string proxyServer = Properties.Settings.Default.ProxyServer;
                httpsBindingElement.BypassProxyOnLocal = true;
                httpsBindingElement.UseDefaultWebProxy = false;
                httpsBindingElement.ProxyAddress = new Uri(proxyServer);
            }

            GZipMessageEncodingBindingElement compBindingElement = new GZipMessageEncodingBindingElement();
            bindingElements.Add(compBindingElement);
            bindingElements.Add(httpsBindingElement);

            CustomBinding myBinding = new CustomBinding(bindingElements);
            myBinding.Name = Binding;
            myBinding.Namespace = "http://ws.chocbanana.com/";


            // Webサービスインスタンス生成 //
            service = new ims.ServiceClient(myBinding, new EndpointAddress(Address));

            HttpRequestMessageProperty httpRequestProperty = new HttpRequestMessageProperty();

            //application/x-gzip
            httpRequestProperty.Headers.Add(HttpRequestHeader.AcceptEncoding, "gzip");
            httpRequestProperty.Headers.Add(HttpRequestHeader.ContentEncoding, "gzip");

            using (OperationContextScope scope = new OperationContextScope(service.InnerChannel)) {
                OperationContext.Current.OutgoingMessageProperties
                [HttpRequestMessageProperty.Name] = httpRequestProperty;
                Console.WriteLine(service.hello());
            }
        }
    }
}
[ EOF ]
-----------------

[ app.config ]
-----------------
<?xml version="1.0"?>
<configuration>
  <configSections>
    <sectionGroup name="applicationSettings" type="System.Configuration.ApplicationSettingsGroup, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
      <section name="WCF_TEST2.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false"/>
    </sectionGroup>
  </configSections>

  <system.serviceModel>
    <behaviors>
      <endpointBehaviors>
        <behavior name="ClientCredentialsBehavior">
          <clientCredentials>
            <serviceCertificate>
              <authentication certificateValidationMode="None" revocationMode="NoCheck"/>
            </serviceCertificate>
          </clientCredentials>
        </behavior>
      </endpointBehaviors>
    </behaviors>

    <extensions>
      <bindingElementExtensions>
        <add name="gzipMessageEncoding" type="Microsoft.ServiceModel.Samples.GZipMessageEncodingElement, GZipEncoder, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
      </bindingElementExtensions>
    </extensions>

    <bindings>
      <customBinding>
        <binding name="GzipSecureBinding_Service">
          <gzipMessageEncoding innerMessageEncoding="textMessageEncoding" />
          <sslStreamSecurity requireClientCertificate="false"/>
          <httpsTransport manualAddressing="false" authenticationScheme="None"
            bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard"
            proxyAuthenticationScheme="Basic" realm="" useDefaultWebProxy="false" />

        </binding>
      </customBinding>
    </bindings>

    <client>
      <endpoint address="https://127.0.0.1:8443/services/Service" binding="customBinding"
        bindingConfiguration="GzipSecureBinding_Service" contract="ims.IService" behaviorConfiguration="ClientCredentialsBehavior"
         name="CustomBinding_Service" />
      <metadata>
        <policyImporters>
          <extension type="Microsoft.ServiceModel.Samples.GZipMessageEncodingBindingElementImporter, GZipEncoder, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
        </policyImporters>
      </metadata>
    </client>
  </system.serviceModel>

  <applicationSettings>
    <WCF_TEST2.Properties.Settings>
      <setting name="ProxyServer" serializeAs="String">
        <value>http://proxyserver:8080/</value>
      </setting>
      <setting name="ProxyEnabled" serializeAs="String">
        <value>FALSE</value>
      </setting>
      <setting name="Binding" serializeAs="String">
        <value>CustomBinding_Service</value>
      </setting>
      <setting name="Address" serializeAs="String">
        <value>https://chocbanana.com/services/Service</value>
      </setting>
    </WCF_TEST2.Properties.Settings>
  </applicationSettings>
  <startup>
    <supportedRuntime version="v2.0.50727"/>
  </startup>
</configuration>
[ EOF ]
-----------------


.NET Framework3.5 SP1 以降。Windows XP 以降に対応です。

WCFでの実装になりますが、WS-Security(HTTPS)を利用する場合、残念ながらこの機能は利用出来ません。
SOAP(HTTP)でなら、GZipでの圧縮が有効になります。


えー、出来ましたので、別 途掲載させて頂きます。m(_"_)m

開発環境は VisualStudio 2008 C# です。


まずはサービス側。

pjl-comp-filter でGzip圧縮します。pjl-comp-filter.jar と web.xmlだけでOKです。

[ web.xml ]
----------------------
<?xml version="1.0"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
    <display-name>Service</display-name>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:/com/chocbanana/ws/cxf.xml</param-value>
    </context-param>

    <!-- Filter config with GZIP -->
    <filter>
        <filter-name>CompressingFilter</filter-name>
        <filter-class>
            com.planetj.servlet.filter.compression.CompressingFilter
        </filter-class>
        <init-param>
            <param-name>debug</param-name>
            <param-value>false</param-value>
        </init-param>
        <init-param>
            <param-name>statsEnabled</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>

    <filter-mapping>
        <filter-name>CompressingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

   
    <servlet>
        <servlet-name>context</servlet-name>
        <servlet-class>org.springframework.web.context.ContextLoaderServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet>
        <servlet-name>CXFServlet</servlet-name>
        <servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>
        <load-on-startup>2</load-on-startup>
    </servlet>

    <servlet>
        <servlet-name>MessageBrokerServlet</servlet-name>
        <servlet-class>flex.messaging.MessageBrokerServlet</servlet-class>
        <init-param>
            <param-name>services.configuration.file</param-name>
            <param-value>/WEB-INF/flex/services-config.xml</param-value>
        </init-param>
        <load-on-startup>3</load-on-startup>
    </servlet>

    <servlet>
        <servlet-name>ScheduleStarter</servlet-name>
        <servlet-class>com.chocbanana.schedule.ScheduleStarter</servlet-class>
        <load-on-startup>4</load-on-startup>
    </servlet>

    <servlet>
          <servlet-name>FileUpServlet</servlet-name>
          <servlet-class>com.chocbanana.csv.Upload</servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>MessageBrokerServlet</servlet-name>
        <url-pattern>/messagebroker/*</url-pattern>
    </servlet-mapping>

    <servlet-mapping>
        <servlet-name>CXFServlet</servlet-name>
        <url-pattern>/services/*</url-pattern>
    </servlet-mapping>

    <servlet-mapping>
        <servlet-name>FileUpServlet</servlet-name>
        <url-pattern>/fileup/*</url-pattern>
    </servlet-mapping>

    <mime-mapping>
        <extension>wsdl</extension>
        <mime-type>text/xml</mime-type>
    </mime-mapping>
</web-app>
[ EOF ]
----------------------

[ com.chocbanana.ws.Service.java ]
----------------------
package com.chocbanana.ws;

import javax.jws.WebService;
import javax.jws.WebMethod;

@WebService
public class Service implements IService{

    public Service(){
    }
       
    @WebMethod
    public String hello(){
        return "Hello";
    }
}
[ EOF ]
----------------------


んで、WCF側。


前準備としてGZipライブラリが必要になるので、それを用意します。

http://www.microsoft.com/Downloads/details.aspx?familyid=ED9C315C-31AC-4CCB-B62B-721AF1E2BFA8&displaylang=en
より WCF_WF_Samples.exe をダウンロードします。

それを解凍した中に、WCFWFCardSpace\WCF\Extensibility\MessageEncoder\Compression\CS があるので、それをVS2008で少し改変してビルドしておきます。
このソリューションのビルドが成功すると GZipEncoder.dll が作成されますので、GZip圧縮にはこのDLLを利用します。

以下改変部分。

-------------
1) GZipMessageEncoderFactory.cs
        //This is the actual GZip encoder
        class GZipMessageEncoder : MessageEncoder
        {
            //static string GZipContentType = "application/x-gzip";
            static string GZipContentType = "text/xml"; // 置換

2) GZIPMessageEncodingBindingElement.cs
        public GZipMessageEncodingBindingElement(MessageEncodingBindingElement messageEncoderBindingElement)
        {
            this.innerBindingElement = messageEncoderBindingElement;
            this.innerBindingElement.MessageVersion = MessageVersion.Soap11; // 追加
        }
-------------


あとは以 前作ったこのコードとapp.configに修正を加えます。
使用するプロジェクトにGZipEncoder.dll をインポートして、Microsoft.ServiceModel.Samplesの参照設定を行って下さい。

以下サンプル用ソースコード。

[ app.config ]
-----------------------
<?xml version="1.0"?>
<configuration>
  <configSections>
    <sectionGroup name="applicationSettings" type="System.Configuration.ApplicationSettingsGroup, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
      <section name="WCF_TEST.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false"/>
    </sectionGroup>
  </configSections>
  <system.serviceModel>
    <behaviors>
      <endpointBehaviors>
        <behavior name="ClientCredentialsBehavior">
          <clientCredentials>
            <serviceCertificate>
              <authentication certificateValidationMode="None" revocationMode="NoCheck"/>
            </serviceCertificate>
          </clientCredentials>
        </behavior>
      </endpointBehaviors>
    </behaviors>

    <extensions>
      <bindingElementExtensions>
        <add name="gzipMessageEncoding" type="Microsoft.ServiceModel.Samples.GZipMessageEncodingElement, GZipEncoder, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
      </bindingElementExtensions>
    </extensions>

    <bindings>
      <basicHttpBinding>
        <binding name="BasicHttpBinding_Service" closeTimeout="00:01:00"
          openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
          allowCookies="false" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard"
          maxBufferSize="65536" maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
          messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered"
          useDefaultWebProxy="true">
          <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
            maxBytesPerRead="4096" maxNameTableCharCount="16384" />
          <security mode="Transport">
            <transport clientCredentialType="None" proxyCredentialType="None"
              realm="" />
            <message algorithmSuite="Default" />
          </security>
        </binding>
      </basicHttpBinding>
      <customBinding>
        <binding name="ISampleServer">
          <gzipMessageEncoding innerMessageEncoding="textMessageEncoding" />
          <httpTransport manualAddressing="false" authenticationScheme="None"
            bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard"
            proxyAuthenticationScheme="Basic" realm="" useDefaultWebProxy="false" />
        </binding>
      </customBinding>
    </bindings>
    <client>
      <endpoint address="https://127.0.0.1:8443/services/Service" behaviorConfiguration="ClientCredentialsBehavior"
        binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_Service"
        contract="ims.IService" name="BasicHttpBinding_Service" />
      <endpoint address="http://127.0.0.1:8080/services/Service" binding="customBinding"
        bindingConfiguration="ISampleServer" contract="ims.IService"
        name="SoapBinding_Service" />
      <metadata>
        <policyImporters>
          <extension type="Microsoft.ServiceModel.Samples.GZipMessageEncodingBindingElementImporter, GZipEncoder, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
        </policyImporters>
      </metadata>
    </client>
  </system.serviceModel>

  <applicationSettings>
    <WCF_TEST.Properties.Settings>
      <setting name="ProxyServer" serializeAs="String">
        <value>http://proxyserver:8080/</value>
      </setting>
      <setting name="OutputFile" serializeAs="String">
        <value>detault.xml</value>
      </setting>
      <setting name="OutputFileFlag" serializeAs="String">
        <value>FALSE</value>
      </setting>
      <setting name="Debug" serializeAs="String">
        <value>FALSE</value>
      </setting>
      <setting name="ProxyEnabled" serializeAs="String">
        <value>FALSE</value>
      </setting>
      <setting name="Domain" serializeAs="String">
        <value>DOMAIN</value>
      </setting>
      <setting name="AdminUser" serializeAs="String">
        <value>Administrator</value>
      </setting>
      <setting name="AdminPass" serializeAs="String">
        <value>password</value>
      </setting>
      <setting name="AdminDomain" serializeAs="String">
        <value>LONDON</value>
      </setting>
      <setting name="EncryptPassword" serializeAs="String">
        <value>OFF</value>
      </setting>
      <setting name="Binding" serializeAs="String">
        <value>SoapBinding_Service</value>
      </setting>
      <setting name="Address" serializeAs="String">
        <value>https://chocbanana.com/services/Service</value>
      </setting>
      <setting name="SOAP_Address" serializeAs="String">
        <value>http://chocbanana.com/services/Service</value>
      </setting>
    </WCF_TEST.Properties.Settings>
  </applicationSettings>
  <startup>
    <supportedRuntime version="v2.0.50727"/>
  </startup>
</configuration>
[ EOF ]
-----------------------


[ Program.cs ]
-----------------------
using System;
using System.Collections.Generic;
using System.Text;
using ims;
using System.ServiceModel;
using System.ServiceModel.Channels;
using Microsoft.ServiceModel.Samples;
using System.Net;

namespace WCF_TEST {
    class Program {
        static void Main(string[] args) {
            try {
                show();
            } catch (Exception e) {
                Console.WriteLine("msg: " + e.Message);
                Console.WriteLine("data: " + e.Data);
                Console.WriteLine("src: " + e.Source);
                Console.WriteLine("trace: " + e.StackTrace);
            }
        }

        private static void show() {
            string Address = Properties.Settings.Default.Address;
            string Binding = Properties.Settings.Default.Binding;
            ServiceClient service = null;
            if (Address.StartsWith("https://") && Binding == "BasicHttpBinding_Service") {

                //証明書の確認用EvnetHandlerをコール
                ConfirmCertificatePolicy.ConfirmCertificatePolicy_CallEventHandler();
                ims.ServiceClient proxy = new ims.ServiceClient("BasicHttpBinding_Service",
                    new System.ServiceModel.EndpointAddress(Properties.Settings.Default.Address));
                string proxyEnabled = Properties.Settings.Default.ProxyEnabled;

                if (proxyEnabled == "TRUE") {
                    string proxyServer = Properties.Settings.Default.ProxyServer;
                    BasicHttpBinding httpBindingElement = (BasicHttpBinding)proxy.Endpoint.Binding;
                    httpBindingElement.BypassProxyOnLocal = true;
                    httpBindingElement.UseDefaultWebProxy = false;
                    httpBindingElement.ProxyAddress = new Uri(proxyServer);
                    proxy.Endpoint.Binding = httpBindingElement;
                }

                Console.WriteLine(proxy.hello());
                proxy.Close();

            } else {
                ICollection<BindingElement> bindingElements = new List<BindingElement>();
                HttpTransportBindingElement httpBindingElement = new HttpTransportBindingElement();
                string proxyEnabled = Properties.Settings.Default.ProxyEnabled;
                if (proxyEnabled == "TRUE") {
                    string proxyServer = Properties.Settings.Default.ProxyServer;
                    httpBindingElement.BypassProxyOnLocal = true;
                    httpBindingElement.UseDefaultWebProxy = false;
                    httpBindingElement.ProxyAddress = new Uri(proxyServer);
                }

                //httpBindingElement.AuthenticationScheme = AuthenticationSchemes.Basic;

                GZipMessageEncodingBindingElement compBindingElement = new GZipMessageEncodingBindingElement();
                bindingElements.Add(compBindingElement);
                bindingElements.Add(httpBindingElement);

                CustomBinding myBinding = new CustomBinding(bindingElements);
                myBinding.Name = "SoapBinding_Service";
                myBinding.Namespace = "http://ws.chocbanana.com/";


                // Webサービスインスタンス生成 //
                Address = Properties.Settings.Default.SOAP_Address;
                service = new ims.ServiceClient(myBinding, new EndpointAddress(Address));

                HttpRequestMessageProperty httpRequestProperty = new HttpRequestMessageProperty();

                httpRequestProperty.Headers.Add(HttpRequestHeader.AcceptEncoding, "gzip");
                httpRequestProperty.Headers.Add(HttpRequestHeader.ContentEncoding, "gzip");

                using (OperationContextScope scope = new OperationContextScope(service.InnerChannel)) {
                    OperationContext.Current.OutgoingMessageProperties
                    [HttpRequestMessageProperty.Name] = httpRequestProperty;
                    Console.WriteLine(service.hello());
                }
            }
        }
    }
}
[ EOF ]
-----------------------

参考サイトURL
http://www.vistax64.com/indigo/113763-wcf-client-j2ee-server-using-gzip.html

このページの先頭へ