.NET Compact FrameworkのP/Invoke

WZero3 でアプリを書いていて、接続の制御をしようとして適当に書いていたら、動かない。ちょっと調べてみたら、.NET Compact Framework は .NET Framework のサブセットなので完全な .NET Framework の方法とはやや異なる。ということで、注意が必要。

Microsoft .NET Compact Framework の P/Invoke とマーシャリング入門

Microsoft .NET Compact Framework での高度な P/Invoke

 
ぐちゃぐちゃ書いてあって、わかりにくいので、簡単にポイントだけ整理する。
.NET Compact Framework の相違点の概要
    • SetLastError を true にすることを忘れないように。[DllImport("abc.dll", SetLastError=true)]
    • すべてが Unicode
    • Winapi(既定のCdecl) のみをサポート
    • .NET Compact FrameworkのP/Invokeは、 コールバックをサポートしない
    • EntryPointNotFoundException, ExecutionEngineExceptionのかわりに、 MissingMethodExceptionとNotSupportedExceptionが上がる
    • Formでは、ウィンドウハンドル (hwnd)、DefWndProc メソッドがサポートされない。MessageWindow、Messageクラスを使用して、他のウィンドウにメッセージを送信できる。サンプル コード… smartdevices.microsoftdev.com 。
    • 複合オブジェクト (参照型) をマーシャリングできないことがある。特に、構造体の中にstring配列があるような場合は注意。対応法方法は複数あり。

構造体内の文字列のマーシャリング

構造体内またはクラス内の文字列ポインタを正しくマーシャリングできない。対応方法は次の3つがある。
      • サンク層での呼び出し
      • unsafe ブロックの使用
      • 文字列ポインタを処理するカスタム クラスの作成

構造体内の固定長文字列のマーシャリング

System.Char の配列が実行時に配列への 4 バイト ポインタとしてマーシャリングされるので、構造体内の固定長文字列のマーシャリングは単純には動作しない。対応方法は2つ。
    • 正確な合計サイズのバイト配列を作成した後、構造体の各フィールドをバイト配列にコピーしたり、バイト配列からコピーする。 →複雑。
    • カスタム マーシャリングを組み合わせる方法。

——————

class Memory のC#版
using System;
using System.Runtime.InteropServices;
using System.ComponentModel;
namespace Uchukamen.WZero3
{
    class Memory
    {
        [DllImport("coredll.dll", SetLastError = true)]
        private static extern IntPtr LocalAlloc(int uFlags, int uByte);
        [DllImport("coredll.dll", SetLastError = true)]
        private static extern IntPtr LocalFree(IntPtr hMem);
        [DllImport("coredll.dll", SetLastError = true)]
        private static extern IntPtr LocalReAlloc(IntPtr hMem, int uBytes, int fuFlags);
        private const int LMEM_FIXED = 0;
        private const int LMEM_MOVEABLE = 2;
        private const int LMEM_ZEROINIT = 0x40;
        private const int LPTR = LMEM_FIXED | LMEM_ZEROINIT;
        // LocalAlloc を使用して、メモリ ブロックを割り当てます。
        public static IntPtr AllocHLocal(int cb)
        {
            return LocalAlloc(LPTR, cb);
        }
        // AllocHLocal で割り当てられたメモリを解放します。
        public static void FreeHLocal(IntPtr hlocal)
        {
            if (!hlocal.Equals(IntPtr.Zero))
            {
                if (!IntPtr.Zero.Equals(LocalFree(hlocal)))
                {
                    throw new Win32Exception(Marshal.GetLastWin32Error());
                }
                hlocal = IntPtr.Zero;
            }
        }
        // 以前に AllocHLocal で割り当てられたメモリ ブロックのサイズを変更します。
        public static IntPtr ReAllocHLocal(IntPtr pv, int cb)
        {
            IntPtr newMem = LocalReAlloc(pv, cb, LMEM_MOVEABLE);
            if (newMem.Equals(IntPtr.Zero))
            {
                throw new OutOfMemoryException();
            }
            return newMem;
        }
        // マネージ文字列の内容をアンマネージ メモリにコピーします。
        public static IntPtr StringToHLocalUni(string s)
        {
            if (s == null)
                return IntPtr.Zero;
            else
            {
                int nc = s.Length;
                int len = 2 * (1 + nc);
                IntPtr hLocal = AllocHLocal(len);
                if (hLocal.Equals(IntPtr.Zero))
                    throw new OutOfMemoryException();
                else
                {
                    Marshal.Copy(s.ToCharArray(), 0, hLocal, s.Length);
                    return hLocal;
                }
            }
        }
    }
}
 

RASCONN

結構面倒
  C:\Program Files\Microsoft Visual Studio 8\SmartDevices\SDK\Smartphone2003\Include\ras.h
#define RAS_MaxEntryName      20
RASCONNW
{
    DWORD    dwSize;
    HRASCONN hrasconn;
    WCHAR    szEntryName[ RAS_MaxEntryName + 1 ];
};

#define RASCONN RASCONNW

 
注意: sizeof RASCONN は、4 + 4 + 2 * ( RAS_MaxEntryName + 1 ) = 50 byteだけども、4byte バウンダリーになるため、52 byteになる。
————-
参考になるリンク

WZero3 ActiveApplicationの取得

        SystemState activeApplication = new SystemState(SystemProperty.ActiveApplication);
        activeApplication.Changed += new ChangeEventHandler(activeApplication_Changed);
 
        void activeApplication_Changed(object sender, ChangeEventArgs args)
        {
            string res = (string)args.NewValue;
            string[] apps = res.Split(new char[] { ‘\x1b’ });
        }
 
エスケープシーケンスで区切られた文字列でアクティブなアプリケーションの名前が帰ってくる。

W-SIM状態チェック

こたさんからの情報
通信中を強制切断するには?
 
【 W-SIM状態チェック】
        [DllImport("shphonelib.dll", EntryPoint = "?GetWsimStateInfo@CshphoneClientlib@@SAHXZ")]
        public static extern int GetWsimStateInfo();

//0:通常
//-2:W-SIMなし
//-3:通信中
//-7:停止

【 W-SIM ON/OFF】
        [DllImport("shphonelib.dll", EntryPoint = "?SuspendRadio@CshphoneClientlib@@SAHH@Z")]
        public static extern int SuspendRadio( int val);
    val…0  W-SIM アンテナOff
    val…1  W-SIM アンテナOn
 
 

緯度、経度より、最も至近のAmedasの観測所コードを取得する XML Web Service

全国のAmedas コードをデータベースに入れて、経度、緯度から、至近の観測ポイントのIDを取り出すストアドを作って、XML Web Service化した。
 
呼び出しは、
int code = amedas.GetNearestCode(35.1239f, 139.455f);
ああっ、なんて楽チンな。
 
緯度、経度をとる方法は分かったが、こたさんの情報によると、InterOpしなきゃ・・だめか・・・
あと2日。
————-
using System;
using System.Data;
using System.Web;
using System.Collections;
using System.Web.Services;
using System.Web.Services.Protocols;
using System.ComponentModel;
using System.Configuration;
using System.Data.SqlClient;
namespace WebServiceAmedas
{
    /// <summary>
    /// WebServiceAmedas の概要の説明です
    /// </summary>
    [WebService(Namespace = "http://uchukamen.com/")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    [ToolboxItem(false)]
    public class Amedas : System.Web.Services.WebService
    {
        /// <summary>
        /// 緯度、経度より、最も至近のAmedasのエリアコードを取得する。
        /// </summary>
        /// <param name="latitude">緯度</param>
        /// <param name="longitude">経度</param>
        /// <returns></returns>
        [WebMethod]
        public int GetNearestCode(float latitude, float longitude)
        {
            string connectionString = ConfigurationManager.ConnectionStrings["uchukamen_comConnectionString1"].ConnectionString;
            using (SqlConnection sqlConn = new SqlConnection(connectionString))
            using (SqlCommand sqlCmdStored = new SqlCommand("GetNearestAmedasCode"))
            {
                sqlCmdStored.CommandType = CommandType.StoredProcedure;
                SqlParameter sqlLatitude = new SqlParameter("@緯度", SqlDbType.Float);
                sqlLatitude.Value = latitude;
                sqlCmdStored.Parameters.Add(sqlLatitude);
                SqlParameter sqlLongitude = new SqlParameter("@経度", SqlDbType.Float);
                sqlLongitude.Value = longitude;
                sqlCmdStored.Parameters.Add(sqlLongitude);
                sqlConn.Open();
                sqlCmdStored.Connection = sqlConn;
                using (SqlDataReader dr = sqlCmdStored.ExecuteReader())
                {
                    if (dr.Read())
                    {
                        return (int)dr["ID"];
                    }
                }
            }
            return 0;
        }
    }
}

XML Web Serviceで名前空間を変更するとエラー

たとえば、XML Web Service のファイルで、namespace WebServiceAmedasを、namespace Uchukamen.WZero3 のように変更して、その他関連する場所も変更したつもりでも、なぜかエラーになる。何が関係しているのか不明・・・。リファクタリングでも面倒みてくれない。今は時間がないので、とりあえずメモ。

発生するエラーは、次の通り。

‘/’ アプリケーションでサーバー エラーが発生しました。


ランタイム エラー

説明: サーバーでアプリケーション エラーが発生しました。このアプリケーションの現在のカスタム エラー設定では、セキュリティ上の理由により、アプリケーション エラーの詳細をリモート表示できません。ただし、ローカル サーバー コンピュータで実行されているブラウザで表示することはできます。

詳細: このエラー メッセージの詳細をリモート コンピュータで表示できるようにするには、現在の Web アプリケーションのルート ディレクトリにある "web.config" 構成ファイル内に、<customErrors> タグを作成してください。その後で、この <customErrors> タグで "mode" 属性を "off" に設定してください。

リサイズイベントで、スクリーンオリエンテーションを変更すると例外

環境: Windows Mobile 5.0、.NET Compact Framework 2.0
 
 
レイアウトがめんどくさいので、スクリーンのオリエンテーションが変更されるときに発生するリサイズイベントの中で、スクリーンのオリエンテーションを変更しようとすると、例外("RegistryException")になる。
 
        private void Form1_Resize(object sender, EventArgs e)
        {
            if (SystemState.DisplayRotation == 0)
                SystemSettings.ScreenOrientation = ScreenOrientation.Angle90;
        }
 
この対応は、 
..NET Compact Framework 向けの表示方向切り替え対応および高dpi対応アプリケーションの開発
にあるように、ポートレート、ランドスケープ、どちらでも正しくレイアウトするようにする。
 
ただ、コントロールの数が多いと大変。
今回は、面倒だけど、この方法で対処。

コードピッチテクニック (code pitching technique ) とは

コードピッチテクニック (code pitching technique ) とは
 
しばらく使われていないメソッドで使われているメモリを解放し、メソッドのネィティブコードコードブロックを解放する機能。
コードブロックをスタブで置き換え、再度呼ばれた時にスタブがJITerを呼び出し、ネィティブコードを再生成する。
 
ふ~~ん、苦労しているねぇ・・・
早く Windows Mobile 6.0 出ないかな・・・そうすればメモリの制約がかなり軽減されるはず。
 
参照先
 
In addition, the economy JITter supports code pitching. Code pitching is the ability for the common language runtime to discard a method’s native code block, freeing up memory used by methods that haven’t been executed in a while. Of course, when the common language runtime pitches a block of code, it replaces the method with a stub so that the JITter can regenerate the native code the next time the method is called.