จาก Blog ตอนที่แล้วที่ผมได้อธิบาย Named Pipe ไปว่า ได้ลองนำเทคนิคนี้มาใช้เพื่อแก้ปัญหาของ Legacy System ใน Blog นี้เป็นการขยายความเพิ่มเติม
ปล สำหรับการเชื่อม VB6 กับ .NET มีอีกท่าทำเป็น dotnet dll build เป็น RegisterForComInterop + ComVisible DLL ให้ VB6 เห็นครับ
Table of Contents
ทำไมใช้ 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.



