2012-10-05 10 views
8

Da un programma C# voglio utilizzare WM_COPYDATA con SendMessage per comunicare con un'applicazione legacy C++/cli MFC.Processo da C# a C++ con WM_COPYDATA che passa struct con stringhe

Voglio passare una struttura gestita contenente oggetti stringa.

È possibile trovare l'handle dell'applicazione C++ da utilizzare con SendMessage.

Il bit che non conosco è come la struttura e le sue stringhe possono essere controllate e lette dall'altra parte. Soprattutto perché contiene non-blittables.

Le persone pensano che sia fattibile? Continuerò a lavorarci su, ma apprezzerei qualcuno che ha fatto questo genere di cose dicendomi se non avrebbe funzionato.

Ecco un codice demo se si trattava di un programma C++/cli e non è difficile farlo funzionare. Tuttavia, mi piacerebbe che fosse in una libreria di classi .Net in modo che possa essere facilmente riutilizzato.

//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; 
} 

risposta

9

Ho funzionato.

Un approccio semplice consiste nel serializzare la struttura in una stringa singola e trasferire una stringa. Il blog di swhistlesoft era utile http://www.swhistlesoft.com/blog/2011/11/19/1636-wm_copydata-with-net-and-c

Questo potrebbe essere sufficiente per fornire la semplice messaggistica. La struttura può essere ricostruita all'altra estremità se necessario.

Se una struttura con qualsiasi numero di stringhe deve essere dichiarata come-è, allora deve essere una dimensione fissa, questa è la cosa principale che non stavo ottenendo. Il

MarshalAs(System.Runtime.InteropServices.UnmanagedType.ByValTStr, SizeConst = 9) 

definisce sostanzialmente la dimensione corrisponda alla dimensione C++ che nel nostro caso è un TCHAR szTest [9];

Per trasferire a.struct Net tramite WM_COPYDATA da C# a C++ (/ CLI) ho dovuto fare come segue:

[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; 
    }; 

Per inviare una stringa:

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; 

Per ricevere stringa in C++:

else if(pCDS->dwData == 2) 
    { 
     //copydata message 
     CString csMessage = (LPCTSTR)pCDS->lpData; 
     OutputDebugString("Copydata message received: " + csMessage); 
    } 

Per inviare la struttura:

  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); 

Per re ceive struct in 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; 
    } 

Si prega di notare che questo è il codice demo e esigenze di lavoro in termini di stile, gestione delle eccezioni, ecc, ecc ...

+0

Grazie. Per il marshalling della struttura personalizzata in WM_COPYDATA, il tuo esempio è stato l'unico lavoro trovato su questo sito. –

1

Dalla documentazione:

I dati vengono passati non devono contenere puntatori o altri riferimenti a oggetti non accessibili alla domanda che riceve i dati.

Quindi è necessario impacchettare la stringa in COPYDATASTRUCT.lpData. Se si dispone di una lunghezza massima per ogni stringa allora si può incorporare in una struttura lunghezza fissa

typedef struct tagMYDATA 
{ 
    char s1[80]; 
    char s2[120]; 
} MYDATA; 

se si dispone di una sola stringa di lunghezza variabile si può mettere la stringa alla fine e utilizzare un'intestazione seguita da dati stringa

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()); 

Se si dispone di più stringhe di lunghezza variabile si può ancora usare un colpo di testa e di destinare maggiori spazi per ogni stringhe più un doppio terminatore null, o serializzare tutto in una stringa XML.