[C#] Named Pipe เชื่อม VB6 กับ .NET

จาก 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.