c++程序与c#程序通过命名管道通信

16 篇文章 3 订阅
9 篇文章 0 订阅

最近做的项目需要在两个程序间进行通信,server端是c++(qt)写的程序,client是c#写的程序。

之前使用QProcess通信已经测试通过了,不过是server和client都是用qt做的模拟,实际在c#中没办法接收到消息,现在重新用管道通信来实现。

server:

#include "mainwindow.h"
#include "ui_mainwindow.h"

HANDLE MainWindow::hPipe = NULL;


MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    InputThread * inputThread = new InputThread;

    connect(inputThread,SIGNAL(inputedSignal(QString)),this,SLOT(onReadOutput(QString)));

    inputThread->start();

    OnPipeCreate();
}
void MainWindow::OnPipeCreate()
{
    MainWindow::hPipe = NULL;
    // 注意管道创建的参数
    // 第一个参数是管道名称,本机的话前面固定(\\\\.\\Pipe\\)必不可少
    // PIPE_ACCESS_DUPLEX|FILE_FLAG_WRITE_THROUGH|FILE_FLAG_OVERLAPPED双通道,缓存,
    MainWindow::hPipe = CreateNamedPipe(TEXT("\\\\.\\Pipe\\TestPipe"),PIPE_ACCESS_DUPLEX|FILE_FLAG_WRITE_THROUGH|FILE_FLAG_OVERLAPPED,PIPE_TYPE_MESSAGE|PIPE_READMODE_MESSAGE|PIPE_WAIT,PIPE_UNLIMITED_INSTANCES,0,0,NMPWAIT_WAIT_FOREVER,NULL);
    if(INVALID_HANDLE_VALUE == MainWindow::hPipe)
    {
        QMessageBox::about(this,"提醒","创建管道失败");
        CloseHandle(MainWindow::hPipe);
        MainWindow::hPipe = NULL;
        return;
    }

    HANDLE hEvent;
    hEvent = CreateEvent(NULL,true,false,NULL);
    if(!hEvent)
    {
        QMessageBox::about(this,"提醒","事件创建失败");
        CloseHandle(MainWindow::hPipe);
        MainWindow::hPipe = NULL;
        return;
    }
    OVERLAPPED ovlap;
    ovlap.hEvent = hEvent;
    if(!ConnectNamedPipe(hPipe,&ovlap))
    {
        if(ERROR_IO_PENDING!=GetLastError())
        {
            QMessageBox::about(this,"提醒","等待客户端连接失败");
            CloseHandle(hPipe);
            CloseHandle(hEvent);
            hPipe = NULL;
            return;
        }
    }
    if(WAIT_FAILED == WaitForSingleObject(hEvent,INFINITE))
    {
        QMessageBox::about(this,"提醒","等待对象失败");
        CloseHandle(hPipe);
        CloseHandle(hEvent);
        hPipe = NULL;
        return;
    }
    CloseHandle(hEvent);
}
void MainWindow::onReadOutput(QString str)
{
    this->ui->receiveText->append(str.trimmed());
}
void MainWindow::on_sendBtn_clicked()
{
    // server发送给client,\n必不可少
    QString str = this->ui->sendText->text()+"\n";
    char * buf;
    QByteArray ba = str.toUtf8();
    buf = ba.data();

    DWORD dwWrite;
    if(!WriteFile(MainWindow::hPipe,buf,strlen(buf),&dwWrite,NULL))
    {
        QMessageBox::about(this,"提醒","数据写入失败");
        return;
    }
}
MainWindow::~MainWindow()
{
    delete ui;
}



client:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.IO.Pipes;
using System.Linq;
using System.Security.Principal;
using System.Text;
using System.Threading;
using System.Windows.Forms;

namespace TestCallTo
{
    public partial class FormClient : Form
    {
        SynchronizationContext syncContext = null;

        NamedPipeClientStream pipeClient = new NamedPipeClientStream(
            ".", 
            "TestPipe", 
            PipeDirection.InOut, 
             PipeOptions.Asynchronous|PipeOptions.WriteThrough,  
            TokenImpersonationLevel.None
            );  
        // 增加一个StreamWriter的定义
        StreamWriter sw = null;
        // 增加一个StreamReader的定义
        StreamReader sr = null;

        BackgroundWorker worker = new BackgroundWorker();

        public FormClient()
        {
            InitializeComponent();
            this.FormClosed += FormClient_FormClosed;
            
            worker.WorkerReportsProgress = true;
            worker.ProgressChanged += worker_ProgressChanged;
            worker.DoWork += worker_DoWork;
            worker.RunWorkerAsync();

            syncContext = SynchronizationContext.Current;
        }

        void FormClient_FormClosed(object sender, FormClosedEventArgs e)
        {
            if (pipeClient.IsConnected)
            {
                pipeClient.Close();
            }
        }

        private void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            if (e.ProgressPercentage == 100)
            {
                rtbRevice.Text += e.UserState as string + "\r\n";
            }
            else if (e.ProgressPercentage == 10)
            {
                lbStatus.Text = (bool)e.UserState ? "已连接" : "未连接";
            }
        }

        void worker_DoWork(object sender, DoWorkEventArgs e)
        {
            if (pipeClient.IsConnected == false)
            {
                worker.ReportProgress(10, false);
                pipeClient.Connect();
                // 增加StreamWriter的初始化
                sw = new StreamWriter(pipeClient);
                sw.AutoFlush = true;
                // 增加StreamReader的初始化
                sr = new StreamReader(pipeClient);

                worker.ReportProgress(10, pipeClient.IsConnected);
            }
            while (true) 
            {
                if (sr!=null) 
                {
                    var receiveStr = sr.ReadLine();
                    // 添加到文本框
                    syncContext.Post(SetTextSafePost, receiveStr);
                }
                
            }
        }

        private void btnSend_Click(object sender, EventArgs e)
        {
            if (pipeClient.IsConnected == false)
            {
                MessageBox.Show("没有连接服务端");
                return;
            }
            if (tbSend.Text.Length == 0)
            {
                return;
            }
            // 修改发送方式
            if(sw!=null)
                sw.WriteLine(tbSend.Text);

            //byte[] buffer = Encoding.Unicode.GetBytes(tbSend.Text);
            //pipeClient.Write(buffer, 0, buffer.Length);
            //pipeClient.Flush();
            //pipeClient.WaitForPipeDrain();
        }
        private void SetTextSafePost(object text) 
        {
            rtbRevice.AppendText(text.ToString());
        }
    }
}

重点说说server:

1、初始化:
MainWindow::hPipe = CreateNamedPipe(TEXT("\\\\.\\Pipe\\TestPipe"),PIPE_ACCESS_DUPLEX|FILE_FLAG_WRITE_THROUGH|FILE_FLAG_OVERLAPPED,PIPE_TYPE_MESSAGE|PIPE_READMODE_MESSAGE|PIPE_WAIT,PIPE_UNLIMITED_INSTANCES,0,0,NMPWAIT_WAIT_FOREVER,NULL);

第一个参数

        管道名称,如果是本机的话,前缀"\\\\.\\Pipe\\"都是需要的,固定这样写就可以了,后面再加上管道的名称;

第二个参数

        PIPE_ACCESS_DUPLEX表示此管道是双通道的;

        FILE_FLAG_WRITE_THROUGH表示通过快速缓存写入磁盘;

        FILE_FLAG_OVERLAPPEDB表示对文件进行重叠操作,即不要使用默认IO,而是IO在后台操作,你程序可以继续其他操作;

第三个参数

        PIPE_TYPE_MESSAGE表示管道使用消息模式,区别于字节模式;

        PIPE_READMODE_MESSAGE表示管道读取模式采用消息模式,区别于字节模式;

        PIPE_WAIT表示管道会处于等待状态,等待客户端来连接,区别于不等待,不等待的话没有连接到客户端马上就关闭了;

第三个参数

        PIPE_UNLIMITED_INSTANCES不限制实例,指的是不限制客户端的实例数量;

第四个参数

        输出缓存(size),0表示默认;

第五个参数

        输入缓存(size),0表示默认;

第六个参数

        NMPWAIT_WAIT_FOREVER表示永远等待下去,不设超时;

第七个参数

        安全校验模式,这里传NULL即不进行安全校验;

2、等待连接

创建管道时执行CreateNamedPipe就会进入等待连接状态,如果有客户端开启则会进行连接,也就是说server端没有必要再执行一个connect操作。理论上来说,server端只要执行一个CreateNamedPipe就好了。

但是我们要监控是否有客户端连接,就需要再做一些额外工作。

如果是C#就很好办,他这个管道是经过封装的,他有一个属性叫做IsConnected来判断是否连接。在c++的代码中就要费一些周折。

如文章开头所贴的大段代码,步骤简单叙述如下:

        1、初始化即CreateNamedPipe;

        2、执行一个CreateEvent,这个CreateEvent是为了后面的步骤做准备;

        3、执行ConnectNamedPipe,此函数会马上返回是否有客户端已连接;

        4、执行WaitForSingleObject,此函数会阻塞,直到有客户端来连接,否则会阻塞于等待连接的状态;

这里面第3步其实有2种用法,这里说明一下:

1、ConnectNamedPipe第一种用法,非阻塞:
    hEvent = CreateEvent(NULL,true,false,NULL);
    if(!hEvent)
    {
        QMessageBox::about(this,"提醒","事件创建失败");
        CloseHandle(MainWindow::hPipe);
        MainWindow::hPipe = NULL;
        return;
    }
    OVERLAPPED ovlap;
    ovlap.hEvent = hEvent;
    if(!ConnectNamedPipe(hPipe,&ovlap))
    {
        if(ERROR_IO_PENDING!=GetLastError())
        {
            QMessageBox::about(this,"提醒","等待客户端连接失败");
            CloseHandle(hPipe);
            CloseHandle(hEvent);
            hPipe = NULL;
            return;
        }
    }

会传入HANDLE和一个OVERLAPPED,马上返回当前是否已连接。其实我怀疑这种用法是否凑效,实测在子线程里,多次执行此方法,即使在已经连接的情况下,他仍然返回未连接。

2、ConnectNamedPipe的第二种用法,阻塞:
    if(!ConnectNamedPipe(hPipe,NULL))
    {
        if(ERROR_IO_PENDING!=GetLastError())
        {
            QMessageBox::about(this,"提醒","等待客户端连接失败");
            CloseHandle(hPipe);
            CloseHandle(hEvent);
            hPipe = NULL;
            return;
        }
    }else
    {
        this->ui->statusLabel->setText("已连接");
    }

第二个参数传入NULL,他会一直等待连接,这样就解决了如何判断是否已连接的问题。

因此整个程序就可以改成,CreateNamedPipe+ConnectNamedPipe,然后处于等待状态,直到连接成功返回,因为一直阻塞所以应该将他放在子线程中处理。

3、注意事项:管道名称的传入

执行CreateNamedPipe的时候,要传入管道名称,这是一个LPCWSTR类型,直接传入QString肯定是不行的。

如果是常亮hardcode字符串,直接TEXT("ABC")这样传入就可以了;

如果是变量,则需要将QString转为指定的类型:

            // 传入管道名称,如果是常量直接
            // TEXT("ABC")
            // 如果是变量,从QString转过来:
            // QString myPipeName = QString("\\\\.\\Pipe\\%1").arg(pipeName);
            // const QChar* pipeText = myPipeName.unicode();
            // (LPCWSTR)pipeText
            ControllerBg::hPipe = CreateNamedPipe((LPCWSTR)pipeText,PIPE_ACCESS_DUPLEX|FILE_FLAG_WRITE_THROUGH|FILE_FLAG_OVERLAPPED,PIPE_TYPE_MESSAGE|PIPE_READMODE_MESSAGE|PIPE_WAIT,PIPE_UNLIMITED_INSTANCES,0,0,NMPWAIT_WAIT_FOREVER,NULL);


  • 1
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值