• ベストアンサー

C言語のコールバック

Java/C#/PHPといった言語はすでに使えるのですが、現在C言語およびWin32 APIを勉強中です。 C言語の関数にはJavaなどのインスタンスメソッドのthisにあたる引数が渡されませんが、Win32 APIのウィンドウプロシージャに代表されるコールバック関数において不都合があります。Javaの場合は、 interface Callback{ void onCallback(); } といったインターフェイスが定義されているものとして、 hoge(new Callback(){ int data = 10; public void onCallback(){ System.out.println(data); } }); のようなコードで引数以外のデータも参照できますが、C言語ではこのように引数で渡せないデータを参照するにはどうしたらよいでしょうか。グローバル変数として参照するのは、Javaのstaticなフィールドと同様に拡張性を損なうので避けたいと思います。

質問者が選んだベストアンサー

  • ベストアンサー
  • MrBan
  • ベストアンサー率53% (331/615)
回答No.1

C言語のコールバックは、明示的にthis相当のデータを引数に 渡すよう定義するのが一般的かと思います。 Win32などは、ほぼ例外なく、Handleや、その他のvoid*を取れます。 # HWNDの場合などは、GetWindowLongPtr等のAPIで、 # HWMDに紐づいた情報をとることができるので引数にはありませんが。 引数にvoid*等があると、そこにthis(実態はポインタ)を 入れたり、必要な情報をまとめた構造体のポインタを入れたりが 可能です。 まぁC言語でも多くの場合はそういうものを渡して使いますし、 Javaは、そのあたりを暗黙に渡せるようになっているだけかと。 逆に、Javaを使うと不要な場合でも余分なthisの情報が 内部的に渡されるので、C言語開発当時の思想ではそういうものを サポートしていないのが割りと普通です。 # C言語の場合には、必須でないものはプログラマに選択権を # 与える(言語が勝手に新設の押し売りをしない)のが基本姿勢 # なので、勝手にそういった情報を渡してくれない。 # 必要な場合のみ、プログラマがその情報を渡す。 # (OSを書いたりするためのものですし、性能優先です) # で、時代は下りマシンパワーもあがり、アプリを書くための # 言語Javaでは、そのあたりもお手軽に使いやすいようになってる。

Sappary
質問者

補足

ご回答ありがとうございます。 教えていただいたGetWindowLongPtrやcbWndExtraのキーワードで 検索したら情報が見つかりました。ウィンドウプロシージャに void*な引数がないので困っていましたが、cbWndExtraのような 領域を確保してウィンドウごとのデータを持つわけですね。 Javaでいえばjava.awt.Frameにユーザデータを格納する java.lang.Object型のフィールドがあるようなもので、 普段Javaなどを使っている身からすればいささか気持ちが わるいですが、そういうものなのでしょうね……。 だいたい、this(に相当するもの)の型がvoid*というのも ダサいと思います。このようなケースでC言語では型安全なAPIの 設計は出来ないのでしょうか? >プログラマに選択権を与える というのもわかりますが、ライブラリの設計者が コールバック関数にthisが確実に必要ない、と言い切れる場面が 自分には思いつきません。 >Win32などは、ほぼ例外なく、Handleや、その他のvoid*を取れます。 ということは、ほぼすべてのコールバック関数でthisのようなものが 必要になる可能性があるということでしょうから、選択肢は 複数あっても実際に有効な選択肢は常にひとつに見えます。 thisが無ければ、まさかグローバル変数をそのまま使って 拡張性を犠牲にしたくはありませんし、ウィンドウをキーにした ハッシュテーブルでデータを格納してもオーバーヘッドや コーディングの手間が気になります。 所詮は設計の古い言語であるということでしょうか?

その他の回答 (3)

  • MrBan
  • ベストアンサー率53% (331/615)
回答No.4

OOP+αに対応したC++ならthisがありますが、 C言語はそもそもOOP用じゃないですから。 C++とからなら、class Objectなりを定義することもできれば、 インターフェイス的な純粋仮想クラスFooInterfaceなんてのも 作れますけど、「そういう枠組みも無い方がいい(型情報が邪魔)」 というケースも、泥臭いコードを書く際には必要になるわけで、 C言語ってのは他の方も書かれているように、そういうケースにも 対応できてそれなりに汎用的な、ある意味よくできた言語です。 Javaがオートマの軽自動車だとしたら、C言語の根底はF1カーですから、 F1カーで一般人が町乗りしたら面倒なのは当然かと。 エンジンの回転数やクラッチワーク等に常に気を使うのは 面倒だという主張は理解しますが、それが、設計思想の違いであって、 用途に合わない使い方(使わされ方)に問題はないですか? ちなみに、現代でも、例えば組込環境などでクリティカルな処理を 書くと数バイト/数ステップが気になることもあります。 理由としては、大量生産する組込機器(電気製品の中身)だと、 「メモリの値段が製造コストに結構影響するから」だったり、 「上位(例えばJavaアプリ)の動作に積もり積もって 速度差が出るから」だったり、「xxms以下で処理が完了しないと 事故が起きるから」とかだったりしますが。 こういう、メモリ制約/時間制約を受けてソフトが書ける熟練の プログラマは減ってますし、メモリとの単価差は減ってきてたり、 CPUの速度/値段が下がったりもしてますが、それでも、 「少しでも低い性能のハードの方が安い」とかあるわけで、 減ってると言ってもその環境で最適に動作するためにはやっぱり、 チューニングとしてそういうことはありますよ。 # Javaとかだと気にしない(仮にしたくてもできない)部分ですけど。

Sappary
質問者

補足

ご回答ありがとうございました。 OOPでなくともthisにあたるものは必要になると思うのです。 自分もC言語の役割は未だ失われてはいないだろうと思って 勉強を始めました。C言語の設計思想、もうすこし学んでみようかと 思います。 当初の質問からちょっと脱線させてしまいましたので、 反省しつつこのあたりで締め切りたいと思います。

  • yphkz4063
  • ベストアンサー率23% (34/144)
回答No.3

それは単にC言語というものに対する勘違いでしかありません。 他の回答者様もおっしゃっているようにC言語はアセンブリ言語です。 ベタベタなアセンブリ言語ですから、実装したいことはご自分でコードを書いてください。 それがアセンブリ言語の仕様です。

Sappary
質問者

補足

ご回答ありがとうございます。 型安全なコールバック関数というものを実装したくなりましたが、 どう自分でコードを書いてもC言語では難しそうです。 それをC言語に求めること自体が誤りだということなんですね。

  • Tacosan
  • ベストアンサー率23% (3656/15482)
回答No.2

「設計が古い」というのは, まあ確かに設計されたのは古いけど, むしろそれは「設計思想」という観点でいくべきではないかなぁ? #1 でもいわれてますが, ぶっちゃけいえば, C の設計思想は ・プログラマができることはコンパイラではしない ・プログラマは全て理解している です. もともと OS を書くための「構造化アセンブラ」として開発されてますし, 当時のシステムを想定すればしかたのないことでしょう. システム全体で 48kword しかないこともありましたから, そのうちの 1word も無駄にはできないという事情があります.

Sappary
質問者

補足

ご回答ありがとうございます。 おっしゃるとおり、確かにAPIの設計者がthisにあたる引数を ちゃんと定義すればいいわけで、Javaのように言語仕様として 勝手にthisを定義する必要性は無いかと思います。まず間違いなく thisは定義しておく必要はあるとは思うのですけれど……。 システム全体で48kword!?現在の環境しか知らない自分には 想像もつきませんが、確かに無駄は一切許されない時代も あったのでしょうね。とはいえ、そのあたりの思想に現代でも 納得のいく意味づけがないと、C言語でのコーディングは 苦痛でしかありません……。

関連するQ&A

  • C言語のvoid型ポインタ変数について。

    C言語のvoid型ポインタ変数について。 C言語のvoid型ポインタ変数について質問があります。 組み込み系の開発を行っているのですが、現在使用しているシステムで、 提供されている "API" を介してアプリケーション部のソフト作成を行っています。 この "API" ですが、引数の多くはvoid型ポインタとなっています。 ある人がこの引数がvoid型となっているのを見て、 『なんでvoid型なんや??、C言語でアセンブラと違うんやから、void型なんかにしない方が良い』 とおっしゃいました。 この意味がよくわからなかったのですが、なぜ void型はよろしくないんでしょうか? -- 僕が思うに、APIなんやから引数を void型ポインタ にすることでどんな型にも対応できる 汎用的であると感じ、逆にこの方が良いのではと感じたのですが。。 -API例---- int _exApiKannsuu( char in_data, void* out_data ) "in_data" をもとに "out_data" を取得する。 どーやらこの "out_data" が void型 であるのががよくないらしい・・

  • C#のdelegateをC++とjavaで?

    C#のデリゲードを javaとC++でできないでしょうか。 ただし、質問内容を勘違いされそうなので、求めている内容と、 求めていない内容を、詳しく 書きますと。 インタフェース委譲を使った例は、求めていません。 あらかじめ、委譲先に静的に仕組みを 作っておかなく手も、C#のデリゲードのように、シグニチァが同じなら 委譲先をはめ込めれるのが欲しいです。 単なる関数ポインタの例は、求めて いません。 C#でdelegate型を引数にとる ところに、あるインスタンスの メソッドを渡すと。 どのインスタンスのどのメソッドかまで、 認識して、コールバックできます。 関数ポインタでは、これができない。 STLの関数オブジェクトとか、古い感じの情報にヒントがあるか。調べてみましたが。よくわかりませんでした。 結局、この関数オブジェクトは、僕が求めているモノとは、違うような気がします。 よくわかりませんが。 欲しいのは、どのインスタンスのどのメソッドかまでを特定して、 記憶できる型をどうやって javaや、C++で実装できるか。 それをインタフェース委譲のように、 委譲先にあらかじめ、仕込んでおくことなしに。 ただ、メソッドのシグニチァが同じであるだけで、どのインスタンスのどのメソッドかまでを特定して記憶できる C#のデリゲードのような型を。 特殊なコンポーネントを利用せず。 標準的な言語構文のみをつかって、 どのようにしたら、 javaや、C++で、実装できるか? です。 詳しい方が、いらっしゃいましたら、 教えてください。

  • c言語

    (c++ではなくC89準拠) c言語について質問 (1) 関数名()と関数名(void)は違う意味 (2) mainの引数の型は(void)か(int argc, char *argv[]) (3) K&Rは標準c準拠でmain()という表記があります ということは、(1)の関数名とはmain以外の関数名で、 main()とmain(void)は同じなんでしょうか。

  • Java言語のインタフェースについて

    Java言語を学習している者です。 インターフェースについて理解できずに困っております。 ここでは、Runnableインターフェースを例にとります。 (1)正しいコード public class ThreadTest implements Runnable { public static void main (String args[]) { ThreadTest ins = new ThreadTest(); Thread th = new Thread(ins); th.start(); for(int i = 0; i < 10; i++) { System.out.println("main : " + i); } } public void run() { for(int i = 0; i < 10; i++) { System.out.println("run : " + i); } } } (1)のコードは正常に動きます。 (2)間違ったコード (implements Runnableを記述しなかった場合) public class ThreadTest{ public static void main (String args[]) { ThreadTest ins = new ThreadTest(); Thread th = new Thread(ins); th.start(); for(int i = 0; i < 10; i++) { System.out.println("main : " + i); } } public void run() { for(int i = 0; i < 10; i++) { System.out.println("run : " + i); } } } (2)のコードでは、 Thread th = new Thread(ins); で、 コンストラクタ―Thread(ThreadTest)は未定義です。 とエラーが出てしまいます。 確かに未定義なのですが、(1)でも定義していないように思えます。 Runnableインターフェースの働きのお陰だと言うことはわかるのですが、どういった働きによるものなのかがわかりません。 インターフェースのについて、 ・インターフェースのフィールドは必ず定数。 ・インターフェースのメソッドは必ず抽象メソッド。 ・インターフェースはインスタンスを作ることが出来ない。 ・抽象メソッドは必ず実装しなければならない。 は理解しているつもりです。 今回の質問をするに当たって、 RunnableのAPI http://java.sun.com/javase/ja/6/docs/ja/api/java/lang/Runnable.html を見たのですが、それでも理由が分からないので質問致します。 もし宜しかったら、APIの見方についてもコメントして頂けるとありがたいです。 宜しくお願いします。

    • ベストアンサー
    • Java
  • objective-cにはCのような関数はない?

    c言語とobjective-cを並行して勉強しています。 c言語では機能をまとめるために関数を定義できますよね。 ----- void display(int a){ printf("%d" , a); } int main(void){ display(10); } --- これでdisplayという関数に10という引数を渡して表示させることができますよね? objective-cの場合は例えば以下のように書くことがわかり、コンパイルも通りました。 - (void)display{ NSlog(@" TEST "); } しかし・・・・呼び出すことができませんね。。。 objective-cの場合は[ インスタンス メソッド名 ]; のような呼び方なのですよね。 メソッドだけを呼び出すことはできない、ってことなのでしょうか・・・? 色々検索してみたのですが、 @implement~@endで囲んでクラスにして インスタンスを作って、メソッドを呼ぶ方法ばかりで、 上記のC言語の例のような、単純な関数定義→呼び出しの方法を見つけることができませんでした。 上記のようなC言語の例はよくあると思うのですが、 これと同じことがobjective-cで、できるともできないとも、書いてないように思います。 例えば上のc言語と同じ動作をobjective-c に移植するとどうなるのでしょうか・・・?というか、不可能、ということでしょうか・・・?

  • C言語のポインタによる関数の引数の書き方について教えてください。

    C言語を今勉強中の大学生です。 一気に複数の値をmainに返せる、参照による呼び出しによる関数の引数の書き方で困っています。配列を自作関数に引き渡したいのですが、どう書けばいいのでしょうか。 下の3つは、それぞれ(1)mainからの呼び出し、(2)自作関数での引数の引き受け、です。どこをどう変えたらエラーが出なくなるのか、分かる方、お願いいたします! (1)school(&m,h); (2)void school(int *m, float *h){ ※ちなみに変数は int m,float h[10][10]

  • C言語の高階関数についてです。

    C言語の高階関数についてです。 double f ( (*g)(double) ){...} とすると「関数を引数にする関数」が作れますが、 「関数を引数にする関数」を引数にする関数って作れますか??? その場合は、引数のところどうやって書けばいいでしょうか??

  • C言語のvoid型ポインタを使いたいのですが…

    C言語のvoid型ポインタを使いたいのですが… 関数の引数として、void型ポインタを使おうと思ったのですが、内部でどのように処理すればいいのかわかりません キャストすれば問題なく使えるとのことですが、どの型でキャストするのかをどのように判断するのかがわかりません 具体的には、画像処理で画像の構造体をいくつか作ったのですが、それぞれの構造体ごとに関数を書くと関数が多くなるので、void型ポインタでまとめてつくろうとしています どのように型の判断を行えばいいのかを教えてください

  • C言語での関数の引数の受け渡しについて

    C言語での関数の引数の受け渡しについて教えてもらいたいのです。 char *p=Goo;  というポインタpがmain関数で定義され、このポインタpをある関数 void func(・・・) に渡すことは出来ますか? つまりポインタを実引数として扱うことはできるのかという事ですが・・・ int p=10; とかだったら、 void func(int test) の関数には、main関数で func(p) で仮引数testにわたせると思うんですが・・・ もし出来るようでしたら、関数の渡し方と定義の記述を教えてください。 どうか宜しくお願いします。

  • クラス内にWin32APIのコールバック関数がある場合

     Win32APIをC++で作成しようとしているのですが、途中どのようにしたらいいのか分からないのでここで質問させていただきました。  Win32をC++で作成するためクラス内にウィンドウの登録、設定の関数を用意したのはいいのですが、CALLBACK関数を含めて実行しようとするとウィンドウの登録の際に、エラーが起きるようです。 /*////////////////////////////////////////// ウィンドウ・クラスの登録 //////////////////////////////////////////*/ ATOM windCreate::InitApp(void) {         (省略)      wc.lpfnWndProc = WndProc; //ここでエラー         (省略)      return RegisterClassEx(&wc); } エラー内容は下記の通りです。 error C2440: '=' : 'LRESULT (__stdcall windCreate::* )(HWND,UINT,WPARAM,LPARAM)' から 'WNDPROC' に変換できません。  クラスの宣言は以下の通りです。 class windCreate{     // WinMain インスタンスハンドルへのアクセス      public:         HINSTANCE hInst;      private:         WNDCLASSEX wc;         LPSTR szClassName;         HWND hWnd;         RECT w_rect;         SIZE window_size;      public:         ATOM InitApp(void);         BOOL InitInstance(int nCmdLine);       //ここがシステムから呼び出されるコールバック関数                              LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);          ---------(以下省略)--------- };  ウィンドウからのメッセージ受取を行う関数なんですが、どうしたら エラーなく実行できるのでしょうか?

専門家に質問してみよう