Pythonは開発効率は高いものの、スクリプト言語としての性能は高くないため、開発効率やパフォーマンスを考慮するため、性能要件の高いモジュールはCまたはC++で実装するか、CでPythonスクリプトを実行することが一般的です。処理にはロジックまたは C++ が使用されます。前者は通常、Python でのいくつかのモジュールの実装であり、後者はサーバーサイド プログラム (ビジネス拡張またはプラグイン機能の実装) やゲーム開発 (スクリプトはロジックのみを処理します) でより一般的です。この記事では、cでpythonスクリプトを実行することでpythonとcの相互呼び出しを実現する方法を主に、cとpythonスクリプトで同じメモリ領域を設定する例を用いて紹介します。
はじめに
最近、仕事の都合でオンラインゲーム対戦用のudpベースのデータ同期プロトコルを作成しようと考えており、初期段階のデータをテストするために外部プロキシを作成することにしました。原則はサーバー側で、クライアントとは別にネットワーク転送プロキシを確立します。つまり、元の C/S 接続が 2 つのプロキシ間の高速データ送信に変更されます。 udp ライブラリは C++ で書かれたコードであるため、データをテストするときにパラメータを変更したり、再コンパイルしたり、出力統計データを変更したり、表を作成したりするのは非常に面倒です。最終的に、エクスポート インターフェイスへの論理呼び出しを行うために Python スクリプトを使用することにしました。以下で言うことはあまりありませんが、詳細な紹介を見てみましょう:
c で Python スクリプトを実行するには、プログラムがリンクされるときに Python 仮想マシン ライブラリをそれにリンクする必要があります。 Python 仮想マシンのライブラリは、インストール ディレクトリの libs にある python27.lib ファイルです。ライブラリをプログラムにリンクする方法については、自分で検索できます。 Python の一部のメソッドとデータ構造は C で使用されているため、Python インストール ディレクトリ下の include ディレクトリをプロジェクトのインクルード ディレクトリに追加する必要があります。準備はこれだけです。これで、メモリ領域の設定例の実装を開始できます。
C/C++ を Python にエクスポートするには、さまざまなニーズに応じて、次のさまざまな方法を使用できます。
1. ctypes は、ユニバーサル Python 標準ライブラリ モジュールに含まれており、実行時にダイナミック リンク ライブラリ (dll など) をロードでき、CPython 2.x/3.x および PyPy でサポートされています。この方法の利点は、Python API を使用してエクスポート 関数 を記述する必要がなく、ダイナミック リンク ライブラリのシンボル テーブルを直接ロードして、Python で直接呼び出すことができることです。
2. サードパーティの Python バインディング。例としては、boost-python が挙げられます。これは、Python/C API を使用してツール自動化によって実装され、一連の C++ ラッパー関数を生成します。大規模なライブラリやエンジンを Python にエクスポートする場合に特に適しています。
3. Python バインディング関数を手動で作成します。 Python C API に精通している場合は、この方法が最も柔軟であり、API ドキュメントを読んだ後に使用できます。理論上は効率が最高になるはずですが、私のようなPython初心者にとってはかなりの時間がかかるかもしれません。
C 関数を Lua スクリプトにエクスポートした以前の経験に基づいて、最初に Python C API を勉強し、それを完了するまでに半日取り組む必要があると思いました。後で、Python 標準ライブラリ モジュールの ctypes がすでに非常に強力であることがわかりました。パフォーマンスは 3 つの方法の中で最も悪いはずですが、最大 60fps のこのトンネルでは、C/Python インターフェイス境界呼び出しの損失は無視されます。初めて。他の 2 つの設計方法とは異なり、ctypes はインターフェイスを呼び出す非侵襲的な方法を使用します。元の C インターフェイスを変更したり、バインディング コードを作成したりする必要はなく、コンパイルされた動的ライブラリを直接呼び出します。 ctypes を使用するプロセスも非常に快適です。
以下では ctypes の使用方法を紹介します:
1. DLL ダイナミック リンク ライブラリをロードします
ここでは、ダイナミック リンク ライブラリ関数が cdecl 呼び出しを使用するか stdcall 呼び出しを使用するかを区別する必要があります。規則に従って、それぞれ cdll または Windll を使用して動的ライブラリをロードします。
例:
# 加载udp库函数 udp_server = cdll.LoadLibrary("./udp_server.so") init_udp_server = udp_server.init_udp_server destroy_udp_server = udp_server.destroy_udp_server update_udp_server = udp_server.update_udp_server SendMsg = udp_server.SendMsg SetConnectCallback = udp_server.SetConnectCallback SetDisconnectCallback = udp_server.SetDisconnectCallback SetTimeoutCallback = udp_server.SetTimeoutCallback SetRecvCallback = udp_server.SetRecvCallback
2、データ型マッピング
ctypes で定義される基本的なデータ型 (c_char、c_int、c_double など) に加えて、ポインター関数を使用して次のこともできます。ポインタ型に変換します。ネットワーク ライブラリをエクスポートするには、コールバック関数を設定することが必須です。C++ ライブラリでは、コールバック関数は、関数ポインター の宣言もサポートしています。例: recv_cb = CFUNCTYPE( None, c_char_p, c_int )
は、戻り値が void で、パラメータが char* および int 型であるコールバック関数を示します。
def init(self, port, ip="127.0.0.1"): self._port = port self._ip = ip self._clients = {} self.c_connect_cb = connect_cb(self.server_connect) self.c_disconnect_cb = disconnect_cb(self.server_disconnect) self.c_timeout_cb = timeout_cb(self.server_timeout) self.c_recv_cb = recv_cb(self.server_recv) def create(self): if self._port: if init_udp_server(self._ip, self._port) == 0: print "server listen %s:%d" % (self._ip, self._port) SetConnectCallback( self.c_connect_cb ) SetDisconnectCallback( self.c_disconnect_cb ) SetTimeoutCallback( self.c_timeout_cb ) SetRecvCallback( self.c_recv_cb ) return True print "[error] init_udp_server error", self._ip, self._port return False
コールバックパラメータをバインドするときは、Pythonのガベージコレクションによってコールバック関数がワイルドポインタになるのを避けるために、バインドされたコールバック関数をメンバー変数として保存する必要があることに注意する必要があります(上記の記述方法)。 。これは小さな穴であると考えられます。基本的に、小規模なライブラリではこれらの関数のみが使用されます。
以上がPython と C 間での相互呼び出しの詳細な紹介の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。