Archive for 2012/05

第2回 はこだて IT 見本市で展示します

明日と明後日(6/1, 2)に函館駅前の WAKO ビル 3F 会場で開催される、「第2回 はこだて IT 見本市」に、EMS-JP グループ北海道支部加盟企業として、弊社も出展します。
 http://hakoika.jp/modules/bulletin/index.php?page=article&storyid=34

Armadillo-400 シリーズの全ての機種、つまり、Armadillo-440, 460, そして 420 で Windows Embedded Compact 7 が動作する様子を展示します。お近くの方は、ぜひ会場にいらして下さいませ。

Add comment 2012/05/31 koga

第三回 D2C (Device2Cloud) コンテスト

東京エレクトロンデバイス社主催の、D2C (Device2Cloud) コンテストを、一昨年(昨年3月決勝)と昨年(今年3月決勝)に続き、今年も開催することになりました:
 http://www.d2c-con.com/

第三回の今回は、3月ではなく、12月初めに決勝大会を実施します。参加される学生さんにとっては、3月に決勝大会を実施するよりも、12月初めに実施する方が、より参加しやすいのではないかと考えたからです。興味のある方は、ふるってご参加下さい。面白いアイディアが出てくることを、楽しみにしています。なお、上のコンテストのページには、「参加者向けトレーニング」を7月下旬もしくは8月上旬に実施予定となっていますが(5/25現在)、参加申し込み締め切りに近い9月上旬に実施する予定で、現在調整中です。

弊社は、過去二回と同様、今回も、CPU ボードの指定機材である Armadillo-440 用の、Windows Embedded Compact 7 (WEC 7) の BSP を提供します。これまで WEC 7 に触れたことのない学生の皆さんに、WEC 7 を使って組込みアプリケーション開発を体験するきっかけを提供できれば、素敵だなと考えています。参加を考えていらっしゃる皆さん、参加者向けのトレーニング、そして、決勝大会でお会いしましょう!

Add comment 2012/05/25 koga

ネイティブコードから C# のメソッドをコールバック

今回は、ネイティブコード(アンマネージコード)から C# のメソッド、つまりマネージドコードをコールバックする方法について紹介します。

■C# のコールバック実装
ネイティブコードから C# のメソッドをコールバックさせる場合の手順を説明する前に、C# でコールバック関数を実装する方法について、簡単に説明します。C#/CLR の delegate についてご存じの方は、以下の説明は飛ばして、次の「ネイティブコードからコールバックさせる場合の手順」をご覧になって下さい。

C# でコールバック関数を実装する場合は、delegate を定義して、コールバック関数として用いるメソッドを、その delegate に割り当てます。delegate については、C# のプログラミングガイドの、次のページで説明されています。

 delegate (C# Reference)
 http://msdn.microsoft.com/en-us/library/900fyy8e%28v=VS.80%29.aspx

 Delegates (C# Programming Guide)
 http://msdn.microsoft.com/en-us/library/ms173171(v=vs.80).aspx

上のページで説明されているように、delegate は、メソッドに対する参照型(reference type)です。delegate は、delegate キーワードを使って、通常のメソッドと同じように宣言しますが、delegate と同じシグネチャを持つメソッドを、その実体として割り当てることができます。この時、宣言された delegate に対して、System.Delegate クラスの派生クラスがコンパイラによって生成され、インスタンス化されます。Delegate クラスのリファレンスは、次のページにあります。

 Delegate Class
 http://msdn.microsoft.com/en-us/library/system.delegate(v=vs.90).aspx

なお、delegate に割り当てることのできるメソッドは、static である必要はなく、インスタンスメソッドを割り当てることが可能です。たとえば、System.Threading の Thread クラスは、コンストラクタの引数に ThreadStart という delegate を受け取り、その delegate に割り当てられたメソッド実体を、インスタンス内部で生成するスレッドによって実行します。ここで、インスタンスメソッドを delegate に割り当てることができるので、C++ の場合とは違い、クラスのインスタンスは渡す必要がない、というわけです。

■ネイティブコードからコールバックさせる場合の手順
さて、本題です。ネイティブコードから C# のメソッドをコールバックさせる手順ですが、実は簡単です。必要な手順は、次の三つです:

  1. コールバックさせるメソッドに対応する delegate を定義する。
  2. 定義した delegate にメソッドを割り当てたのち、System.Runtime.InteropServices.GCHandle 構造体の Alloc() メソッドを適用して、delegate 実体に対する GCHandle インスタンスを生成する。
  3. delegate 実体に、System.Runtime.InteropServices.Marshal クラスの GetFunctionPointerForDelegate() を適用して、その戻り値を、関数ポインタとしてネイティブコードに渡す。

C のネイティブコード API で、コールバック関数の関数ポインタを受け取るものを使ってコールバックする場合ですと、関数ポインタの引数を IntPtr として P/Invoke の宣言を行い、System.Runtime.InteropServices.Marshal クラスの GetFunctionPointerForDelegate() の戻り値(IntPtr 型)を、関数ポインタの引数として渡せばよいのです。ただし、それだけですと、その関数ポインタに関連づけられた delegate の実体、つまり C# のメソッド(インスタンスメソッドの場合のインスタンスおよび、クラス)が、ガベージコレクション(GC)によって解放されてしまう可能性があります。そのため、メソッドを割り当てた delegate 実体に対して GCHandle のインスタンスを生成し、GC の対象外にする、というわけです。

GCHandle 構造体および、System.Runtime.InteropServices.Marshal クラスの GetFunctionPointerForDelegate() については、リファレンスの次のページをご覧ください。

 GCHandle Structure
 http://msdn.microsoft.com/en-us/library/khk3k17t(v=vs.90)

 Marshal.GetFunctionPointerForDelegate Method
 http://msdn.microsoft.com/en-us/library/system.runtime.interopservices.marshal.getfunctionpointerfordelegate(v=vs.90)

ここで述べた、ネイティブコード(アンマネージコード)からマネージドコードを呼び出す手順については、Bojan Resnik という人の Blog でも紹介されています:

 Passing C++/CLI delegate to native code
 http://resnikb.wordpress.com/2009/05/18/passing-ccli-delegate-to-native-code/

この Blog エントリでは、C# ではなく、C++/CLI の場合の例を説明していますが、C# の場合も同様です。上の説明と合わせ、参考にしてみて下さい。

■おまけ: .NET CF のガベージコレクション
上の説明で、ガベージコレクション(GC)について述べましたので、ついでに、.NET CF の GC について留意点を書いておきます。

まず、.NET CF の GC は、フル版の .NET Framework の GC とは実装が異なります。フル版の .NET Framework では(より正確には、フル版の .NET Framework の CLR では)、世代別 GC かつ、コンカレントまたはバックグラウンド GC が実装されています。詳しくは、次のページをご覧ください。

 Fundamentals of Garbage Collection
 http://msdn.microsoft.com/en-us/library/ee787088

一方、.NET CF では、コンパクション付きのマーク&スイープ GC です。これについては、.NET CF チームの Steven Pratschner という人が MSDN Blog に書いたエントリが参考になります:

 An Overview of the .Net Compact Framework Garbage Collector
 http://blogs.msdn.com/b/stevenpr/archive/2004/07/26/197254.aspx

 The Design of the .Net Compact Framework CLR, Part III: GC Heap Management
 http://blogs.msdn.com/b/stevenpr/archive/2005/12/14/503818.aspx

.NET CF の GC は、コンカレント GC ではなく、また、世代別 GC でもないため、GC が起きた際の、GC 実行中におけるマネージドコード全スレッドの停止と、停止期間について留意する必要があります。一般論としては、マネージドコードでは、リアルタイム処理に関わる動作を行わず、リアルタイム処理は全てアンマネージコードに任せるか、または、頻繁に GC が起きないように設計する方が良いと思います。GC が頻繁に起きないようにする方策としては、マネージドコードでも、バッファやオブジェクトのプールを実装し、過度に GC に頼らないようにすることが考えられます。もちろん、GC に頼らないということにこだわり過ぎて、全てのオブジェクトのメモリ管理を自前で行おうとして複雑な設計や実装をするのは、本末転倒です。とはいえ、数百KB以上のサイズのオブジェクトやバッファ領域を頻繁に使用する場合には、それらを都度 new で生成して GC に回収させるのではなく、あらかじめ必要な個数を生成しておき(プーリング)、それらを使いまわすプール方式で実装する方が良い場合は、少なくないと思います。

GC の場合も、チューニングを行う場合には、定性的な評価・予測に頼るのではなく、実測に基づく、定量的な評価・分析が重要です。たとえば、「GC.Collect() をアプリケーションが呼ぶのは間違いか?」という意味のタイトルの次の質問に対する回答でも、「それが有効な場合は、レアケースとして考えられる。ただし、十分に実測して、そのうえで効果を判定すべし。」と書かれています:

 What’s so wrong about using GC.Collect()?
 http://stackoverflow.com/questions/118633/whats-so-wrong-about-using-gc-collect

2 comments 2012/05/14 koga

WEC/WinCE から共有ディレクトリ(ファイルサーバ)をアクセス

前回のエントリで、.NET CF アプリケーションから WNetAddConnection3() を呼び出す例を述べました。その例の説明で書いたように、WNetAddConnection3() は、Windows フィルサーバとの接続などを行う API です。

■Windows ファイルサーバに対するクライアント機能
Windows ファイルサーバ(CIFS サーバ)をアクセスする機能、つまり、Windows ファイルサーバに対するクライアント機能は、”Windows Networking API/Redirector” というコンポーネントになっており、SYSGEN 変数の SYSGEN_REDIR を設定することにより、OS イメージに組み込まれます。詳細については、リファレンスの次のページをご覧ください。

 Windows Networking API/Redirector Reference (Windows Embedded Compact 7)
 http://msdn.microsoft.com/en-us/library/ee495264.aspx

 Windows Networking API/Redirector (Windows Embedded CE 6.0)
 http://msdn.microsoft.com/en-US/library/ee494246(v=winembedded.60).aspx

Platform Builder のカタログ項目ビュー(Catalog Items View)の階層で言うと、次の項目を選択すると、SMB/CIFS クライアント機能が OS Design に組み込まれます(※WEC 7 の場合):

 <OS Design 名>
 Core OS
  Windows Embedded Compact
   Communication Services and Networking
    Networking – General
★    Windows Networking API/Redirector (SMB/CIFS)

ここで、”Redirector” という Windows の用語に馴染みのない方は、たとえば、次の解説記事が参考になるのではないかと思います。

 「基礎から学ぶWindowsネットワーク 第20回」
 http://www.atmarkit.co.jp/fwin2k/network/baswinlan020/baswinlan020_03.html

 「基礎から学ぶWindowsネットワーク」
 http://www.atmarkit.co.jp/fwin2k/network/baswinlan002/baswinlan002_03.html

なお、CIFS クライアントだけでなく、CIFS サーバ機能も WEC/WinCE に付属しています。こちらは、カタログ項目ビューの階層の、次の場所にあります:

 <OS Design 名>
 Core OS
  Windows Embedded Compact
   Communication Services and Networking
    Servers
     File Server (SMB/CIFS)

CIFS サーバに関する記述は、なぜか WEC 7 のリファレンスに見当たりません。興味のある方は、WinCE 6.0 のリファレンスをご覧ください。

 File Server (Windows Embedded CE 6.0)
 http://msdn.microsoft.com/en-us/library/ee500573(v=winembedded.60).aspx

■SMB/CIFS クライアント機能を組み込む場合の注意点
さて、SMB/CIFS クライアント機能(Windows Networking API/Redirector)を組み込む場合、一つ注意しなければいけません。WEC/WinCE のリファレンスには、このコンポーネントが依存するのは、TCP/IP (SYSGEN_TCPIP) と Winsock (SYSGEN_WINSOCK)、および NDIS (SYSGEN_NDIS) とだけ書かれているのですが、それらのコンポーネントだけでは、SMB/CIFS サーバにアクセスすることは、できません。

CIFS サーバに接続する場合は、認証処理が必要ですが、認証処理に必要なコンポーネントは、SMB/CIFS クライアント機能を組み込んでも自動的に組み込まれないため、明示的に組み込む必要があるのです。認証処理に必要なコンポーネントは、WEC 7 ですと、カタログ項目ビューの階層の、次の場所にあります:

 <OS Design 名>
 Core OS
  Windows Embedded Compact
   Security
    Authentication Services (SSPI)
★    Kerberos
★    NTLM

通常は、NTLM だけを選択しても CIFS サーバに接続できるでしょう。お手元の環境で、試してみて下さい。これらのコンポーネントについては、リファレンスの次のページで説明されています(※WEC 7 の場合)。

 NTLM Security Support Provider (Windows Embedded Compact 7)
 http://msdn.microsoft.com/en-us/library/ee498104.aspx

 Kerberos Security Support Provider (Windows Embedded Compact 7)
 http://msdn.microsoft.com/en-us/library/ee498711.aspx

 Authentication Services (Windows Embedded Compact 7)
 http://msdn.microsoft.com/en-us/library/ee498877.aspx

Add comment 2012/05/07 koga

.NET CF で Win32 API を P/Invoke する時の注意点

WEC/WinCE で、.NET CF (.NET Compact Framework) のクラスライブラリでは提供されていない API を使う場合は、CLR (Common Language Run-time) の P/Invoke を利用する必要があります。P/Invoke の仕組み自体は、.NET Framework と同じですが、.NET CF における制約や、WEC/WinCE での Win32 API の細かな違いがあり、.NET Framework と全く同じにはできない場合があります。

以下では、それらの注意点のうち、二点紹介します。

■Win32 API の「実際の」名前が違う場合
P/Invoke を使って Win32 API を呼び出す場合に、.NET Framework では動作したコードが、.NET CF では動作しない、という場合があります。つまり、Windows 7 などでは動作した C# のコードを、WEC/WinCE 用にビルドして動かそうとすると、例外送出が起きて動作しない、という場合があるのです。これは、その API の実際の名前が、WEC/WinCE では違っていることが原因です。

例1)InternetConnect()
wininet.dll で提供されている InternetConnect() は、マクロであり、ネイティブコード(アンマネージコード)の ANSI ビルドでは InternetConnectA(), Unicode ビルドでは InternetConnectW() の呼び出しに置換されます。ここで、.NET Framework の場合には、ANSI ビルド用の InternetConnectA() も使うことができますが、WEC/WinCE は Unicode のみであるため、InternetConnectA() は提供されておらず、使うことが出来ません。従って、InternetConnectW という名前を指定しなければいけません。

例2)SetEvent()
Windows 7 などでは、SetEvent() は kernel32.dll で提供されている一方、WEC/WinCE では、SetEvent() は coredll.dll で提供されています。DLL の名前が違うことに加え、.NET CF で SetEvent に対して P/Invoke 呼び出しを行うと、そのような関数は実装されていないというエラーになります(例外が起きます)。実は、WEC/WinCE では、SetEvent() という名前の API 関数の実体は、存在しないのです。何が違うのかは、両者のリファレンスから辿って、関数宣言のヘッダファイルを調べてみると分ります:

 ・デスクトップ版の SetEvent()
 http://msdn.microsoft.com/en-us/library/windows/desktop/ms686211(v=vs.85).aspx 

 ・WEC/WinCE 版の SetEvent()
 http://msdn.microsoft.com/en-us/library/ee488785.aspx

上の二つのページで、”Requirements” の項にある表の、”Header” を見て下さい。デスクトップ版では、WinBase.h ですが、WEC/WinCE 版では kfuncs.h となっています。WEC/WinCE のソースツリーにある winbase.h を見てみると(%_WINCEROOT%/public/COMMON/sdk/inc/ にあります)、SetEvent() の宣言は、#ifndef UNDER_CE … #endif で囲われており、WEC/WinCE では無効になっています。そして、それらの関数は、kfuncs.h で宣言/定義されているというコメントがあるのです。そこで、kfuncs.h を見てみると、SetEvent() は、_inline 修飾子を使ってインライン関数として定義され、EventModify() という関数を呼び出していることが分かります。EventModify() は、二つの引数を取り、SetEvent() の場合は EVENT_SET という定数(値は3)を第二引数に渡します。つまり、WEC/WinCE では、SetEvent() という名前の関数の実体は、存在しないのです。SetEvent() の呼び出しは、EventModify() の呼び出しに展開されますので、P/Invoke する場合は、EventModify という名前を指定しなければいけません。

このように、同じ名前の Win32 API であっても、WEC/WinCE では実体が異なる場合があるため、.NET Framework では動いたコードが、.NET CF では動かない、という場合があるのです。Win32 API を P/Invoke する場合には、リファレンスを見て、WEC/WinCE では実体が異なっていないかどうか確認してみて下さい。リファレンスだけでは分かりづらいですから、見落とす場合もあると思います。従って、Win32 API を P/Invoke する場合は、必ず単体テストして、期待通りに動作するかどうかを確認し、エラーが起きる場合には、ヘッダファイルをチェックしてみるのが良いでしょう。

■P/Invoke のマーシャリングに関する制約
P/Invoke における、関数引数のマーシャリングは、.NET Framework と .NET CF で一部異なっています。次のページに、その違いがまとめられています:
 An Introduction to P/Invoke and Marshaling on the Microsoft .NET Compact Framework
 http://msdn.microsoft.com/en-us/library/aa446536.aspx

ここで述べられている違いの中で、Win32 API を P/Invoke する際に注意が必要なのは、.NET CF の場合、構造体メンバ中に参照型(reference type)があっても、マーシャリングされない、という点です。上のページの、”Passing Structures” という項に書かれています。これは、文字列型のメンバを持つ構造体を引数に受け取る API 関数を P/Invoke する際に、問題となります。

たとえば、Windows ファイルサーバ(CIFS サーバ)との接続などを行う、WNetAddConnection3() は、NETRESOURCE 構造体を引数に受け取りますが、この NETRESOURCE 構造体は、文字列型のメンバを持ちます。なお、WNetAddConnection3() の実体は、WNetAddConnection3W() です。

ここで、関数の引数が文字列を受け取る場合は、.NET CF の場合、string オブジェクトをそのまま渡せます。前述した InternetConnectW() の引数や、WNetAddConnection3W() の引数 lpPassword と lpUserName が、その例です。しかし、構造体のメンバが文字列の場合には、マーシャリングが行われないので、特別な対応が必要です。この詳細については、次のページで説明されています。

 Advanced P/Invoke on the Microsoft .NET Compact Framework
 http://msdn.microsoft.com/en-us/library/aa446529.aspx

上のページでは、いくつかの方策が示されていますが、真ん中ほどにある “Using a Managed String Pointer” という項で説明されている方策が、お手頃ではないかと思います。この方策は、IntPtr 型のメンバを持つ CLR の struct(構造体)を定義して、マネージドコードの文字列(string)とアンマネージコードの文字列の間の変換(マーシャリング)を自前で行う、というものです。上のページの例では、文字列マーシャリング用の StringPtr という CLR の構造体と、StringPtr が使う Memory というクラスを VB .NET で定義しています。同様のことを、C# でもう少し単純に定義すると、次のようにも書けます:

using System;
using System.Runtime.InteropServices;

public struct NativeString {
    private IntPtr szString;

    // constructor
    public NativeString(string strObj) {
        int len = (strObj.Length + 1) * 2;

        szString = Marshal.AllocHGlobal(len);
        Marshal.Copy(strObj.ToCharArray(), 0, szString, strObj.Length);
        Marshal.WriteByte(szString, len - 2, 0);
        Marshal.WriteByte(szString, len - 1, 0);
    }

    // release the resource
    public void    Dispose() {
        if (IntPtr.Zero != szString)
        {
            Marshal.FreeHGlobal(szString);
            szString = IntPtr.Zero;
        }
    }
}


この NativeString を使って、NETRESOURCE を次のように定義できます。

public struct NETRESOURCE {
    public int  dwScope;
    public int  dwType;
    public int  dwDisplayType;
    public int  dwUsage;
    public NativeString lpLocalName;
    public NativeString lpRemoteName;
    public IntPtr lpComment;
    public IntPtr lpProvider;

    // constructor
    public NETRESOURCE(string localName, string remoteName) {
        dwScope       = 0;
        dwType        = 1;  // RESOURCETYPE_DISK
        dwDisplayType = 0;
        dwUsage       = 0;
        lpLocalName   = new NativeString(localName);
        lpRemoteName  = new NativeString(remoteName);
        lpComment     = IntPtr.Zero;
        lpProvider    = IntPtr.Zero;
    }

    // release the resource
    public void Cleanup() {
        lpLocalName.Dispose();
        lpRemoteName.Dispose();
    }
}

なお、NativeString は、クラスではなく、構造体(struct)でなければならないことに注意して下さい。NativeString は、Win32 API、つまりアンマネージコードと同じメモリレイアウトを持った NETRESOURCE 構造体のメンバとして使いますので、参照型であるクラスではなく、値型(value type)である構造体として定義する必要がある、というのがその理由です。

以上、.NET CF から Win32 API を P/Invoke する場合に注意すべき点を二つ紹介しました。.NET CF からのアンマネージコードの呼び出しについては、以下の Technical Articles セクションに、他にも記事がありますので、興味のある方は、ご覧になってみて下さい。

 Native Interoperability
 http://msdn.microsoft.com/en-us/library/aa145821.aspx

上の記事の中には、.NET CF 2.0 を扱ったものもあり(※現時点での .NET CF の最新版は、3.5 です)、少し古く感じられるものもありますが、今でも通用する内容がほとんどだと思います。

Add comment 2012/05/02 koga


Categories

Links

Posts by Authors

Recent Posts

Calendar

2012年5月
« 4月   6月 »
 12345
6789101112
13141516171819
20212223242526
2728293031  

Posts by Month

Posts by Category

Meta