2012-03-06 30 views
6

Tôi cần tham gia chương trình C++, tải CLR và gọi một hàm trong thư viện C#. Hàm tôi cần gọi trong giao diện COM làm tham số.CLR Hosting: Gọi hàm có chữ ký phương thức tùy ý?

Vấn đề của tôi là, CLR giao diện lưu trữ dường như chỉ cho phép bạn gọi một phương thức có chữ ký này:

int Foo(String arg) 

Ví dụ, C này đang tải ++ CLR và chạy chức năng P.Test trong "test.exe ":

ICLRRuntimeHost *pClrHost = NULL; 
HRESULT hrCorBind = CorBindToRuntimeEx(NULL, L"wks", 0, CLSID_CLRRuntimeHost, IID_ICLRRuntimeHost, (PVOID*)&pClrHost); 

HRESULT hrStart = pClrHost->Start(); 

DWORD retVal; 
HRESULT hrExecute = pClrHost->ExecuteInDefaultAppDomain(L"C:\\Test.exe", L"P", L"Test", L"", &retVal); 

những gì tôi cần làm là gọi một hàm với phương pháp chữ ký này (lưu ý tôi sở hữu mã C#, vì vậy tôi có thể thay đổi nó):

void SomeFunction(IFoo interface) 

Trường hợp IFoo là giao diện com. Tôi thậm chí có thể làm những gì tôi cần phải nếu tôi có thể gọi một chức năng như thế này:

IntPtr SomeFunction(); 

tôi có thể có SomeFunction xây dựng một đại biểu đúng sau đó sử dụng Marshal.GetFunctionPointerForDelegate. Tuy nhiên, tôi không thể tìm ra cách để làm cho các giao diện lưu trữ làm bất cứ điều gì khác hơn là gọi một chức năng với một int func (chuỗi) chữ ký.

Có ai biết cách gọi hàm C# từ mã C++ với chữ ký khác không?

(. Lưu ý tôi không thể sử dụng C++/CLI cho điều này tôi cần phải sử dụng các API lưu trữ.)

+0

Bạn đang cố gắng tương tác với chương trình C# (ví dụ: gửi giao diện COM dưới dạng tham số hoặc lấy lại một tham số) hoặc chỉ gọi một số phương thức từ C++ đến C#? – AVIDeveloper

+0

Tôi thực sự chỉ cần gọi một hàm C# từ C++ (và lấy lại IntPtr từ C#, không phải là int).Tôi có thể lo phần còn lại, nhưng tôi không thấy cách nào để làm điều này. – LCC

+0

Tôi nghĩ rằng một số chi tiết hơn về những gì bạn đang làm sẽ giúp đỡ. Bạn đang cố gắng để có được mã C# để làm một điều sau đó trở lại, hoặc bạn đang mong đợi một interop liên tục hơn. Tại sao C++/CLI không phải là một khả năng nếu bạn kiểm soát cả hai bên được quản lý và không được quản lý? Cuối cùng là có bất kỳ cách nào bạn có thể xác định lại chéo qua được quản lý bắt đầu (do đó bằng cách sử dụng DllImport). – Guvante

Trả lời

7

Edit: tôi hứa sẽ cập nhật câu trả lời của tôi để bao gồm mã để thông qua giá trị 64-bit, vì vậy đây là ..

  • Tôi để lại câu trả lời gốc nếu ai đó quan tâm đến giải pháp ít phức tạp hơn cho hệ thống 32 bit.

Lưu ý: Vì bạn đang sử dụng CorBindToRuntimeEx, đó là lỗi thời trong .net 4.0, tôi sẽ giả định một .net 2.0 giải pháp phù hợp sử dụng tốt Win32 API cũ.

Vì vậy, để truyền dữ liệu giữa C# và C++ (trong trường hợp của chúng tôi - các IntPtr của một đại biểu), chúng ta sẽ tạo một dự án Win32 DLL nhỏ, tên SharedMem, với hai phương pháp thẳng về phía trước.

SharedMem.h

#pragma once 

#ifdef SHAREDMEM_EXPORTS 
#define SHAREDMEM_API __declspec(dllexport) 
#else 
#define SHAREDMEM_API __declspec(dllimport) 
#endif 

#define SHAREDMEM_CALLING_CONV __cdecl 

extern "C" { 
    SHAREDMEM_API BOOL SHAREDMEM_CALLING_CONV SetSharedMem(ULONGLONG _64bitValue); 
    SHAREDMEM_API BOOL SHAREDMEM_CALLING_CONV GetSharedMem(ULONGLONG* p64bitValue); 
} 

Bây giờ cho các tập tin thực hiện:

SharedMem.cpp

#include "stdafx.h" 
#include "SharedMem.h" 

HANDLE  hMappedFileObject = NULL; // handle to mapped file 
LPVOID  lpvSharedMem = NULL;  // pointer to shared memory 
const int SHARED_MEM_SIZE = sizeof(ULONGLONG); 

BOOL CreateSharedMem() 
{ 
    // Create a named file mapping object 
    hMappedFileObject = CreateFileMapping(
          INVALID_HANDLE_VALUE, 
          NULL, 
          PAGE_READWRITE, 
          0, 
          SHARED_MEM_SIZE, 
          TEXT("shmemfile") // Name of shared mem file 
         ); 

    if (hMappedFileObject == NULL) 
    { 
     return FALSE; 
    } 

    BOOL bFirstInit = (ERROR_ALREADY_EXISTS != GetLastError()); 

    // Get a ptr to the shared memory 
    lpvSharedMem = MapViewOfFile(hMappedFileObject, FILE_MAP_WRITE, 0, 0, 0); 

    if (lpvSharedMem == NULL) 
    { 
     return FALSE; 
    } 

    if (bFirstInit) // First time the shared memory is accessed? 
    { 
     ZeroMemory(lpvSharedMem, SHARED_MEM_SIZE); 
    } 

    return TRUE; 
} 

BOOL SetSharedMem(ULONGLONG _64bitValue) 
{ 
    BOOL bOK = CreateSharedMem(); 

    if (bOK) 
    { 
     ULONGLONG* pSharedMem = (ULONGLONG*)lpvSharedMem; 
     *pSharedMem = _64bitValue; 
    } 

    return bOK; 
} 

BOOL GetSharedMem(ULONGLONG* p64bitValue) 
{ 
    if (p64bitValue == NULL) return FALSE; 

    BOOL bOK = CreateSharedMem(); 

    if (bOK) 
    { 
     ULONGLONG* pSharedMem = (ULONGLONG*)lpvSharedMem; 
     *p64bitValue = *pSharedMem; 
    } 

    return bOK; 
} 
  • Lưu ý rằng vì đơn giản tôi chỉ chia sẻ một Giá trị 64 bit, nhưng đây là tổng quát cách chia sẻ bộ nhớ giữa C# và C++. Vui lòng phóng to SHARED_MEM_SIZE và/hoặc thêm các chức năng để chia sẻ các loại dữ liệu khác mà bạn thấy phù hợp.

Đây là cách chúng tôi sẽ tiêu thụ các phương pháp trên: chúng tôi sẽ sử dụng SetSharedMem() trên C# bên nhằm thiết lập của IntPtr như một giá trị 64-bit (không phân biệt nếu mã chạy trên một phiên bản 32 hoặc một đại biểu Hệ thống 64 bit).

C# Mã

namespace CSharpCode 
{ 
    delegate void VoidDelegate(); 

    static public class COMInterfaceClass 
    { 
     [DllImport("SharedMem.dll")] 
     static extern bool SetSharedMem(Int64 value); 

     static GCHandle gcDelegateHandle; 

     public static int EntryPoint(string ignored) 
     { 
      IntPtr pFunc = IntPtr.Zero; 
      Delegate myFuncDelegate = new VoidDelegate(SomeMethod); 
      gcDelegateHandle = GCHandle.Alloc(myFuncDelegate); 
      pFunc = Marshal.GetFunctionPointerForDelegate(myFuncDelegate); 
      bool bSetOK = SetSharedMem(pFunc.ToInt64()); 
      return bSetOK ? 1 : 0; 
     } 

     public static void SomeMethod() 
     { 
      MessageBox.Show("Hello from C# SomeMethod!"); 
      gcDelegateHandle.Free(); 
     } 
    } 
} 
  • Lưu ý việc sử dụng GCHandle để ngăn chặn các đại biểu từ việc thu gom rác.
  • Để có các biện pháp tốt, chúng tôi sẽ sử dụng giá trị trả về làm cờ thành công/lỗi.

Trên mặt C++, chúng tôi sẽ trích xuất giá trị 64 bit bằng cách sử dụng GetSharedMem(), chuyển đổi nó thành con trỏ hàm và gọi đại biểu C#.

C++ Mã

#include "SharedMem.h" 
typedef void (*VOID_FUNC_PTR)(); 

void ExecCSharpCode() 
{ 
    ICLRRuntimeHost *pClrHost = NULL; 
    HRESULT hrCorBind = CorBindToRuntimeEx(
           NULL, 
           L"wks", 
           0, 
           CLSID_CLRRuntimeHost, 
           IID_ICLRRuntimeHost, 
           (PVOID*)&pClrHost 
          ); 

    HRESULT hrStart = pClrHost->Start(); 

    DWORD retVal; 
    HRESULT hrExecute = pClrHost->ExecuteInDefaultAppDomain(
           szPathToAssembly, 
           L"CSharpCode.COMInterfaceClass", 
           L"EntryPoint", 
           L"", 
           &retVal // 1 for success, 0 is a failure 
          ); 

    if (hrExecute == S_OK && retVal == 1) 
    { 
     ULONGLONG nSharedMemValue = 0; 
     BOOL bGotValue = GetSharedMem(&nSharedMemValue); 
     if (bGotValue) 
     { 
      VOID_FUNC_PTR CSharpFunc = (VOID_FUNC_PTR)nSharedMemValue; 
      CSharpFunc(); 
     } 
    } 
} 

The Original trả lời - Tốt cho hệ thống 32-bit

Dưới đây là một giải pháp đó là dựa vào việc sử dụng IntPtr.ToInt32() để chuyển đổi các Func đại biểu . ptr. đến số int được trả về từ phương thức tĩnh C# EntryPoint.

(*) Lưu ý việc sử dụng GCHandle để ngăn đại biểu không bị thu gom rác thải.

C# Mã

namespace CSharpCode 
{ 
    delegate void VoidDelegate(); 

    public class COMInterfaceClass 
    { 
     static GCHandle gcDelegateHandle; 

     public static int EntryPoint(string ignored) 
     { 
      IntPtr pFunc = IntPtr.Zero; 
      Delegate myFuncDelegate = new VoidDelegate(SomeMethod); 
      gcDelegateHandle = GCHandle.Alloc(myFuncDelegate); 
      pFunc = Marshal.GetFunctionPointerForDelegate(myFuncDelegate); 
      return (int)pFunc.ToInt32(); 
     } 

     public static void SomeMethod() 
     { 
      MessageBox.Show("Hello from C# SomeMethod!"); 
      gcDelegateHandle.Free(); 
     } 
    } 
} 

C++ Mã Chúng tôi sẽ cần phải chuyển đổi trở int giá trị cho một con trỏ hàm, vì vậy chúng tôi sẽ bắt đầu bằng cách xác định một void function ptr. loại:

typedef void (*VOID_FUNC_PTR)(); 

Và phần còn lại của mã trông khá giống mã ban đầu của bạn, với việc bổ sung chuyển đổi và thực thi hàm ptr.

ICLRRuntimeHost *pClrHost = NULL; 
HRESULT hrCorBind = CorBindToRuntimeEx(
          NULL, 
          L"wks", 
          0, 
          CLSID_CLRRuntimeHost, 
          IID_ICLRRuntimeHost, 
          (PVOID*)&pClrHost 
         ); 

HRESULT hrStart = pClrHost->Start(); 

DWORD retVal; 
HRESULT hrExecute = pClrHost->ExecuteInDefaultAppDomain(
          szPathToAssembly, 
          L"CSharpCode.COMInterfaceClass", 
          L"EntryPoint", 
          L"", 
          &retVal 
         ); 

if (hrExecute == S_OK) 
{ 
    VOID_FUNC_PTR func = (VOID_FUNC_PTR)retVal; 
    func(); 
} 

một chút tắm

Bạn cũng có thể tận dụng các string đầu vào trong nhằm chọn ra những phương pháp để thực hiện:

public static int EntryPoint(string interfaceName) 
{ 
    IntPtr pFunc = IntPtr.Zero; 

    if (interfaceName == "SomeMethod") 
    { 
     Delegate myFuncDelegate = new VoidDelegate(SomeMethod); 
     gcDelegateHandle = GCHandle.Alloc(myFuncDelegate); 
     pFunc = Marshal.GetFunctionPointerForDelegate(myFuncDelegate); 
    } 

    return (int)pFunc.ToInt32(); 
} 
  • Bạn có thể nhận được nhiều hơn chung bằng cách sử dụng sự phản chiếu để tìm ra phương pháp đúng theo đầu vào chuỗi đã cho.
+0

Bạn đang giả sử IntPtr sẽ vừa với số nguyên 32 bit sẽ không phải lúc nào cũng đúng. Tôi cho rằng tôi có thể gọi hai lần để có được một nửa cao/thấp của số nguyên đó là một hack khổng lồ như vậy. =/ – LCC

+0

Đồng ý. Tôi đã bật máy tính của mình ngay bây giờ để viết một tuyên bố từ chối trách nhiệm về khoảng trống ToInt32() và 64-bit và tôi thấy bạn đã ở đó. Tôi không nghĩ rằng bạn cần phải gọi hai lần, nhưng một cái gì đó nên được thực hiện. Một giải pháp 'MemoryMappedFile' xuất hiện trong tâm trí, nhưng bây giờ đã quá muộn rồi nên tôi sẽ phải cập nhật câu trả lời của mình sau này. – AVIDeveloper

+0

@AVIDeveloper Noob question: Trong trường hợp 64 bit, các tệp ánh xạ bộ nhớ có thực sự là tuyến đường dễ nhất không? Bên cạnh đó chỉ để vượt qua một giá trị của một loại lớn hơn xung quanh nó có vẻ lạ rằng một bộ công cụ hoàn toàn mới là cần thiết. Và, chúng ta có thể trả về các giá trị của kiểu String^trở lại vùng C++ (sử dụng nó như một chuỗi: 'static_cast (retVal.bstrVal)', xem 'http://blogs.msdn.com/b/msdnforum/archive/2010/07/09/use-clr4-hosting-api-to-invoke-net-assembly-từ-native-c.aspx'), và các chuỗi phải có khả năng mang các giá trị 64 bit. –

Các vấn đề liên quan