[win8] C++/CLI と C++/CX の違い

Microsoft 社の公式見解は、何処かにある(?)と思うので、私のほうで Visual Studio 11 beta を1か月程触った感じで言いますと、

  • 「C++/CX って、C++/CLI の後継でも何でもないやんッ!!! まったく別物じゃ」
  • 「C++/CX って、metro アプリケーションは作れるけれど、desktop アプリは作れないじゃんッ!!!」
  • 「C++/CLI って、metro アプリが作れないどころか、WPF アプリも相変わらず作れないじゃんッ!!!」
  • 「C++/CX って、WinRT を扱えるけど、.NET Framework を扱えないじゃん、つーか、C++ の世界に戻らないとだめじゃんッ!!!」
  • 「そもそも、C++/CX と C++/CLI って混在できないじゃんッ!!!」

ってな具合で、憤懣ひとしきりなんですよ。というわけで、ちょっと以下に戯言を。

希望/期待としては、C++/CLI が更に拡張されて XAML を扱えるようになって、なもんだから metro アプリを作れるよ、という流かと思っていたのですが相当違うようです。WinRT に隠されているというか、C++/CLI は爆死状態というか。いえ、おそらく去年の build で分かっていた段階なんでしょうが、ちと去年の夏頃は別件で忙しくてあまり C++/CLI を眺めてはいなかったんですよね~。ベータ版をインストールして、metro の C++ プロジェクトをさくりと作って「ああ、インテリセンスが動く、進化したなぁ」と思っていた訳ですが、いえいえ、あれは「C++/CX」のほうで「C++/CLI」ではなかったのですね。

さて、「C++/CX」と「C++/CLI」という用語が頻発していますが、microsoft さんの講演の中は一律して「C++」として扱っています。と言いますか「C++/CLI」には一切触れずに、「C++/CX」のことを「C++」と称しています。「Visual Basic .NET」のことを「Visual Basic」と言うようになった流れと同じ感じだと思います。

C++/CLI というのは、「.NET Framework の共通言語基盤(ランタイム)を扱えるC++」という意味です。正式名称は「C++ with Common Language Infrastructure」ってところですかね。単体で Windows アプリケーションを作れますが、主な仕事としては、「旧来のC/C++ライブラリを、そのままC#で扱えるようにする」なところで、単体で扱うことは非常に稀です。Windows アプリを作るならば、C#/VB で作ったほうが手早く作れます。ですが、C++ の文法から、.NET Framework をあつかうことができるので、C#/VB と同じようにプログラミングをすることができます。
ただし、欠点としては、

  • Visual Studio 2010 上でインテリセンスが効かないこと。
  • XAML が扱えないので、WPF や Silverlight アプリケーションが作れないこと。

があります。インテリセンスのほうは、Visual Studio 2008 を使えばよいので特に問題はありません。まあ、Visual Studio 2010 では実質開発ができない状態だったので、なんとも言えないところですが。
XAML が扱えないのは今回の visual studio 11、windows 8 で致命的なところです。

一方で、「WinRT が扱える C++」として登場しているのが「C++/CX」です。正式名称は「C++ with Component Extensions」ってことで、WinRT≒DCOM で拡張されている C++ という意味合いです。今回の Visual C++ 11 の売りは、「C++ で metro アプリケーションを作れる」ことです。metro アプリケーションというのは、windows 8 の主流となるタブレット型のアプリケーションで、内部的には XAML を使っています。となると、意訳すれば「XAML を扱える C++」ということになります。

さて、C++/CLI では XAML を扱えない。逆に、C++/CX では XAML を扱える。という区別があります。このために、C++/CLI と C++/CX の開発領域は二分されて、

  • desktop アプリ(従来の windows アプリ)の場合は、C++/CLI
  • metro アプリの場合は、C++/CX

という住み分けができます。文法的はどちらも C++ をベースにして拡張されていしマネージドポインタ「^」や「%」の扱いも同じなのでほぼ互換性があります。主な違いは、「gcnew」と「ref new」の違いぐらいです…で済めばよいのですが、実際コーディングをしてみると大きく違います。

今回、metro アプリケーションを C++ で作れるようにした理由に「ゲームアプリケーション」があります。タブレット市場でもゲームの割合は大きくて、グラフィック機能とマシンパワーを存分に使ったゲームは、windows 8 になると飛躍的に上がる(と思われる)ところです。
さて、私の場合ゲーム開発業界に疎いので正確なことは分からないのですが、察するに windows 7 の頃のゲーム開発の場合は、

  • グラフィックを扱うならば DirectX を使う。
  • 余りパフォーマンスがいらないところは、Microsoft.Xna.Framework

なのだと思います。DirectX はグラフィックボードの機能を存分発揮できるようにと開発されたインターフェースでC/C++で扱います。途中で、DirectX の .NET Framework 版があったのですが、その後 Microsoft.Xna.Framework に吸収されています。
で、ゲームのパフォーマンスを出す上で、.NET Framework が十分対応しているかというと、そうではありません。結局グラフィック関係は、WinRT に残していることを考えると GC を含む virtual machine 環境ではきついものがあります。メモリへの直接アクセスが禁止されていることのデメリットは、OpenCV や Fortran を触るとよく分かります。

このあたり、C++/CLI の出番かと思っていたのですが、ゲーム業界としては、MFCやATLぐらあらばOKで、わざわざ .NET を使う理由は無かったのですね。

で、どういう要望および内部抗争(?)があったのかは知りませんが、metro アプリケーションがタブレット型を主戦場としていることと、タブレット型ではゲームが流行ることを考えると、metro アプリ + DirectX(C/C++) の組み合わせは避けられない。しかし、C++/CLI では XAML が使えない。どういう理由か分からないけども、XAML を扱うように拡張できない(マンパワーがないのか、開発者が抜けてしまったのかはわかりません)。

という訳で、C++/CLI はばっさり切り捨ててしまって、XAML + C++ という組み合わせの新しいプラットフォームを作った訳です。これが「C++/CX」です。
なので、C++/CX の主戦場が、DirectX 廻りやネットワーク廻りなので、そのあたりが WinRT≒DCOM で補ってしまえば、「ひとまずは」.NET Framework はいらないわけです、C++/CLI をゲーム業界の方が使わないのと同じ理由で、metro アプリを作るときにも .NET Framework は要らないでしょう、という想像が働きます。

こういう経緯を考えてみると、先の憤懣の部分にコメントを付けて、

  • 「C++/CX って、C++/CLI の後継でも何でもないやんッ!!! まったく別物じゃ」
  • → もとも発祥は別物。ゲーム関係の必要性からだから。

  • 「C++/CX って、metro アプリケーションは作れるけれど、desktop アプリは作れないじゃんッ!!!」
  • → ゲームなんだから、metro アプリで十分。

    → desktop アプリは従来のインストーラーが必要なので、流行らないと思われる

  • 「C++/CLI って、metro アプリが作れないどころか、WPF アプリも相変わらず作れないじゃんッ!!!」
  • → C++/CLI は、元のままで良いので、WPF アプリを使えなくてもよい。

    → カラフルなUIを必要としないので、XAML を扱えなくてもよい。

  • 「C++/CX って、WinRT を扱えるけど、.NET Framework を扱えないじゃん、つーか、C++ の世界に戻らないとだめじゃんッ!!!」
  • → 多分「間に合わなかった」んじゃないかな?

    → そのうち様子を見て公開するかも。

  • 「そもそも、C++/CX と C++/CLI って混在できないじゃんッ!!!」
  • → C++/CX が .NET Framework を取り込めるようになったら、混在できるようになるかも。

という感じになります。

C++/CX のハマり処は、.NET Framework を使えないところです。もともとゲーム業界で働いている方の場合は、.NET を使わないので、C/C++ の関数/STLの扱いに慣れているので大丈夫だと思うのですが、.NET プログラマが、C++/CX を扱う場合にはハマり処が満載です。

ひとつあげると、C#/VB で良く使われる「String」は、C++/CXの「String」とは全く別物です。.NET の場合は「System.String」なのですが、C++/CX の場合は「Platform::String」なんですね。なので、String::Format メソッドが無くて「えッ!!!」と吃驚します。
ちなみに、C++/CLI の場合は「System::String」なんですよね~。このあたりの話はまた後程。

カテゴリー: C++, windows 8 | 10件のコメント

[win8] metro-desktopのプロセス間通信をWeb API風にする

[win8] MetroアプリからDesktopアプリへWCFで接続する | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/3387
[win8] metro アプリケーションからデスクトップアプリにプロセス間通信する | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/3379

# 追記 2012/05/12
# 再度確認したところ、localhost によるループバック接続はパッケージを作った時は駄目で、Visual Studio からデバッグ実行しただけ接続できます。このあたり、hosts 書き換え、ip 指定でも駄目なので、別の方式を考えないと。以下は、参考のため残しておきます。
# 業務的には、別マシンに proxy を立てて localhost->proxy->localohst にすれば ok なんですが、もうちょっとうまい方法を考えますか。ネットワーク負荷がかかるし。

なところで、WCF を使ってプロセス間通信できることは確認できたのですが、WCF だと metro アプリのほうで web の参照設定をしないといけません。まぁ、製品的にサーバーが先に固定化されている場合はいいのですが、流動的に作っている場合は先にインターフェースを決めないといけないというのはちょっと酷です。

な訳で、Web API 風に GET コマンドのアドレスを使って metro アプリ(クライアント)から desktop アプリ(サーバー)へ送信できるようにします。

■クライアントの metro アプリ側

private async void Button_Click_1(object sender, RoutedEventArgs e)
{
    string text = textBox1.Text;
    string url = "http://localhost:8083/metro/method";

    HttpClient client = new HttpClient();
    HttpResponseMessage res = await client.GetAsync(url + "/" + textBox1.Text);
    string response = await res.Content.ReadAsStringAsync();
    textBox2.Text = response;
}

metro アプリのほうでは、アドレスを指定してサーバーに接続します。Web API の REST のように「http://localhost:8083/metro/method/masuda/1000」とか「http://localhost:8083/metro/method?name=masuda&num=1000」のように呼び出すことを想定します。ここのサンプルでは適当に呼出URLを作っているだけなので、URLを作るための適当なラッパを作ると良いでしょう。

応答は、XML 形式でもよいのですが、自由に。

■Web APIを提供する desktop アプリ

HttpListener.BeginGetContext メソッド (System.Net)
http://msdn.microsoft.com/ja-jp/library/system.net.httplistener.begingetcontext(v=vs.110).aspx

を参考にして、HttpListener クラスでサーバーを作ります。前回は、同期メソッドを使ったために backgroundWorker コンポーネントでスレッドを使いましたが、今回は非同期メソッドの BeginGetContext、EndGetContext メソッドを使います。上記のサンプルコードではコールバック関数が static になっていますが、下記のように普通の内部メソッドを使うことができます。つーか、内部メソッドを使ったほうが、ListBox などの GUI にアクセスできるので便利かと。

using System.Net;
using System.IO;

namespace SampleLocalWebApiServer
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        HttpListener listener;

        private void button1_Click(object sender, EventArgs e)
        {
            string url = "http://*:8083/metro/";
            // 開始
            listener = new HttpListener();
            listener.Prefixes.Add(url);

            listener.Start();
            listBox1.Items.Add("サーバー開始");
            listener.BeginGetContext(ListenerCallback, listener);
        }

        public void ListenerCallback(IAsyncResult result)
        {
            // HttpListener listener = (HttpListener)result.AsyncState;
            HttpListenerContext context;
            try
            {
                context = listener.EndGetContext(result);
            }
            catch
            {
                // stop メソッドで例外が発生するので、対処
                return;
            }
            // var content = listener.GetContext();
            var req = context.Request;
            var url = req.RawUrl;
            var res = context.Response;

            listBox1.Items.Add("受信");

            var output = new StreamWriter( res.OutputStream ) ;
            output.WriteLine(string.Format("called {0}", url));
            output.Close();

            // 次の受信の準備
            listener.BeginGetContext(ListenerCallback, listener);
        }

        private void button2_Click(object sender, EventArgs e)
        {
            // 終了
            listener.Stop();
            listBox1.Items.Add("サーバー終了");
        }
    }
}

サーバーの停止なのですが、Stop か、Abort を呼び出します。が、ちょっと面倒なのは、Stop メソッドを呼び出した途端にコールバックが呼び出されるんですよね…で、EndGetContext メソッドの呼び出し時に例外が発生してしまうので、コードでは try-catch で、回避しています。異常終了の場合と区別がつかないので、もうちょっとなんとかしたいところですね。

呼び出した URL は、RawUrl プロパティで取得できます。URL プロパティでもいいのですが、RawURL のほうが、サーバー名とポート名を削ってくれるので、処理が楽なのです(名前が逆っぽいのは、見ないことにしよう)。

■実行してみる

サーバーを管理者権限で動かして、metro app からつなげてみます。

無事接続できていますね。サーバー側で適当に振り分けてやれば、Web API として動かすことができます。URL Encode/Decodeすれば文字列は簡単です。複雑な場合は POST で送って、適当なクラスでラップすればよいですかね。というか、シリアライズ機能を使えば ok かも。

カテゴリー: C#, windows 8 | [win8] metro-desktopのプロセス間通信をWeb API風にする はコメントを受け付けていません

nullポインターがokな、オブジェクトをC#で実装する

WCFサービスを調べているときに見つけたので、ちょっとメモ的に。下記の SafeInvoke メソッドのところです。

Method call if not null in C# – Stack Overflow
http://stackoverflow.com/questions/872323/method-call-if-not-null-in-c-sharp

objective-c には便利な機能があって、変数が null の場合はメソッドを呼び出さないのです。このために null チェックがいりません。具体的にコードを示すと、


 NullObject *obj = [NullObject new];
 [obj callMethod];

 // null を代入
 obj = NULL;
 // 次の関数は呼び出されない
 [obj CallMethod];

ってな感じで、2回目の CallMethod は呼び出されません。 if ( obj != NULL ) というチェックがいらなくなってコードがシンプルになります。まあ、厳密性を重んじるならば NULL チェックをする「意図」は残しておいたほうがいいのですが、コードの安全性を考えるとこれで ok な気がします。

■拡張メソッドを使う

実は C# の拡張メソッドを使うと似たようなことができる、というのを先日知りました。元のクラスを NullObject にして、拡張メソッドを含むクラスを NullObjectExtention にしておきます。

namespace SampleNullObjectExtention
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            var obj = new NullObject();
            Debug.Print("goMessage pre");
            obj.goMessage("masuda");
            Debug.Print("goMessage after");

            obj = null;

            Debug.Print("goMessageEx pre");
            obj.goMessageEx("tomoaki");
            Debug.Print("goMessageEx pre");

        }
    }

    public static class NullObjectExtention
    {
        public static void goMessageEx( this NullObject obj, string msg)
        {
            if (obj != null)
            {
                obj.goMessage(msg);
            }
        }
    }
    public class NullObject
    {
        public void goMessage(string msg)
        {
            Debug.Print("in goMessage {0}", msg);
        }
    }
}

すると、button1 をクリックしたときに、obj を null に設定しておいても、goMessageEx メソッド呼び出しは大丈夫なんですね。なるほど。拡張メソッド側で this で参照させて null チェックをするという技です。LINQ の内部でも使っているのかもしれません。

なかなか面倒ですが、メソッドを全てラップしてしまって、ラップした方のメソッドを使うというルールにすれば ok なんですが、いやいや、そうはいきません。インテリセンスで「goMessage」が出てくるならば、それを使ってしまうかもしれない。ならば、obj が null の場合は通常通り落ちてしまう訳です。使えない技ですね~という感じになってしまいます。

■別のアセンブリに隠す(internal protected)

C++のようにfriendで制限ができればよいのですが、.NETにはありません。その代り「internal protected」というちょっと中途半端な(便利な)範囲設定ができます。
拡張メソッドは、protected なメソッドを呼び出せないので、代替案という訳です。

テスト用のプロジェクトとは別に、クラスライブラリを作成します。

namespace CheckNullObjectLib
{
    ///
    /// NullObject の拡張クラス
    ///
    public static class NullObjectExtention
    {
        ///
        /// SetMessage の Wrapper
        ///
        /// <param name="me" />
        /// <param name="msg" />
        public static void SetMessage(this NullObject me, string msg)
        {
            if (me != null)
            {
                me._setMessage(msg);
            }
        }
        ///
        /// GetMessage の Wrapper
        ///
        /// <param name="me" />
        /// <param name="msg" />
        public static string GetMessage(this NullObject me)
        {
            if (me != null)
            {
                return me._getMessage();
            }
            // あえて空文字列を返す
            return "";
        }
    }
    ///
    /// 本体の NullObject クラス
    ///
    public class NullObject
    {
        protected internal string _msg = "";

        ///
        /// アクセス制限を protected internal(同じアセンブリのみ)にしておく
        ///
        /// <param name="msg" />
        protected internal void _setMessage(string msg)
        {
            _msg = msg;
        }
        protected internal string _getMessage()
        {
            return _msg;
        }
    }
}

本体の NullObject クラスで公開するメソッド/プロパティは、全て protected internal にしておきます。こうすると、同じアセンブリ内のクラスからのみアクセスが可能になります。これを、ラップする NullObjectExtention クラスでは、public で拡張メソッドを作る訳です。

テストコードはこんな感じになります。

using CheckNullObjectLib;

namespace CheckTestNullObject
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private NullObject obj;

        ///
        /// set button
        ///
        /// <param name="sender" />
        /// <param name="e" />
        private void button1_Click(object sender, EventArgs e)
        {
            obj = new NullObject();
            obj.SetMessage(textBox1.Text);
        }

        ///
        /// get button
        ///
        /// <param name="sender" />
        /// <param name="e" />
        private void button2_Click(object sender, EventArgs e)
        {
            // いきなり set button を押して,
            // obj が null の状態でも大丈夫
            textBox2.Text = obj.GetMessage();
        }
    }
}

内部で保持している NullObject オブジェクト obj は、set button を押したときに new する訳ですが、間違って get button を押したとしても GetMessage メソッドで例外が発生しません。なんと便利なッ!!! objective-c の技がッ!!! って思いますが、まぁ、コーディングの基礎しては「きちんと obj は初期化しておこうね」とか「get button を押した時に null チェックをしようね」というのが C# のコーディングとして正しいやり方ですね。

■どんなところで使うのか?

使い処としては、null チェックを省くってのもそうなのですが、メソッドチェーンのところで途中に null を含められるというメリットがあります。
LINQ の定番として、

var data = ...
var result = data.From( ... ).Where( ... ).Select( ... ).OrderBy( ... );

という書き方をした場合、From, Where, Select メソッド null を返すと不意に落ちてしまう訳です。ひとつの方法としては、それぞれのメソッドが null を返さないように実装すればよいのです。もうひとつの方法としては、先の拡張メソッドのテクニックを使って null で落ちないという方法が使えます。

まあ、変数を敢えて null で返したいときに使えるかなぁと。

カテゴリー: C# | 1件のコメント

[win8] MetroアプリからDesktopアプリへWCFで接続する

昨日 [win8] metro アプリケーションからデスクトップアプリにプロセス間通信する の続き。

# 追記 2012/05/12
# 再度確認したところ、localhost によるループバック接続はパッケージを作った時は駄目で、Visual Studio からデバッグ実行しただけ接続できます。このあたり、hosts 書き換え、ip 指定でも駄目なので、別の方式を考えないと。以下は、参考のため残しておきます。
# 業務的には、別マシンに proxy を立てて localhost->proxy->localohst にすれば ok なんですが、もうちょっとうまい方法を考えますか。ネットワーク負荷がかかるし。

metro アプリで HttpClient クラスを使ってローカルホスト(localhost)に接続できることが分かったのですが、「さて、データ形式はどうしようか」ということで再考しておりました。 やっぱり、データ形式は XML 形式がいいよねと、どうせならばクライアントは WCF 形式で繋げられるとよいよね、と考えた挙句…ああ、WCF で使えばいいよね、とひと巡りして来てしまいました、という話。

「System.Net.HttpListenerException: アクセスが拒否されました。」と表示されてしまう
http://social.msdn.microsoft.com/Forums/ja-JP/wcfja/thread/4b1572df-a780-45b0-9488-cb4e3b95b53f

をよく見ると、実は WCF の話だったのですね。なるほど、というわけで

ServiceContractAttribute クラス (System.ServiceModel)
http://msdn.microsoft.com/ja-jp/library/system.servicemodel.servicecontractattribute.aspx
WCF クライアントを使用したサービスへのアクセス
http://msdn.microsoft.com/ja-jp/library/ms734691.aspx

を参考にしながら、

  • desktop アプリで WCF サーバー
  • metro アプリで WCF クライアント

を作成していきます。

■desktop アプリ(windows form アプリ)で WCF サーバー

基本は、ServiceContract、OperationContract 属性を使ってサービスで公開するメソッドを作るのと、ホストを ServiceHost クラスで作るところです。

using System.ServiceModel;
using System.ServiceModel.Description;

namespace SampleWcfMetroServer
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        ServiceHost serviceHost;
        // MetroService host;

        private void button1_Click(object sender, EventArgs e)
        {
            // 開始
            serviceHost = new ServiceHost(
                typeof(MetroService),
                new Uri("http://localhost:8082/metro"));
            //serviceHost.AddServiceEndpoint(
            //    typeof(IMetroService),
            //    new BasicHttpBinding(), "");
            ServiceMetadataBehavior smb = new ServiceMetadataBehavior();
            smb.HttpGetEnabled = true;
            smb.MetadataExporter.PolicyVersion = PolicyVersion.Policy15;
            serviceHost.Description.Behaviors.Add(smb);

            // これは無駄
            // host = new MetroService();
            // host.OnNotiry += host_OnNotiry;

            // 自分のオブジェクトを static に登録
            Form1.me = this;

            serviceHost.Open();
            listBox1.Items.Add("サービス開始");
        }

        void host_OnNotiry(string name)
        {
            // イベントを受ける
            listBox1.Items.Add(name + " " + DateTime.Now.ToString());
        }

        private void button2_Click(object sender, EventArgs e)
        {
            serviceHost.Close();
            listBox1.Items.Add("サービス終了");
        }

        public void OnNotiry( string message )
        {
            this.listBox1.Items.Add( message );
        }

        public static Form1 me;
    }

    [ServiceContract]
    public interface IMetroService
    {
        [OperationContract]
        string GetDate(string name);
    }

    public class MetroService : IMetroService
    {
        public string GetDate(string name)
        {
            // 実行時にオブジェクトが作成されるので、こっちは効かない。
            if ( OnNotiry != null )
                OnNotiry("called GetDate");

            // 仕方がないので、static で公開したフォームに送信させる。
            Form1.me.OnNotiry("called GetDate");

            return string.Format("{0}:{1}", name, DateTime.Now.ToString());
        }

        // public Action<string> OnNotiry(string name);
        public delegate void OnNotiryHandler( string name );
        public event OnNotiryHandler OnNotiry;
    }
}

巷のサンプルでは、公開するクラス内で処理を行っていますが、そのままだと ListBox へ表示するなどの GUI 周りが使えないので、元のフォームにイベントメッセージを飛ばします。WCF で利用するクラスは、.NET リモートは違って接続時に動的に作成される(らしい)ので、フォームへのイベント飛ばしがちょっと妙なことになっています。このあたりは、あとで考えるということで。

■テスト用の WCF クライアントを作成

最初は、Windows Form アプリで試してみます。プロジェクトから先の WCF サービスを参照設定させるために、あらかじめ WCF サーバーを起動させておいて、プロジェクトから「サービスの参照を追加」します。WCF サーバーは管理者権限で起動するか、netsh を使ってポートを URL を登録しておきます。

image

ローカルのサービスの URL を指定して「Go」を押して検索します(このあたりは、Visual Studio 2010 も同じ)。

image

namespace SampleWcfMetroClient
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            // 送信
            var client = new MetroServiceReference.MetroServiceClient();
            string name = textBox1.Text;
            string res = client.GetDate(name);
            textBox2.Text = res;
        }

        private async void button2_Click(object sender, EventArgs e)
        {
            // 非同期送信
            var client = new MetroServiceReference.MetroServiceClient();
            string name = textBox1.Text;
            string res = await client.GetDateAsync(name);
            textBox2.Text = res;
        }
    }
}

面白いのは、.NET Framework 4.5 で作っているので自動的に、同期メソッドの GetDate と、非同期メソッドの GetDateAsync が用意されるところです。どちらを使ってもいいのですが、ここでは metro のために非同期版も作っておきます。

で、うまく送信ができれば準備完了。

■metro 版で WCF クライアントを作成

Form 版がテストができたので、本番の metro 版を作ります。Form 版と同じように「サービスの参照を追加」してから、適当にボタンを配置します。

namespace SampleWcfMetro
{
    /// <summary>
    /// An empty page that can be used on its own or navigated to within a Frame.
    /// </summary>
    public sealed partial class BlankPage : Page
    {
        public BlankPage()
        {
            this.InitializeComponent();
        }

        /// <summary>
        /// Invoked when this page is about to be displayed in a Frame.
        /// </summary>
        /// <param name="e">Event data that describes how this page was reached.  The Parameter
        /// property is typically used to configure the page.</param>
        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
        }

        private async void Button_Click_1(object sender, RoutedEventArgs e)
        {
            // 非同期送信
            var client = new MetroServiceReference.MetroServiceClient();
            string name = textBox1.Text;
            string res = await client.GetDateAsync(name);
            textBox2.Text = res;
        }
    }
}

前回の HttpClient と違うのは、Desktop 版でテストをしたコードがそのまま使えるということです。これは違いが大きいですよね。metro アプリの場合、シミュレーター等と使うのでいまいちデバッグがしづらいところがあります。そうなると基本的なコードは desktop アプリで確認しておいて、metro アプリに適宜追加してくという方法をとるほうが効率的にデバッグができます。

image

実行するとこんな感じ。意外とすんなり動きます。これならばプロセス間通信の代用にしてもよいかなと。

カテゴリー: C#, windows 8 | [win8] MetroアプリからDesktopアプリへWCFで接続する はコメントを受け付けていません

[win8] metro アプリケーションからデスクトップアプリにプロセス間通信する

発端は、.NETリモート通信を metro アプリから desktop アプリ(通常のwin8アプリ)に対して通信をさせたかった、ということです。metro アプリのデバッグログなんかを win8 アプリから見れたり、リモートデバッグしている元の PC から見られるようにするのが目的だったのです。

# 追記 2012/05/12
# 再度確認したところ、localhost によるループバック接続はパッケージを作った時は駄目で、Visual Studio からデバッグ実行しただけ接続できます。このあたり、hosts 書き換え、ip 指定でも駄目なので、別の方式を考えないと。以下は、参考のため残しておきます。
# 業務的には、別マシンに proxy を立てて localhost->proxy->localohst にすれば ok なんですが、もうちょっとうまい方法を考えますか。ネットワーク負荷がかかるし。

が、実は metro アプリでは .NET リモート通信ができません。.NET リモート通信を行うための条件として、

  • TCP/IP 通信ができること(内部ではHTTPで動作している)。
  • 共通のクラスを MarshalByRefObject で継承できること。
  • 共通のクラスを、「共通」で使えること。

になるわけですが、metro って上記の3つとも駄目で、

  • 直接 TCP/IP するクラスが用意されていない。ただし、HTTP だけは HttClient クラスがある。
  • MarshalByRefObject クラスがない。
  • metro のフレームワークと、.NET Framework 4.5 でそもそも異なる。

ってな感じで共通にクラスが使えません。まぁ、Windows Phone やら、他のバージョンのフレームワークに対しては「同じクラス」をアセンブリレベルでは共通に使えないので、当たり前といえば当たり前なのですが、同じ PC 内で、metro – desktop 間でアクセスできないのは、ちょっと辛い。

Client/Server の関係としては、metro が必ず Client になります。というのも、metro はばっくぐらうんどに入ってしまうとサスペンドをしてしまうので、Server にするにはあまり意味がないんですよね。それでも、iPhone の画像転送用の簡易 Http サーバーみたいなことができると便利なのですが、metro app の場合は desktop アプリとワンセットになって windows 8 として組み込まれるので、metro app 単体でなくても役に立つのです。まぁ、インストール自体が発生してしまうので、metro app の簡便性というのは低くなるのと、Store のほうがどうなのかという問題は残りますが…業務アプリならば別に問題なし。

■desktop のサーバーアプリ

HttpListener クラスを使って簡易的な HTTP サーバーを作ります。

using System.Net;
using System.IO;

namespace SampleProcessCommServer
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        // リスナー
        HttpListener listener;

        private void button1_Click(object sender, EventArgs e)
        {
            // 受け付けるURL
            string url = "http://*:8081/metro/";
            // 開始
            this.listener = new HttpListener();
            this.listener.Prefixes.Add(url);
            listener.Start();
            // スレッド開始
            backgroundWorker1.RunWorkerAsync();

            listBox1.Items.Add("サーバー開始");
        }

        private void button2_Click(object sender, EventArgs e)
        {
            // 停止
        }

        private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {
            while (true)
            {
                var content = listener.GetContext();
                var request = content.Request;
                var response = content.Response;

                var output = new StreamWriter(response.OutputStream);
                string text = @"<response>
    <name>SampleProccessCommServer</name>
    <date>" + DateTime.Now.ToString() + @"</date>
</response>
";
                output.WriteLine(text);
                output.Close();
            }
        }
    }
}

画面をブロックしないように、BackgroundWorker コンポーネントを使っていますが、.NET 4.5 なので例の async/await を使っても ok です。

URL の指定の仕方は、HttpListener クラス (System.Net) を参考にしてください。URL の書き方は、.NET リモートと似ています…というか、内部的にこれを使っているかと思われます。

レスポンスは XML 形式で書いていますが、文字列であればなんでも ok です。クライアント側の解析のためは XML 形式にしておいたほうがよいでしょう。

■desktop のクライアントアプリ

テスト用に desktop 版のクライアントアプリを作って試します。

using System.Net;
using System.IO;

namespace SampleProcessCommClient
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            // 送信
            var client = new WebClient();
            string url = "http://localhost:8081/metro/sample001";
            var output = new StreamReader( client.OpenRead(url));
            string content = output.ReadToEnd();
            output.Close();
            textBox2.Text = content;
        }
    }
}

接続先は自分自身なので「localhost」にしておきます。.NET リモート風にアドレスを指定しておいて、最終的にはサーバーのほうで分岐させるという感じになります。

さて、これでテストをするときに、サーバープログラムのほうで、「System.Net.HttpListenerException: アクセスが拒否されました。」とエラーがでます。実は、windows 7 あたりから、一般ユーザーの場合はサーバーのポートを開く権限がないのですよね。「System.Net.HttpListenerException: アクセスが拒否されました。」と表示されてしまう を参考にしてポートを設定するか、アプリを「管理者権限」で立ち上げてしまいます。

ポートの設定は、「netsh http add urlacl url=http://*:8081/metro/ user=masuda」のようにします。

すると無事に送受信ができます。

■metro アプリから接続する。

先にテストしたクライアントアプリを metro に書き写してしまえば ok …と思いきや、かなりコードが違います。

using System.Net.Http;

// The Blank Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=234238

namespace SampleProcessComm
{
    /// <summary>
    /// An empty page that can be used on its own or navigated to within a Frame.
    /// </summary>
    public sealed partial class BlankPage : Page
    {
        public BlankPage()
        {
            this.InitializeComponent();
        }

        /// <summary>
        /// Invoked when this page is about to be displayed in a Frame.
        /// </summary>
        /// <param name="e">Event data that describes how this page was reached.  The Parameter
        /// property is typically used to configure the page.</param>
        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
        }

        private async void Button_Click_1(object sender, RoutedEventArgs e)
        {
            var client = new HttpClient();
            // GET で呼び出し
            var res = await client.GetAsync("http://localhost:8081/metro/sample1");
            // レスポンスを取得
            var cont = await res.Content.ReadAsStringAsync();
            textBoxOutput.Text = cont;
        }
    }
}

connect も get も非同期になるので、async/await を使う、ってのと HttpClient クラスを使うってのがミソです。

実行した結果がこんな感じ。input のところは無視していますが、きちんとレスポンスが返ってきます。

image

このソースの場合は localhost に送ったので、同じ PC に対してプロセス間通信をしていますが、別の PC に飛ばすこともできるので、実質 .NET リモートと同じように動作ができます。で、適当なシリアライズを考えてやれば、なにも MarshalByRefObject を継承する必要はなくて、異なる .NET Framework のバージョン間でも大丈夫という具合。当然、XML-RPC でも動くわけですから、相手が PHP や Java でも大丈夫。

まあ、このまま使うのはあまりにも面倒なので、適当にラップする必要がありますが…ってのと、Store とかに載せた場合はどうなるの?ってのがありますが。一応、プロセス間通信はできるということで。

カテゴリー: C#, windows 8 | 2件のコメント

Windows Developer Days の感想

感想をざっくりと30分位で記録しておきます。以前だときちっとレポートを書いたのだけど、年齢には勝てず…ってことにしておく。

Microsoft Windows Developer Days (WDD) ホーム
http://www.microsoft.com/ja-jp/events/wdd/default.aspx

「Developer」ってことなので、Wndows 8 の metro アプリケーション、Visual Studio 11 での開発ってのが中心になります。私としては、既に acer w500 に windows 8 cosumer preview を入れ、visual studio 11 beta で試して、って具合でもあり、あとは半年後のための情報収集ってことで、

  • Visual C++ 関係
  • Azure 関係
  • ASP.NET MVC 関係

ってところを中心に聴講してきました。

で、結論から先に書くと。

  • xaml/c++ が意外と有望かも。Direct3D 関係を直接扱ってゲーム関係で metro でいけます。
  • metro アプリケーションと従来型のデスクトップアプリケーションは、共存可能、つーか、使い分けないと駄目。
  • metro アプリは基本ワンタスクで動く(バックグラインドに入ればサスペンド)。なので、意外と大雑把な作りをしても ok かも。
  • Azure を含むクラウド関係は、10年越しで計画を立てて徐々に移行という感じ。
  • ASP.NET MVC は、Web API を実装したら、WCF とかいらんかも。

な様相を呈しております。

ワタクシ的には、Windows 95 か XP が出た頃のインパクトがあるかなぁと思っていて、完全に metro に移行するのか、それとも windows 7 に留まるのか、という感じがするのですが、どうなんでしょうねぇ。Windows 8 の使い勝手としては、metro スタイルとデスクトップスタイルは共存が可能/使い分けが可能なので、ゲームやブラウジングなんかは metro でやって、お仕事ツール(開発関係)はデスクトップスタイルという感じになると思うのです。実際、Visual Studio 自体は metro スタイルにならないし(そういう言及もありました)。

しかし、クライアント関係では「見栄え」のところで 、メトロスタイルが出てくるかもしれません。ただし、Silverlight がああいう感じになってしまったのを考えると、「見栄え」だけでは難しいところもあります。予算的にITにどれだけ投資できるか?というもありますからね。しかし、タブレットPC/スレートPC、スマートフォンでは、従来型のデスクトップアプリでは非常に扱いにくいのは確かです。かつての Windows CE や Windows Mobile を完全に廃して、「メトロスタイル」というものを打ち出して来たのは、英断だとは思います…が、本当ならば WPF や Siverlight を打ち出した時にデザインだけでも打ち出しておけば良かったのでは?と思ってしまいます。
まあ、Apple の特許に引っ掛かった(引っ掛かるかもしれない危険を回避した)とも言えますが。

そういう意味で、メトロスタイルというデザインは、Android のユーザーインターフェースが iPhone に酷似するようには、似ていません。なので、別路線を打ち出したということで、ひとまず「特許/意匠」関係で訴えられるリスクを回避しているのでしょう。少なくとも私はそう考えています。

なので、iPhone ほどスマートフォンを考え抜いてはいないものの、新しい「頂き」を構築したのが、今回の Windows 8 のメトロスタイルです。いまのところ「第一」とは言えないものの、「最低」ではありません。ほどよく中間的なデザインだと思います。Apple/iPhone が「ブランド路線」だとすると、Google/Android は「廉価版」路線になっています。5万円ほどの iPad に対して、2,3万円で Android タブレットが買えますからね。となると、Windows 8 のメトロスタイルはどうなるかというと、という問題があるのですが、実はブランドでも廉価でもない路線になるのではと思っています。

ここ2日間ほど、win8 on acer を使ってみて分かったのは、普段使っている Windows アプリを acer にインストールして持って行けるところです。従来の outlook や power point のインターフェースは、タッチパネルで操作するには非常に困難なのですが、動かない訳ではありません。これは、iPad や Android の場合は、別途アプリケーションをインストールしないといけないのに対して、「従来のアプリが使える」という大きなメリットが windows 8 にはあります。使いにくいけれども、使えないことはない、という無理矢理な路線は、そう、ms-dos や windwos 3.1 の頃の感覚に似ています。横目で、unix の堅牢さや mac の素晴らしいインターフェース(けれども非常に高価な)を横目に、なにやらプラモデル感覚でプログラミングをしていた頃を思い出させてくれます。
メトロスタイルという一見「デザイン的に優れた」と銘打ってはいるものの、裏の意味は…と勘繰ってしまったりしますね。

で、話を元に戻すと、Visual C++ のインテリセンスが復活したこと、XAML を VC++ から使えるようになったこと、WinRT という形で windows api を組み替えつつあること、(おそらく)DirectX 2D が復活すること、という事実から、microsoft は再び c++ 開発者を取り込みにかかっているように見えます。現在、様々なアプリケーションのサンプルは「C#」で提供されることが多いのですが(日本の場合は、歴史的にVBも含まれています)、これからは直接XAMLを扱えるようになることから無理矢理C#を使わなくてよい、ということになりそうです。

この無理矢理のところ、一例を出しておくと、

  • DirectX 関係は、元々 C言語インターフェース、COM インターフェースが多い。スピードを要求されるところで、C# を使うのは気が引ける。
  • OpenCV のような画像解析は、C/C++ インターフェースとなっている。
  • Fortran との接続も C/C++ のほうが楽。
  • おそらく、Python, Ruby, Java との相互運用も C# よりも C++ のほうが楽ではないか(想像だけど)。

ということがあります。C# の場合、.NET Framework の中に納まってしまうので、相互運用をしようとするとどうしてもランタイムなところが邪魔してしまうのですね。これは、C++/CLI ではなくて、C言語そのものからC#を呼び出すとその複雑さがよくわかります。そうなると、ネイティブなメモリのマッピングだけを考えると、C/C++ が有利なのですよ。まぁ、それ以外のところ(それ以外のところが非常に多いのですがw)は、C#やVBで書くわけですが。

ちなみに、Objective-C で iPhone アプリを書くと、結構べたべたなコードが出てきます。いわゆるプログラム言語のデザインパターンはC#やJavaのほうが豊富なのです。ですが、iPhone アプリ(iOSアプリ)のクラス群は結構厳格なポリシーに沿って書かれているんですよね。そのあたりがうまく出来ていて、

  • iPhone の場合は、なんにも考えなくても「そこそこのデザインの画面ができる」
  • Windows の場合は、なんにも考えないと「そこそこのデザインの画面しかできない」

という感じがしています。そうですね、このあたりは今週末の.NETラボの勉強会で話そうかな…とネタを考えてみたり。まだプレゼン資料ができていないので、考えるのは自由ですから。

カテゴリー: 雑談 | 3件のコメント

Fortranで複雑な構造体にファイルをロードする

もう少し本格的に Fortran と C++ の相互運用を試してみる。

Fortran に FEMDATA構造体とFEMFILE構造体を作る。FEMDATA構造体は、有限要素データを意識してidと頂点(x,y,z)と応力(qx,qy,qz)のデータを持つ。この頂点データを、FEMFILE構造体にまとめて持つことにする。FEMDATA構造体の最大数は固定長にしておいて、別途 count を持って実際の頂点数を決める。

! 構造体定義
module FEMMODULE
    implicit none
    type FEMDATA
        integer :: id                ! idenitry code
        double precision :: x,y,z    ! position
        double precision :: qx,qy,qz ! potision stress
    end type FEMDATA

    type FEMFILE
        character*10    filename    ! file name
        character*10    author      ! author name
        character*10    makedate    ! make datetime 'yyyy/mm/dd'
        integer :: count            ! data count
        type(FEMDATA) dat(1000)     ! data
    end type FEMFILE

    type(FEMFILE) fdata             ! inner data

contains

    ! read data file
    subroutine FEMREAD( fname ) 
        character(*) :: fname
        character*80 :: temp
        integer :: i, count
        open(10,file=fname,status='old')
        read(10,*) temp, fdata%author
        read(10,*) temp, fdata%makedate
        read(10,*) temp, fdata%count
        count = fdata%count
        print *,"count:", fdata%author, fdata%makedate, count
        do i=1,count
            read(10,*) temp, &
             & fdata%dat(i)%id, &
             & fdata%dat(i)%x, & 
             & fdata%dat(i)%y, & 
             & fdata%dat(i)%z, & 
             & fdata%dat(i)%qx,& 
             & fdata%dat(i)%qy,& 
             & fdata%dat(i)%qz  
        end do
        close(10)
    end subroutine FEMREAD

    ! write data file
    subroutine FEMWRITE( fname ) 
        character(*) :: fname
        integer :: i, count

        fdata%filename = fname
        open(10,file=fname,status='replace')
        write(10,*) "author ", fdata%author
        write(10,*) "makedate ", fdata%makedate
        count = fdata%count
        write(10,*) "count ", fdata%count
        do i=1,count
            write(10,"(A,I5,6E15.7)") "data", &
             & fdata%dat(i)%id, &
             & fdata%dat(i)%x, & 
             & fdata%dat(i)%y, & 
             & fdata%dat(i)%z, & 
             & fdata%dat(i)%qx,& 
             & fdata%dat(i)%qy,& 
             & fdata%dat(i)%qz  
        end do
        close(10)
    end subroutine FEMWRITE

    ! set dummy data
    subroutine FEMDUMMY()
        integer :: i
        
        fdata%filename = "sample.txt"
        fdata%author   = "t.masuda"
        fdata%makedate = "2012-05-02"
        fdata%count = 100
        do i=1,100
            fdata%dat(i)%id = i
            fdata%dat(i)%x = 1.0
            fdata%dat(i)%y = 1.0
            fdata%dat(i)%z = 1.0
            fdata%dat(i)%qx = 0.1
            fdata%dat(i)%qy = 0.1
            fdata%dat(i)%qz = 0.1
        end do
    end subroutine FEMDUMMY

end module FEMMODULE

データをファイルに書き出すのがFEMWRITE関数で、同じファイルから読み込むのがFEMREAD関数。いわゆる永続化処理。Fortranで書き出しファイルはFortran自身で読み出すのが良いのと、配列の実体はFortran上にあるのでこれをアクセスするのは、Fortran自身で行うのが良い、という主旨です。

で、データをGUIを使って突っ込むところはC++(MFC)でやったほうが良かろうということです。最近のIntel Fortran では windowsアプリケーションを作ることもできるようなのですが、まぁ、C++で作ったほうが便利。もっと云えば、C++/CLIを通して、C#を使うのもありかと。ただし、C#で作る場合は、相互運用部分のクラスを相当考え抜かないと解析スピードに難が出そうな気がします。

出力されるデータは、以下のような形です。C言語でも読み込めないことはないけど、コードを見て分かる通り Fortran の場合は、read(*,*) でさっくりと読み込めます。

 author t.masuda  
 makedate 2012-05-02
 count          100
data    1  0.1000000E+01  0.1000000E+01  0.1000000E+01  0.1000000E+00  0.1000000E+00  0.1000000E+00
data    2  0.1000000E+01  0.1000000E+01  0.1000000E+01  0.1000000E+00  0.1000000E+00  0.1000000E+00
data    3  0.1000000E+01  0.1000000E+01  0.1000000E+01  0.1000000E+00  0.1000000E+00  0.1000000E+00
data    4  0.1000000E+01  0.1000000E+01  0.1000000E+01  0.1000000E+00  0.1000000E+00  0.1000000E+00
... 以下 100 まで続く

構造体自体が入れ子になっているので、以下のように「%」が二回続く(C言語で言う「.」です)のがいやらしいところですね。Fortranでポインタを使っても良いけど…C++の「参照(&)」に当たるものってあるんでしょうか?

        do i=1,count
            read(10,*) temp, &
             & fdata%dat(i)%id, &
             & fdata%dat(i)%x, & 
             & fdata%dat(i)%y, & 
             & fdata%dat(i)%z, & 
             & fdata%dat(i)%qx,& 
             & fdata%dat(i)%qy,& 
             & fdata%dat(i)%qz  
        end do

これを C++ から呼び出すときは以下のコードで。

extern "C" {
	void FEMMODULE_mp_FEMDUMMY( void );
	void FEMMODULE_mp_FEMWRITE( const char *);
}

// 構造体の再定義
struct FEMDATA {
	int id ;
	double x,y,z;
	double qx,qy,qz;
};
struct FEMFILE {
	char filename[10];
	char author[10];
	char makedate[10] ;
	int count ;
	FEMDATA dat[1000];
};
extern "C" FEMFILE FEMMODULE_mp_FDATA;

int _tmain(int argc, _TCHAR* argv[])
{
	cout << "call fortran struct" << endl;

	FEMMODULE_mp_FEMDUMMY();
	FEMMODULE_mp_FEMWRITE("sample03.txt");

	FEMFILE &dat = FEMMODULE_mp_FDATA ;
	cout << "filename: " << FEMMODULE_mp_FDATA.filename << endl;
	cout << "count: " << dat.count << endl ;
	return 0;
}

実は、このコードは意図的にバグがあって、filenameとかauthorとかは null 終端ではないのですよ。なので、単純に、FEMMODULE_mp_FDATA.filename を出力すると、10文字以下の文字もずらずらと表示されてしまいます。なので、この部分を string に変えたいですね。Fortran側で、NULL 分の char を入れておくという方法もあるのですが、以下な構造体で扱いたい。

struct FEMFILEX {
	string filename ;
	string author ;
	string makedate ;
	vector<FEMDATA*> dat;
};

文字列のところは string に変換して、配列のところは vector に直します。カウンターはいらないので vector::size を参照ということにしたい。欲を言えば、これを C++ のクラスにしたいですね。

Fortran の構造体と C++ のクラスを相互運用させる時、自前でちまちま書けばできないことはないのでしょうが、それだと可搬性が悪くなるし、たくさんのFortran構造体を扱ったときに、それだけで作業量が膨大になってしまう。なので、実務的にFortarn/C の相互運用を考える場合、

  1. Fortran構造体をC構造体に逐一直す作業
  2. C構造体をstring/vectorを使ったC++構造体に直す作業

を考えないといけない。これを template か適当なスクリプトでやろうかなと。1のほうはperlスクリプトかマクロを使って手作業、2のほうはtemplateを使えると良いかなと、思案中。

カテゴリー: C++, Fortran | Fortranで複雑な構造体にファイルをロードする はコメントを受け付けていません

FortranとC++のstringクラスでやり取りする

FortranではC言語の文字列(char[])位のやり取りをするついでに、C++のstringクラスとのやり取りも実験的に。
今回、画面側がMFCなので、CStringを使うかstringを使うか悩むところなのですが、まぁSTLをごりごり使うという前提でstd::stringを使ってみます。

module strmodule
    implicit none
contains
    ! 文字列を受け取る
    subroutine fstr1( str )
        character(*) :: str
        print *, "in fstr1:", str
    end subroutine fstr1
    ! 文字列を返す
    subroutine fstr2( str ) 
        character*80 :: str
        str = "masuda tomoaki"
    end subroutine fstr2
end module strmodule

Fortran側の module は、「character(*)」で文字列を受け取ります。逆に文字列を渡す場合には「character*80」のようにサイズを指定します。Fortranだけでで文字列を表示するときはいいのですが、C言語とやり取りする時にはうしろの空白が面倒なのです。Fortranの場合は、空きの分は空白で埋めてくれます。

extern "C" {
	void STRMODULE_mp_FSTR1( const char *str, int length );
	void STRMODULE_mp_FSTR2( char *str, int length );
}
// 文字列を設定
void FSTR1( string &str )
{
	STRMODULE_mp_FSTR1(str.c_str(), str.length());
}
// 文字列を取得
string FSTR2()
{
	static char buf[1000] = {0}; // 大きめのバッファ
	STRMODULE_mp_FSTR2( buf, sizeof(buf));
	string str = buf;
	int e = str.find_last_not_of(" ");
	if ( e != string::npos ) {
		str = string( str,0, e+1 );
	}
	return str;
}

なので、std::stringから、Fortranの関数に渡す時には長さを指定して、逆に受け取る場合には string::find_last_not_of で後ろの空白を削除する(trimする)という定番処理が必要になります。

	// string クラスとの相互運用
	string str = "masuda tomoaki";
	// そのまま fortran に渡す
	cout << "call FSTR1" << endl;
	FSTR1( str );
	// fortran から受け取る
	str = FSTR2();
	cout << "call FSTR2:[" << str << "]" << endl;

文字列の場合は、この変換が必要なので適当なマクロを作るか、関数を作るかするかなぁと。char[] のまま C++ で扱うのは厄介なので(今回、STLを使ってよいという話なので)、このあたりは適当に隠蔽したいところ。

本来ならば、unicode を気遣う必要があるのですが、今回は英語版なので…まあ良しとしましょう(ってのがi18nの問題でもあるのだけど、既にあるコードが string を使っているからね。Unicode に気を遣う場合は、MFC の時は CString を使うのが良いです)。

カテゴリー: C++, Fortran | FortranとC++のstringクラスでやり取りする はコメントを受け付けていません

Fortranでファイルの読み書きRead/Writeする

Fortran入門: データ入出力
http://www.nag-j.co.jp/fortran/FI_14.html
format文 (Fortranプログラミング入門マニュアル | Fortran プログラミング 入門 講座)
http://harukin.la.coocan.jp/fortran/004/format.html

Fortranのファイル読み書き&フォーマット文に関しては上記を参照。
ただし、Fortran77 の時もそうだったのだが、Fortran で書き出して、Fortran で読み込む分にはさほど書式に気を遣わなくていい…というか、適当に write(*,*) で書き出して、read(*,*) で読み込めるってのが Fortran の便利なところ。

program Console1
    implicit none
    integer count
    integer readdata
    character*80 fname

    ! ファイル出力
    open(10,file='sample.txt',status='replace')
    write (10,*) 'Hello', x, y 
    close(10)
    print *, 'write sample.txt'

	! 一応初期化    
    x = 0 
    y = 0

    ! ファイル入力
    open(11,file='sample.txt',status='old')
    read(11,*), str, x, y
    close(11)
    
    print *, 'read sample.txt'
    print *, 'read:', str, x, y
    
    fname = 'sample2.txt'
    count = readdata(fname)
    print *,'read count:', count 
end program Console1

フォーマットを決めずに write した後に read する例。単純に、変数を並べて write すれば良しなにしてくれるので非常に便利です。固定のデータ数や配列の場合には、ちまちま書くか、do ループを使って固定数分読み書きすればよい。

    ! データを終端 "END" まで読み込む
    integer function readdata(fname)
        implicit none
        ! character*80 :: fname
        character(*), intent(in) :: fname
        integer :: count
        character*20 :: mark
        integer :: x, y
        count = 0
        
        print *,'fname:', fname
        open(10,file=fname,status='old')
        do 
            read(10,*,end=100), mark, x, y
            count = count + 1
            if ( mark == 'end' ) then
                exit
            end if
            print *, 'read:',x,y
        end do
100     close(11)        
        readdata = count
    end function readdata

この do ループの部分は定番です。read の時に end=100 のように行番号で飛ばすのは、Fortran90 のスタイルではどうなのだろう?と思うのですが、ひとまずこれで。ファイル番号を付けるのは、VB6 と同じです。適当にダブらない番号を付ければ OK です。ただし、サブルーチンからサブルーチンを呼び出すときに、このファイル番号がダブった場合どうなるのかは不明。なので、open/close 自体はちょっと注意しないと。

このサンプルでは、文字列の先頭が「end」で終わったときに終端と判断していますが、数値が「-1」の時というのも定番な動作です。C++の場合は、1行読み込んだ後に、先頭が「end」なのか、有効な数字なのかという判断をしますが、Fortranの場合は、文字列を数値を混在させるのは面倒なので、最初のカラムが数値ならば「-1」で、文字列にするならば「end」でという使い分けをしたほうが良いでしょう…と fortran90 初心者は思う訳です(fortran77は昔やったことがあるけど)。「end」を終端にする場合は、先頭のカラムの文字列は捨てカラムにするのが良いですね。

data	10	21
data	11	22
data	12	23
data	13	24
end

のように、先頭のカラムを捨ててしまうわけです。実は、これが便利なのは先頭が「data」の時はデータ行をあらわして「end」の場合は終端、という形で1行を読み込んだときに判断が付くところです。perl で処理する時に楽だったりします。大学の頃は、フォーマットの前処理を perl でやって、最終的に fortran で読み込ませる方法を取っていました。

カテゴリー: 開発, Fortran | Fortranでファイルの読み書きRead/Writeする はコメントを受け付けていません

FortranにC++から文字列を渡す

文字列の受け渡しはなるべくしたくないのだが、設定やら名称取得やらで必要になってくる。
Fortran の場合は、NULL terminate ではないのでC++で扱うときにはちょっと厄介である…そういえば、stringクラスを使うことになるので、そのあたりも考えないと。
基本は、固定領域をとっておいて、あとからC++側でnullを付け加えるとok。

module structmodule
    implicit none
contains
    ! 文字列を受け取る
    subroutine fstr1( s )
        character*20 s
        print *, 'in fstr1:[', s,']'
    end subroutine fstr1
    ! 文字列を渡す
    subroutine fstr2( s )
        character*20 s
        integer i
        ! * で埋める
        do i=1,10
            s(i:i) = '*'
        end do
    end subroutine fstr2
    ! 文字列を渡す
    subroutine fstr3( s )
        character*20 :: s
        s = 't.masuda'
    end subroutine fstr3
end module structmodule

文字列を引数にすると、C++の関数では長さを定義する変数が入る。これは数値の変数とは違って、値型で渡す。

extern "C" {
	// 文字列を渡す
	void STRUCTMODULE_mp_FSTR1( char *str, int count );
	// 文字列を受け取る
	void STRUCTMODULE_mp_FSTR2( char *str, int count );
	void STRUCTMODULE_mp_FSTR3( char *str, int count );
}
	char str1[20+1] = {0};
	strcpy( str1, "t.masuda" );
	STRUCTMODULE_mp_FSTR1( str1, sizeof(str1)-1);

	char str2[20+1] = {0};
	STRUCTMODULE_mp_FSTR2( str2, sizeof(str2)-1);
	cout << "str2:[" << str2 << "]" << endl;

	char str3[20+1] = {0};
	STRUCTMODULE_mp_FSTR3( str3, sizeof(str3)-1);
	cout << "str3:[" << str3 << "]" << endl;

戻り値で扱うことができるかは不明。引数で渡すほうが無難だろう。
string型との変換を考えると、適当なマクロをC++側に仕込むのがよかろう。

カテゴリー: 開発, C++, Fortran | 1件のコメント