Связи С++ и С# с использованием именованной трубы
Я пытаюсь отменить обработку dll, введенного в процесс, который делает hook winsock send()
и отправляет данные по PipeStream
.
Это код С#, который считывает поток канала:
[StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)]
public struct PipeHeader
{
[MarshalAs(UnmanagedType.I1)]
public byte command;
[MarshalAs(UnmanagedType.I4)]
public int sockid;
public int datasize;
}
public static object RawDeserializeEx(byte[] rawdatas, Type anytype)
{
int rawsize = Marshal.SizeOf(anytype);
if (rawsize > rawdatas.Length)
return null;
GCHandle handle = GCHandle.Alloc(rawdatas, GCHandleType.Pinned);
IntPtr buffer = handle.AddrOfPinnedObject();
object retobj = Marshal.PtrToStructure(buffer, anytype);
handle.Free();
return retobj;
}
private void PipeRead()
{
byte[] dbPipeMsgIn = new byte[9];
byte[] zero = new byte[] { 0 };
byte[] dbPipeMsgInData;
PipeLoop:
while (pipeIn.Read(dbPipeMsgIn, 0, 9) != 0)
{
strPipeMsgIn = (PipeHeader)RawDeserializeEx(dbPipeMsgIn, typeof(PipeHeader));
if (strPipeMsgIn.datasize != 0)
{
dbPipeMsgInData = new byte[strPipeMsgIn.datasize];
pipeIn.Read(dbPipeMsgInData, 0, dbPipeMsgInData.Length);
//do something with dbPipeMsgInData
}
}
if (pipeIn.IsConnected) goto PipeLoop;
}
До сих пор я подключил функцию send()
, подключил и отправил сообщения через трубу.
Проблема в том, что полученные данные - это не данные, которые я ожидаю, поэтому, вероятно, я сделал неправильный путь. Мне нужна помощь, потому что у меня очень мало знаний о С++.
Код С++:
#pragma pack(1)
typedef struct
{
byte command;
int sockid;
int datasize;
} PipeHeader;
#pragma pack(0)
int WINAPI MySend(SOCKET s, const char* buf, int len, int flags)
{
PipeHeader ph;
string p(buf);
if(p.find("<TalkMsg") == 0){
byte cmd = 0;
ph.command = cmd;
ph.sockid = s;
ph.datasize = len;
char buffer[sizeof(ph)];
memcpy(buffer, &ph, sizeof(ph));
if(SendPipeMessage(buffer, sizeof(buffer))){
if(SendPipeMessage(buf, sizeof(buf))){
MessageBox(NULL,"Message Sent", NULL, NULL);
}
}
fopen_s(&pSendLogFile, "C:\\SendLog.txt", "a+");
fprintf(pSendLogFile, "%s\n", buf);
fclose(pSendLogFile);
}
return pSend(s, buf, len, flags);
}
BOOL SendPipeMessage(LPCVOID lpvMessage, DWORD ctToWrite){
// Send a message to the pipe server.
cbToWrite = sizeof(lpvMessage);
fSuccess = WriteFile(
hPipe, // pipe handle
lpvMessage, // message
cbToWrite, // message length
&cbWritten, // bytes written
NULL); // not overlapped
if ( ! fSuccess)
{
return false;
}else return true;
}
Изменить, подробнее:
Цель состоит в том, чтобы отправить сообщение, содержащее длительность PipeHeader
Struct по 9 байтам по каналу, затем отправить другое сообщение, содержащее данные winsock send()
, (переменную buf
), прочитать трубу в приложении С# и проанализируйте первое сообщение, чтобы получить DataSize следующего входящего сообщения (что datasize
var of PipeHeader
), затем, используя datasize
, снова прочитайте трубку, чтобы получить буфер send().
Я думаю, что работаю именно так. Я не очень хорошо знаю, как работает Pipes.
В любом случае главная цель - отправить буфер send()
из С++ Dll в приложение С#.
Update:
Кажется, что я должен сначала сериализовать структуру PipeHeader
таким образом, чтобы я мог Deserialize с помощью RawDeserializeEx()
в коде С#.
Я пробовал:
char buffer[sizeof(ph)];
memcpy(buffer, &ph, sizeof(ph));
Проблема заключается в том, что в С++ sizeof(ph)
или sizeof(buffer)
возвращается 12 байтов.
Вместо этого в С# неуправляемый размер (Marshal.SizeOf()
) того же Struct, верните 9 байтов.
Обновление 2:
Устранены различия в размерах, изменив структуру упаковки.
Но я все еще не получаю правильные значения в С#.
Обновлен код.
Ответы
Ответ 1
Мой первый пост, так что прошу меня: -)
BOOL SendPipeMessage(LPCVOID lpvMessage){
// Send a message to the pipe server.
cbToWrite = sizeof(lpvMessage);
sizeof используется неправильно. Он вернет размер типа LPCVOID вместо размера буфера, как вы планируете. Вы отправляете 4 байта (на x86), когда вы хотите отправить 9 байтов.
По-моему, вам нужно предоставить новый параметр для вашего SendPipeMessage()
BOOL SendPipeMessage(LPCVOID lpvMessage, DWORD cbToWrite)
Вы можете использовать sizeof при первом вызове. При втором вызове вы можете использовать len в качестве аргумента.
if(SendPipeMessage((LPCVOID)&ph, sizeof(ph))){
if(SendPipeMessage(buf, len)){
Ответ 2
Не проверял весь код, так как сейчас я устарел. Но я заметил:
- Я уверен, вы не можете ожидать, что
int
всегда 4 байта на С++. Где размер имеет значение int
не лучший выбор.
- Вы забыли
[MarshalAs(UnmanagedType.I4)]
для члена datasize
в коде С#. Атрибуты применяются только к следующему элементу, а не ко всем следующим элементам.
- Замените
SendPipeMessage(buffer, sizeof(buffer))
на SendPipeMessage((char*)&ph, sizeof(ph))
, чтобы получить желаемый результат. Нет необходимости в memcpy
для массива char и как M.L. упомянутый sizeof(buffer)
не то, что вы хотите.
- Замените
sizeof(buf)
на len
. У вас уже есть длина, поэтому зачем использовать sizeof? И sizeof(buf)
снова не то, что вы хотите.
- Вы можете упростить код возврата в конце до
return fSuccess;
. Нет необходимости в if-else. Или просто return WriteFile(...);
.
- Почему вы используете
cbToWrite = sizeof(lpvMessage);
? Это снова не то, что вы хотите, и вы уже прошли ctToWrite
, который вы должны использовать вместо этого в вызове WriteFile
.
Ответ 3
Ваш вопрос не был особенно ясен для меня. Я не был уверен, что проблема заключается в обработке труб или сериализации. Я предоставил ниже полный код сервера канала С++ и код клиента канала С#. Я попытался сохранить все так же просто, как полезно продемонстрировать диалог. Я использовал ваш код структуры и десериализации и добавил метод сериализации. В обоих примерах кода есть ссылка на MS doc, которая должна упростить изменение отношения клиентского сервера, если это необходимо. Сервер и клиентский код автономны и предназначены для работы в разных процессах (два консольных приложения). Нет потоков, и сервер не будет обрабатывать более одного соединения.
В терминах простой (де/) сериализации я бы предложил использовать класс и методы BitConverter внутри структуры для ручной конвертации в массив байтов и из него. Это дает вам более тонкий уровень контроля и легче отлаживать. Если у вас есть сложные структуры для представления, я бы предложил вам посмотреть на "protobuf" Google. Конечно, YMMV.
Код сервера канала С++ (Windows)
#include <stdio.h>
#include <stdint.h>
#include <winsock.h>
#include <string>
// see https://msdn.microsoft.com/en-us/library/bb546085(v=vs.110).aspx?cs-save-lang=1&cs-lang=csharp#code-snippet-1
// for information on creating pipe clients and servers in c++
using namespace std;
#pragma pack(1)
typedef struct {
int8_t command;
int32_t sockid;
int32_t datasize;
} PipeHeader;
#pragma pack()
int wmain( int argc, wchar_t* argv[] ) {
auto pipename = "\\\\.\\pipe\\pipey"; // can be any name, but must start '\\.\pipe\'
printf("Create pipe '%s'\r\n", pipename);
auto pipeHandle = CreateNamedPipe(pipename,
PIPE_ACCESS_DUPLEX,
PIPE_TYPE_MESSAGE | PIPE_WAIT,
1, // # instances
64, // out buff
64, // in buff
0, // timeout, 0 = default of 50ms
NULL); // security attrs
printf("Waiting for pipe connect\r\n");
if (ConnectNamedPipe(pipeHandle, NULL)) {
printf("Pipe connected\r\n");
PipeHeader hdr;
DWORD bytesRead;
int8_t cmd = -1;
while (cmd != 0) {
if (ReadFile(pipeHandle, &hdr, sizeof(PipeHeader), &bytesRead, NULL)) {
// you can check or assert here that bytesRead == sizeof(PipeHeader)
printf("Read %d bytes from pipe, {command: %d, sockid: %d, datasize: %d}\r\n",
bytesRead, hdr.command, hdr.sockid, hdr.datasize);
if (hdr.command == 0) { // assume 0 is the exit cmd
break;
}
else if (hdr.command == 1) {
// do whatever cmd 1 is ...
}
hdr.command = 4;
hdr.sockid = 101;
hdr.datasize *= 2;
if (WriteFile(pipeHandle, &hdr, sizeof(PipeHeader), &bytesRead, NULL)) {
printf("Data written to pipe\r\n");
}
else {
printf("Pipe write failed\r\n");
break;
}
}
else {
printf("Read error: %d\r\n", GetLastError());
break; // exit
}
}
if (DisconnectNamedPipe(pipeHandle) == FALSE) {
printf("Disconnect error: %d\r\n", GetLastError());
}
}
else {
printf("Pipe connect failed\r\n");
}
CloseHandle(pipeHandle);
printf("Exiting program\r\n");
return 0;
}
Код канала клиента С# (Windows)
using System;
using System.Runtime.InteropServices;
using System.IO.Pipes;
namespace Pipes {
// see https://msdn.microsoft.com/en-us/library/bb546085(v=vs.110).aspx?cs-save-lang=1&cs-lang=csharp#code-snippet-1
// for information on creating pipe clients and servers in c#
class Program {
[StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)]
public struct PipeHeader {
[MarshalAs(UnmanagedType.I1)]
public byte command;
[MarshalAs(UnmanagedType.I4)]
public Int32 sockid;
public Int32 datasize;
}
static void Main(string[] args) {
var pipename = "pipey";
var pipeClient = new NamedPipeClientStream(pipename);
Console.WriteLine("Connecting to server pipe '{0}'", pipename);
pipeClient.Connect();
var hdr = new PipeHeader();
var hdrSize = Marshal.SizeOf(hdr);
hdr.command = 1;
hdr.sockid = 1912;
hdr.datasize = 32;
var buf = Serialize(hdr);
Console.WriteLine("Writing to server pipe");
pipeClient.Write(buf, 0, hdrSize);
pipeClient.Read(buf, 0, hdrSize);
hdr = (PipeHeader) Deserialize(buf, hdr.GetType());
Console.WriteLine("Pipe read {{ command: {0}, sockid: {1}, datasize: {2} }}",
hdr.command, hdr.sockid, hdr.datasize);
hdr.command = 0; // tell server to disconnect
buf = Serialize(hdr);
Console.WriteLine("Sending disconnect");
pipeClient.Write(buf, 0, hdrSize);
pipeClient.Close();
Console.WriteLine("Pipe closed");
}
public static object Deserialize(byte[] rawdatas, Type anytype) {
int rawsize = Marshal.SizeOf(anytype);
if (rawsize > rawdatas.Length)
return null;
GCHandle handle = GCHandle.Alloc(rawdatas, GCHandleType.Pinned);
IntPtr buffer = handle.AddrOfPinnedObject();
object retobj = Marshal.PtrToStructure(buffer, anytype);
handle.Free();
return retobj;
}
public static byte[] Serialize(object obj) {
int rawsize = Marshal.SizeOf(obj);
var rv = new byte[rawsize];
IntPtr ptr = Marshal.AllocHGlobal(rawsize);
Marshal.StructureToPtr(obj, ptr, true);
Marshal.Copy(ptr, rv, 0, rawsize);
Marshal.FreeHGlobal(ptr);
return rv;
}
}
}
Скомпилируйте оба набора кода. Запустите код сервера, затем запустите клиент. Результат несколько минимален, но показывается обмен данными, и сервер перед тем, как отправить его обратно, изменит данные (показывая двухсторонний обмен данными). Для примера нет реальной цели, кроме как служить.
Надеюсь, этот пример предоставит вам основную информацию, необходимую для решения вашей проблемы.