จาก Blog ตอนที่แล้วที่ผมได้อธิบาย Named Pipe ไปว่า ได้ลองนำเทคนิคนี้มาใช้เพื่อแก้ปัญหาของ Legacy System ใน Blog นี้เป็นการขยายความเพิ่มเติม
ทำไมใช้ Named Pipe
- เป็นเทคโนโลยีที่รองรับทั้ง VB6 และ .NET
- ไม่อยากไปพัฒนาโมดูลใหม่ๆบน VB6 แล้ว เพราะติดปัญหา
- Maintain ยาก ทำ Unit Test ไม่ได้ด้วย
- ไปกับเทคโนโลยีใหม่ๆยาก
- ไม่รองรับมาตรฐานความปลอดภัยใหม่ๆ
VB6 ส่งไป .NET ติดปัญหาอะไรไหม ?
- ปัญหาภาษาไทย ตัว VB6 มันไม่รู้จัก UTF-8 แบบสมบูรณ์ ต้องส่งแบบ ASCII
- VB6 Request ส่งแบบ ASCII ไม่งั้นจะติดปัญหาภาษาไทย
- .NET Receive ส่วนฝั่ง .NET ต้นทางส่งเป็น ASCII ปลายทางต้องอ่านแบบเดียวกันครับ
- .NET Request กับไปหา VB6 แปลงเป็น ASCII
- VB6 Receive อ่านในรูปแบบ ASCII
VB6 Client
- สำหรับตัว VB6 ใข้ WIN32_API : CallNamedPipeA function (winbase.h) - Win32 apps
- โดยการใช้งานต้องมีการประกาศใช้ CallNamedPipeA โดย API นี้ Return ค่าออการ ถ้าเท่ากับ 0 แสดงว่า Request มีปัญหาครับ นอกจากนั้นส่งได้สำเร็จครับ
Private Declare Function CallNamedPipe Lib "kernel32" Alias _ "CallNamedPipeA" ( _ ByVal lpNamedPipeName As String, _ lpInBuffer As Any, _ ByVal nInBufferSize As Long, _ lpOutBuffer As Any, _ ByVal nOutBufferSize As Long, _ lpBytesRead As Long, _ ByVal nTimeOut As Long) As Long
- ประกาศชื่อ Pipe ชื่อนี้ต้องตรงทั้ง Client และ Server ครับ โดยกำหนดเป็นชื่อเป็น invest-pipe และส่งใน Local (
\\.\\pipe\\<PIPE-NAME>
)
Private Const szPipeName = "\\.\\pipe\\invest-pipe"
- จากปัญหาภาษาไทย Input String ต้องมาแปลงเป็น ASCII Byte โดยใช้ Function ASC
'BEGIN: THIS CODE CONVERT EACH CHARACTOR TO ASCII FROM SUPPORT ENGLISH + THAI ReDim messageInByte(Len(message)) lmessageByteCount = 1 For lmessageByteCount = 1 To Len(message) messageInByte(lmessageByteCount) = Asc(Mid$(message, lmessageByteCount, 1)) Next 'END: THIS CODE CONVERT EACH CHARACTOR TO ASCII FROM SUPPORT ENGLISH + THAI
- เรียกใช้ WIN32_API : CallNamedPipeA
Dim res As Long res = CallNamedPipe(szPipeName _ , messageInByte(0) _ , UBound(messageInByte) + 1 _ , messageOutByte(0) _ , UBound(messageOutByte) + 1 _ , cbRead _ , 30000) 'Wait up to 30 seconds for a response
- โดยที่
- szPipeName : ที่อยู่ของ Pipe
- messageInByte(0) / UBound(messageInByte) + 1 : ข้อมูลที่ส่งไป Pipe Server ในรูปแบบ Array of Byte
- messageOutByte(0) / UBound(messageOutByte) + 1 : Response ที่ตอบกลับจาก Pipe Server ในรูปแบบ Array of Byte
- cbRead : ขนาด byte ที่อ่านได้จาก Array messageOutByte
- 30000 : timeout 30 วินาที
- ตอนรับผลลัพธ์อ่านที่ละ cbRead มาแปลง ASCII กลับเป็น String คร้บ
If res > 0 Then Dim c As Variant For Each c In messageOutByte temp = temp & Chr(c) Next c temp = Left$(temp, cbRead) txtResult.Text = temp Else MsgBox "Error number " & Err.LastDllError & " attempting to call CallNamedPipe.", vbOKOnly End If
.NET Server
- Pipe Server ผมได้ Implement โดยใช้ Named Pipe แบบ Multiple Pipe Instance
Class NamedPipeServer
- กำหนดให้ตัว Pipe ทำงานได้มากที่สุด 5 Instance (รับ 5 Request ได้พร้อมๆกัน)
public NamedPipeServer(string pipeName, int maxNumberOfServerInstances, int initialNumberOfServerInstances) { this.pipeName = pipeName; this.maxNumberOfServerInstances = maxNumberOfServerInstances; this.initialNumberOfServerInstances = initialNumberOfServerInstances; }
- เมื่อมี Request เข้ามา กรณีที่ไม่เกิน สร้าง NamedPipeServerInstance และการกำหนด Event PipeMsgEventArgs เมื่อทำงานเสร็จ Clean Resource
private void NewServerInstance() { // Start a new server instance only when the number of server instances // is smaller than maxNumberOfServerInstances if (servers.Count < maxNumberOfServerInstances) { NamedPipeServerInstance server = new NamedPipeServerInstance(pipeName, maxNumberOfServerInstances); server.newServerInstanceEvent += (s, e) => { NewServerInstance(); }; server.newRequestEvent += (s, e) => { newRequestEvent.Invoke(s, e); }; newInstanctEvent.Invoke(this, "New Instance"); servers.Add(server); } // Run clean servers anyway CleanServers(false); }
Class PipeMsgEventArgs
- Event ที่เก็บข้อมูล Request / Response
public class PipeMsgEventArgs : EventArgs { public string Request { get; set; } public string Response { get; set; } public PipeMsgEventArgs() { } public PipeMsgEventArgs(string request) { this.Request = request; } }
Class NamedPipeServerInstance
- สร้าง NamedPipeServerStream และกำหนดการทำงานแบบ Asynchronous
- Method Communication ทำหน้าที่ Read Message จาก VB6 และส่งผลลัพธ์กลับออกไป
private void Communication() { StringBuilder messageBuilder = new StringBuilder(); string messageChunk = string.Empty; byte[] messageBuffer = new byte[1024]; do { _instanceServer.Read(messageBuffer, 0, messageBuffer.Length); messageChunk = CreateRequestString(messageBuffer, IsUTF8); messageBuilder.Append(messageChunk); messageBuffer = new byte[messageBuffer.Length]; } while (!_instanceServer.IsMessageComplete); PipeMsgEventArgs msgEventArgs = new PipeMsgEventArgs(messageBuilder.ToString()); newRequestEvent.Invoke(this, msgEventArgs); string response = msgEventArgs.Response + Environment.NewLine; byte[] byteSend = CreateResponseString(response, IsUTF8); _instanceServer.Write(byteSend, 0, byteSend.Count()); }
- อย่าลืมติดต่อกับ VB6 แปลงเป็น ASCII ครับ โดยมี Method แปลง ดังนี้
private String CreateRequestString(byte[] messageBuffer, bool pIsUTF8) { if (pIsUTF8) { //NOTE ถ้าคุยกับ Application ที่ใช่ UTF8 ได้ ควรใข้อันนี้ return Encoding.UTF8.GetString(messageBuffer); } else { //NOTE : VB6 Send ASCII Byte //Encoding.Default < System.Globalization.CultureInfo.CurrentCulture.TextInfo.ANSICodePage; String messageChunk = Encoding.Default.GetString(messageBuffer); //Clear \0 Non-String such as \0ทดสอบ\0\0\0\0\0 >> ทดสอบ messageChunk = messageChunk.Replace("\0", string.Empty); return messageChunk; } }
Test
- ตอนนี้มี App ทั้ง VB6 และ C# แล้วครับ
- ทดสอบส่งจาก VB6 Client > .NET Server
Source Code
Reference
Discover more from naiwaen@DebuggingSoft
Subscribe to get the latest posts sent to your email.