2012-01-12 33 views
2

以下のコードは、スレッド内のソケット経由でデータを読み取るためのコードです。Delphi:メインスレッドの外側にソケットを作成して使用する

//main method from dll 
function GetSocketData(const IP, Port: PChar): PChar; export; cdecl; 
var 
    Thread: TMyThread; 
    DataIsRead: TEvent; 
begin 
    DataIsRead := TEvent.Create(nil, True, False, ''); 
    Thread:= TMyThread.Create(DataIsRead, IP, Port); 
    DataIsRead.WaitFor(INFINITE); 
    Result := BlockAlloc(Thread.ResultData); 
    DataIsRead.Free; 
    Thread.Free; 
end; 

TMyThreadBase = class(TThread) 
protected 
    FResultData: string; 
public 
    constructor Create; 

    property ResultData: string read FResultData; 
end; 

constructor TMyThreadBase.Create; 
begin 
    inherited Create(False); // Suspended 
    FResultData := ''; 
    FreeOnTerminate := False; 
end; 

TMyThread = class(TMyThreadBase) 
private 
    FMyData: TMyData; 
    FSocketCom: TSocketCom; 
    //other params 
protected 
    procedure Connect(Sender: TObject); 
    procedure Execute; override; 
public 
    constructor Create(DataIsRead: TEvent; const IP, Port: PChar); 
    destructor Destroy; override; 
end; 

constructor TMyThread.Create(const IP, Port: PChar); 
begin 
    inherited Create; 
    /init params/ 

    CoInitialize(nil); 
    FSocketCom := ComCreate(FPort, FIP); 
    FSocketCom.OnConnect := Connect;//Connect method sends the special command to the port {`ClientSckt.Socket.SendBuf(B[0], Count)`} 
    FSocketCom.Reopen; 

    FMyData := TMyData.Create(DataIsRead, FSocketCom);//class used for received data interpretation 
    //DataIsRead event is being set when all data is interpreted 
    FSocketCom.SetRxFunc(FMyData.NCData);//set method for data interpretation 
    FMyData.InitData(...);//init values needed while data is being interpreted 
end; 

destructor TMyThread.Destroy; 
begin 
    CoUninitialize; 
    inherited; 
end; 

procedure TMyThread.Execute; 
begin 
    inherited; 
    while not Terminated do 
    Sleep(100); 
    //that is the place where I do not know what to do to wait while OnRead event is fired. 
end; 

TSocketCom = class(TCustomCom) 
private 
    ClientSckt: TClientSocket; 

    procedure SocketConnect(Sender: TObject; Socket: TCustomWinSocket); 
    procedure SocketRead(Sender: TObject; Socket: TCustomWinSocket); 
protected 
    procedure SetThread; override; 
public 
    constructor Create; 
    destructor Destroy; override; 
    function Open:Boolean; override; 
    function Read(Buf:PAnsiChar; Size:Integer; Wait:Integer = 0):Integer; override; 
end; 

procedure TCustomCom.SetRxFunc(OnRxData: TRxDataEvent); 
begin 
    ... 
    SetThread; 
    ... 
end; 

function TSocketCom.Open:boolean; 
var 
    i,j:integer; 
begin 
    ... 
    ClientSckt:=TClientSocket.Create(nil); 
    ClientSckt.ClientType:=ctBlocking; 
    ClientSckt.HostAndAddress:='127.0.0.0'; 
    ClientSckt.Port:=1234; 
    ClientSckt.OnConnect:=SocketConnect; 
    ClientSckt.Open; 
    ... 
end; 

function TSocketCom.Read(Buf:PAnsiChar;Size:Integer; Wait:Integer):Integer; 
begin 
    if Opened then 
    Result:=ClientSckt.Socket.ReceiveBuf(Buf^,Size); 
    if Result<0 then Result:=0; 
end; 

procedure TSocketCom.SetThread; 
begin 
    inherited; 
    ClientSckt.OnRead:=SocketRead; 
end; 

問題:OnReadイベントは発生しませんが、必要なすべてのインスタンスがスレッド内に作成されます。接続が確立され、コマンドが送信されます。

+1

:より多くの代わりにこのような何かを使用してください。 –

+0

OK。それはメインスレッドを保持する問題を修正します。しかし、このスレッド内でソケットを強制的に読み込むことはできますか?私が見たように、executeからのメソッド呼び出しだけがスレッドと共に実行されます。しかし、Readメソッドが呼び出されると、ソケットの読み取りが行われます。私はExecuteでそれを呼び出すことはできません。 – Yuriy

+0

なぜExecuteで呼び出すことができないのですか? –

答えて

0

この関数とスレッドMyThreadForReadingメソッド内ですべてのクライアント<>サーバー通信を行うには、最も簡単な方法はスレッドコンストラクター(またはExecuteメソッドの最上部)にTClientSocketのインスタンスを作成し、 。 PChar、hostAddr、portなどをコンストラクタパラメータとして渡します。コンストラクタ/ executeで、パラメータ、onReadイベントを使用してTClientSocketをロードし、clientTypeを 'ctBlocking'に設定します。実行時には、接続してPCharにデータをループして読み込みます。入ったら、メインスレッドにPCharバッファが「フル」であることを通知するいくつかの選択肢があります。 PostMessage()はメインスレッドのメッセージハンドラを起動するものですが、メインスレッドが待機する必要がある場合は、Davidが提案したようにWaitFor()を使用してください。

+0

PS - スレッドを終了する前にTClientSocketを閉じて解放することを忘れないでください。 –

3

この種のコードは、呼び出しコードがスレッドの終了を待ってブロックされているため、最初にスレッドを持つ理由がないため、スレッドの悪用(およびFreeOnTerminateプロパティの悪用)です。

これは、デフォルトではTClientSocketが非ブロックモードで動作し、ウィンドウメッセージを内部的に使用してソケットイベントをトリガーします。ソケットをアクティブにするスレッドはメッセージループを持つ必要があり、ソケット通知を正しく受信してディスパッチすることができます。それ以外の場合は、代わりにブロックモードでソケットを使用する必要があります。

更新:

あなたが示されている更新されたコードは、複数のレベルで単純に間違っています。スレッディングはすべて間違っています。 TClientSocketの使用はすべて間違っています。 GetSocketData()の機能をブロックすると、内部でスレッドを使用する必要はありません(特に、GetSocketData()自体がスレッドで呼び出されているため、余分なスレッドは過剰です)、気にする必要はありませんTClientSocketイベント、特にOnReadイベントは、ブロッキングモードではまったく呼び出されません(問題の根源です)。

コードをもっと複雑にする必要があります。あなたが行ったように、それをマークするために、 `Terminate`を使用して、単に` WaitFor`ではなく、その `while`ループ呼び出す必要がありますスレッドを待つしたい場合は

function GetSocketData(const IP, Port: PChar): PChar; export; cdecl; 
var 
    ClientSckt: TClientSocket; 
    //other params 
begin 
    Result := nil; 
    try 
    /init params/ 

    CoInitialize(nil); 
    try 
     ClientSckt := TClientSocket.Create(nil); 
     try 
     ClientSckt.ClientType := ctBlocking; 
     ClientSckt.HostAndAddress := IP; 
     ClientSckt.Port := Port; 
     ClientSckt.Open; 
     try 
      // send the special command to the port from here 
      // read all data and interpret from here 
      Result := BlockAlloc(...); 
     finally 
      ClientSckt.Close; 
     end; 
     finally 
     ClientSckt.Free; 
     end; 
    finally 
     CoUninitialize; 
    end; 
    except 
    end; 
end; 
+0

私は99%に同意します - この 'データ受信の完全なカプセル化'の必要性、おそらく最小限の 'サポート'を持つサーバーからのデータを返すDLL呼び出しと何かがあるかもしれません。メッセージループのない他のスレッドからのものです(ノンブロッキングモードでは動作しません)。 –

+0

Hmm。呼び出しコードはブロックされていますが、このアプリケーションがUIを持っていると仮定すると、このように使用しないと、リクエストが進行中にUIを更新できますが、スレッドは発生しません。私はアプリケーションがWebルックアップの期間中フリーズしたときにそれが嫌いです。 – eis

+0

@eis - OPはメインスレッドから呼び出していることを示唆しており、スレッドがデータを取得するまで待つことを望みます。これはメインスレッドをブロックし、スレッドがその完了を通知するまでメッセージ処理を阻止します。レミーは、OPが求めていることが彼/彼女が望むものではないという点でおそらく正しいでしょう。確かに、別のスレッドの結果をUIスレッドのイベントハンドラで待つことは本当に悪い考えですが、多くの開発者は依然として「スレッドを呼び出して戻りを待つ」と主張しています。 –

関連する問題