Процесс C# в C++ с WM_COPYDATA, передавая структуру со строками
Из программы aC# я хочу использовать WM_COPYDATA с SendMessage для связи с унаследованным приложением C++/cli MFC.
Я хочу передать управляемую структуру, содержащую строковые объекты.
Я могу найти дескриптор приложения C++ для использования с SendMessage нормально.
Немного о чем я не знаю, так это о том, как структура и ее строки могут быть собраны и прочитаны на другом конце. Тем более, что он содержит неблитбалы.
Люди думают, что это возможно? Я буду продолжать работать над этим, но был бы признателен кому-то, кто делал подобные вещи, и говорил мне, если это просто не сработает.
Вот некоторый демонстрационный код, если это была программа на C++ / cli, и заставить ее работать не сложно. Тем не менее, я хотел бы, чтобы это было в библиотеке классов.Net, чтобы ее можно было легко использовать повторно.
//Quick demonstation code only, not correctly styled
int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
struct MessageInfo
{
int nVersion;
char szTest[ 10 ];
};
MessageInfo sMessageInfo;
sMessageInfo.nVersion = 100;
strcpy( sMessageInfo.szTest, "TEST");
COPYDATASTRUCT CDS;
CDS.dwData = 1; //just for test
CDS.cbData = sizeof( sMessageInfo );
CDS.lpData = &sMessageInfo;
//find running processes and send them a message
//can't just search for "MYAPP.exe" as will be called "MYAPP.exe *32" on a 64bit machine
array<System::Diagnostics::Process^>^allProcesses = System::Diagnostics::Process::GetProcesses();
for each (System::Diagnostics::Process^ targetProcess in allProcesses)
{
if (targetProcess->ProcessName->StartsWith("MYAPP", System::StringComparison::OrdinalIgnoreCase))
{
HWND handle = static_cast<HWND>(targetProcess->MainWindowHandle.ToPointer());
BOOL bReturnValue = SendMessage( handle, WM_COPYDATA, (WPARAM)0, (LPARAM)&CDS ) == TRUE;
}
}
return 0;
}
2 ответа
У меня это работает.
Простой подход заключается в сериализации структуры в одну строку и передаче строки. Блог swhistlesoft был полезен http://www.swhistlesoft.com/blog/2011/11/19/1636-wm_copydata-with-net-and-c
Этого может быть достаточно для обеспечения простого обмена сообщениями. Структура может быть перестроена на другом конце, если это необходимо.
Если структура с любым количеством строк должна быть распределена как есть, то это должен быть фиксированный размер, это главное, чего я не получил.
MarshalAs(System.Runtime.InteropServices.UnmanagedType.ByValTStr, SizeConst = 9)
в основном устанавливает размер в соответствии с размером C++, который в нашем случае является TCHAR szTest[ 9 ];
Чтобы перенести структуру.Net через WM_COPYDATA из C# в C++(/cli), мне нужно было сделать следующее:
[System.Runtime.InteropServices.DllImport("user32.dll", CharSet = System.Runtime.InteropServices.CharSet.Auto)]
static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
[System.Runtime.InteropServices.DllImport("user32.dll", CharSet = System.Runtime.InteropServices.CharSet.Auto)]
static extern bool SetForegroundWindow(IntPtr hWnd);
public static uint WM_COPYDATA = 74;
//from swhistlesoft
public static IntPtr IntPtrAlloc<T>(T param)
{
IntPtr retval = System.Runtime.InteropServices.Marshal.AllocHGlobal(System.Runtime.InteropServices.Marshal.SizeOf(param));
System.Runtime.InteropServices.Marshal.StructureToPtr(param, retval, false);
return (retval);
}
//from swhistlesoft
public static void IntPtrFree(IntPtr preAllocated)
{
if (IntPtr.Zero == preAllocated) throw (new Exception("Go Home"));
System.Runtime.InteropServices.Marshal.FreeHGlobal(preAllocated);
preAllocated = IntPtr.Zero;
}
[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
struct COPYDATASTRUCT
{
public uint dwData;
public int cbData;
public IntPtr lpData;
}
/// <summary>
/// Dot net version of AppInfo structure. Any changes to the structure needs reflecting here.
/// struct must be a fixed size for marshalling to work, hence the SizeConst entries
/// </summary>
[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential, Pack = 1)]
struct AppInfoDotNet
{
public int nVersion;
[System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.ByValTStr, SizeConst = 9)]
public string test;
};
Чтобы отправить строку:
COPYDATASTRUCT cd = new COPYDATASTRUCT();
cd.dwData = 2;
cd.cbData = parameters.Length + 1;
cd.lpData = System.Runtime.InteropServices.Marshal.StringToHGlobalAnsi(parameters);
IntPtr cdBuffer = IntPtrAlloc(cd);
messageReceived = ((int)SendMessage(targetProcess.MainWindowHandle, WM_COPYDATA, IntPtr.Zero, cdBuffer)) != 0;
Чтобы получить строку в C++:
else if(pCDS->dwData == 2)
{
//copydata message
CString csMessage = (LPCTSTR)pCDS->lpData;
OutputDebugString("Copydata message received: " + csMessage);
}
Чтобы отправить структуру:
AppInfoDotNet appInfo = new AppInfoDotNet();
appInfo.test = "a test";
COPYDATASTRUCT cds3;
cds3.dwData = 1;
cds3.cbData = System.Runtime.InteropServices.Marshal.SizeOf(appInfo);
IntPtr structPtr = System.Runtime.InteropServices.Marshal.AllocCoTaskMem(System.Runtime.InteropServices.Marshal.SizeOf(appInfo));
System.Runtime.InteropServices.Marshal.StructureToPtr(appInfo, structPtr, false);
cds3.lpData = structPtr;
IntPtr iPtr = System.Runtime.InteropServices.Marshal.AllocCoTaskMem(System.Runtime.InteropServices.Marshal.SizeOf(cds3));
System.Runtime.InteropServices.Marshal.StructureToPtr(cds3, iPtr, false);
messageReceived = ((int)SendMessage(targetProcess.MainWindowHandle, WM_COPYDATA, IntPtr.Zero, iPtr)) != 0;
System.Runtime.InteropServices.Marshal.FreeCoTaskMem(iPtr);
System.Runtime.InteropServices.Marshal.FreeCoTaskMem(structPtr);
Получить структуру в C++:
LRESULT CMainFrame::OnCopyData( WPARAM wParam, LPARAM lParam )
{
LRESULT lResult = FALSE;
COPYDATASTRUCT *pCDS = (COPYDATASTRUCT*)lParam;
//Matching message type for struct
if(pCDS->dwData == 1)
{
AppInfo *pAppInfo = (AppInfo*)pCDS->lpData
lResult = true;
}
Обратите внимание, что это демонстрационный код, и он требует работы с точки зрения стиля, обработки исключений и т. Д. И т. Д.
Из документации:
Передаваемые данные не должны содержать указателей или других ссылок на объекты, недоступные для приложения, получающего данные.
Поэтому вам нужно упаковать вашу строку в COPYDATASTRUCT.lpData. Если у вас есть максимальная длина для каждой строки, вы можете встроить ее в структуру фиксированной длины
typedef struct tagMYDATA
{
char s1[80];
char s2[120];
} MYDATA;
Если у вас есть только одна строка переменной длины, вы можете поместить строку в конец и использовать заголовок, за которым следуют строковые данные
typedef struct tagMYDATA
{
int value1;
float value2;
int stringLen;
} MYDATAHEADER;
MyCDS.cbData = sizeof(MYDATAHEADER)+(int)stringData.size();
MyCDS.lpData = new BYTE[MyCDS.cbData];
memcpy(MyCDS.lpData,&dataHeader,sizeof*(MYDATAHEADER);
StringCbCopyA (
((BYTE*)MyCDS.lpData)+sizeof*(MYDATAHEADER)
,stringData.size()
,stringData.c_str());
Если у вас несколько строк переменной длины, вы все равно можете использовать заголовок и выделять больше пробелов для каждой строки плюс двойной нулевой терминатор или сериализовать все в одну строку XML.