2010-03-20 62 views
13

Tôi có một ứng dụng C++ bản địa (không được quản lý) (sử dụng wxWidgets cho giá trị của nó). Tôi đang xem xét một ứng dụng công cụ riêng biệt được viết bằng C# có chứa các hộp thoại dựa trên winform. đưa một số các hộp thoại trong một DLL riêng biệt sẽ hữu ích như tôi hy vọng sẽ có thể sử dụng chúng từ ứng dụng C++ của tôi.Tạo một C# DLL và sử dụng nó từ không được quản lý C++

Nhưng tôi không biết có bao nhiêu điều rắc rối là cần thiết để thực hiện điều này, nó có đặc biệt dễ dàng không?

EDIT:

Tôi không cần phải gọi hộp thoại chức năng trực tiếp. Tôi chỉ cần một cách cho ứng dụng C++ của tôi để gọi một API trong C# DLL để truyền dữ liệu, và một cách để C# DLL gọi các phương thức trên một số loại đối tượng quan sát/trở lại trong ứng dụng C++.

ví dụ từ C++:

CSharpManager *pCSM = SomehowGetPointerToCSharpObject(); 
CSharpObserver pCSO = new CSharpObserver; 

pCSM->RegisterCPlusPlusObserver(pCSO); 
pCSM->LaunchDialog(); 

Khi người dùng thực hiện các công cụ trong hộp thoại C#, phương pháp PCSO được kêu gọi để truyền dữ liệu trở lại vào C++

Vì vậy, nó là khá nhiều liệu C++/C câu hỏi # giao tiếp , Tôi nghĩ. Nhưng mặc dù tôi biết C++ và C# Tôi không biết làm thế nào .net chính nó hoạt động. Tôi biết COM nhưng thực sự muốn tránh nó, tôi nghi ngờ bất kỳ nhà phát triển khác tôi làm việc với biết nó.

+1

Xuất chức năng từ thư viện lớp C#: http://stackoverflow.com/questions/2425212/exporting-functions-from-ac-class-library –

+0

Nếu bạn không muốn tham gia vào COM, tôi sẽ đề xuất những gì người khác cũng có: bọc lớp được quản lý trong C++ được quản lý. Một chủ đề khác được đưa vào đó cụ thể: http://stackoverflow.com/questions/9944539/c-cli-wrapping-managed-code-for-unmanaged-use –

Trả lời

1

Điều đó tùy thuộc vào ý của bạn là "Tôi hy vọng có thể sử dụng chúng từ ứng dụng C++ của tôi".

Trong thế giới riêng, hộp thoại có cấu trúc mẫu hộp thoại và bạn có thể "nấu" cái này vào tệp thi hành của mình, có thể là tệp DLL hoặc EXE. Khỏe. Nhưng trong thế giới được quản lý, mọi thứ hơi khác một chút. Không có loại tài nguyên "mẫu hộp thoại" nào cho các ứng dụng Winforms. Thay vào đó, các biểu mẫu chỉ là mã.

Tuy nhiên:

  • Bạn luôn có thể gọi vào một DLL được quản lý từ mã không được quản lý. Đây là tầm thường. DLL được quản lý đó có thể hiển thị các hộp thoại Winforms của bạn. Vì vậy, phần C++ gốc của ứng dụng của bạn có thể gọi các hộp thoại đó. Nhưng không thể khởi tạo trực tiếp mà không cần thêm một số công việc.

  • Bạn luôn có thể chèn một "DLL shim" C++/CLI giữa mã C++ gốc và DLL được quản lý của bạn. Trong C++/CLI, bạn có thể tải cả các tài nguyên/hộp thoại được quản lý và .NET một cách minh bạch.

  • Đối với vấn đề đó, bạn có thể gọi các phương thức .NET trực tiếp từ mã gốc, không có DLL trung gian C++/CLI, mặc dù hơi lộn xộn một chút.

Nhưng về việc sử dụng "tài nguyên hộp thoại .NET/Winforms" trực tiếp ... không. Không theo ý nghĩa của sử dụng cùng một mẫu hộp thoại cho cả Winforms cũng như C++ gốc.

+0

Tôi không cần gọi trực tiếp các chức năng của hộp thoại. Tôi chỉ cần một cách cho ứng dụng C++ của tôi để gọi một API trong C# DLL để truyền dữ liệu, và một cách để C# DLL gọi các phương thức trên một số loại đối tượng quan sát/trở lại trong ứng dụng C++. –

6

lingua franca của interop trong mã không được quản lý là COM. Điều đó rất dễ dàng để tiến lên phía C#, chỉ cần sử dụng [ComVisible] attribute. Bạn sẽ cần phải viết mã COM trong chương trình C++ của bạn để sử dụng nó, không phải dễ dàng như vậy để có được đi nếu bạn đã không bao giờ làm điều đó. Bắt đầu với #import directive nếu bạn sử dụng trình biên dịch MSVC.

Tùy chọn tiếp theo của bạn là lưu trữ CLR trong mã không được quản lý của bạn thay vì dựa vào lớp tương tác COM để xử lý nó. Nó cho phép bạn tạo các loại được quản lý trực tiếp. Điều này đòi hỏi COM là tốt, nhưng chỉ để có được CLR nạp và khởi tạo. This project cho thấy cách tiếp cận.

+0

Heh. "Lingua franca". Thật. +1 –

+1

COM không cần thiết cho điều này, vì vậy nếu ứng dụng C++ chưa sử dụng nó, tôi sẽ không bắt đầu. Tất nhiên, trong trường hợp như vậy gọi trở lại mã C++ trở thành lời gọi đến một hàm toàn cục truyền một con trỏ đối tượng trạng thái như một đối số, thay cho các hàm thành viên của đối tượng trạng thái, nhưng đây là SO dễ dàng hơn nhiều để viết (và gỡ lỗi - nếu một cái gì đó bị ghi đè do p/gọi không khớp, ít nhất bạn vẫn đang gọi mã đúng ngay cả khi dữ liệu bị hỏng, không giống như COM nơi các cuộc gọi hàm dựa trên một con trỏ v-table hợp lệ). –

+0

@Ben: kiểm tra OP, đây là EXE không được quản lý. Không thể gọi lại mã C++ cho đến khi ai đó tải CLR và mã được quản lý cuộc gọi trước. –

2

Hoặc sử dụng COM, hoặc viết trình bao bọc C++/CLI gọi C# d của bạn ialog, sau đó gọi trình bao bọc C++/CLI này từ mã C++ không được quản lý của bạn.

+0

+1 Tôi khuyên bạn nên sử dụng trình bao bọc C++/CLI. –

1

Tôi biết có một vài câu trả lời ở đây, nhưng không có câu nào trong số đó chỉ ra một ví dụ làm việc. Khi tôi gặp phải vấn đề này, tôi đã có thể tìm ra được nhờ ví dụ này.

http://support.microsoft.com/kb/828736

-1

tôi sẽ đăng bài này như một bình luận cho một bài trước đây, nhưng kể từ khi bạn đã không được chấp nhận bất kỳ câu trả lời nào có thể nó sẽ là một trong những bạn đang tìm kiếm.

Bài đăng gốc của bạn có một câu hỏi: "có dễ dàng không?" Câu trả lời cho điều đó là nhấn mạnh không, bằng chứng là câu trả lời bạn nhận được.

Nếu các đề xuất khác (xuất bản gốc/COM/v.v.) là "trên đầu của bạn" (lời nói của bạn!), Và bạn sẽ không thể đi sâu vào và tìm hiểu, đề xuất của tôi sẽ là bạn cần xem xét lại kiến ​​trúc được đề xuất của bạn.

Tại sao không viết các hàm được chia sẻ trong thư viện C++, sau đó có thể dễ dàng sử dụng ứng dụng C++ hiện có của bạn? Theo nguyên tắc chung, việc tiêu thụ các thành phần gốc từ mã được quản lý dễ dàng hơn nhiều so với ngược lại - vì vậy việc viết ứng dụng C# của bạn để tiêu thụ DLL C++ được chia sẻ sẽ là một công việc dễ dàng hơn nhiều.

Tôi nhận thấy điều này không trả lời câu hỏi kỹ thuật ban đầu, nhưng có lẽ đó là câu trả lời thực tế hơn cho vấn đề bạn đang gặp phải.

0

Sử dụng biểu mẫu trong C# DLL được gọi từ C++ không dễ dàng, nhưng một khi bạn có một số mã tiện ích được viết, nó có thể khá mạnh mẽ. Callbacks to C++ code rất dễ dàng.

Để thực hiện Biểu mẫu (hoặc WPF cho vấn đề đó), lớp NativeWindow là bạn của bạn. Bạn muốn có nhiều khả năng hơn NativeWindow cung cấp cho bạn để một dẫn xuất là theo thứ tự. Đoạn mã dưới đây cho thấy một triển khai có nguồn gốc từ NativeWindow và cung cấp cho các trình xử lý sự kiện tin nhắn cho các trình xử lý sự kiện và các trình xử lý sự kiện của cửa sổ.

using System; 
using System.Collections.Generic; 
using System.Diagnostics; 
using System.Linq; 
using System.Runtime.InteropServices; 
using System.Windows.Forms; 

/// <summary> 
/// A <see cref="NativeWindow"/> for the main application window. Used 
/// to be able to run things on the UI thread and manage window message 
/// callbacks. 
/// </summary> 
public class NativeWindowWithCallbacks : NativeWindow, IDisposable 
{ 
    /// <summary> 
    /// Used to synchronize access to <see cref="NativeWindow.Handle"/>. 
    /// </summary> 
    private readonly object handleLock = new object(); 

    /// <summary> 
    /// Queue of methods to run on the UI thread. 
    /// </summary> 
    private readonly Queue<MethodArgs> queue = new Queue<MethodArgs>(); 

    /// <summary> 
    /// The message handlers. 
    /// </summary> 
    private readonly Dictionary<int, MessageHandler> messageHandlers = 
     new Dictionary<int, MessageHandler>(); 

    /// <summary> 
    /// Windows message number to prompt running methods on the UI thread. 
    /// </summary> 
    private readonly int runOnUiThreadWindowsMessageNumber = 
     Win32.RegisterWindowMessage(
       "NativeWindowWithCallbacksInvokeOnGuiThread"); 

    /// <summary> 
    /// Handles the message. 
    /// </summary> 
    /// <param name="sender"> 
    /// The this. 
    /// </param> 
    /// <param name="m"> 
    /// The message. 
    /// </param> 
    /// <returns> 
    /// True if done processing; false otherwise. Normally, returning 
    /// true will stop other handlers from being called, but, for 
    /// some messages (like WM_DESTROY), the return value has no effect. 
    /// </returns> 
    public delegate bool MessageHandler(object sender, ref Message m); 

    /// <summary> 
    /// Gets a value indicating whether the caller must call BeginInvoke 
    /// when making UI calls (like <see cref="Control.InvokeRequired"/>). 
    /// </summary> 
    /// <returns> 
    /// True if not running on the UI thread. 
    /// </returns> 
    /// <remarks> 
    /// This can get called prior to detecting the main window (likely if 
    /// the main window has yet to be created). In this case, this method 
    /// will return true even if the main window subsequently gets 
    /// created on the current thread. This behavior works for queuing up 
    /// methods that will update the main window which is likely the only 
    /// reason for invoking methods on the UI thread anyway. 
    /// </remarks> 
    public bool InvokeRequired 
    { 
     get 
     { 
      int pid; 
      return this.Handle != IntPtr.Zero 
       && Win32.GetWindowThreadProcessId(
         new HandleRef(this, this.Handle), out pid) 
       != Win32.GetCurrentThreadId(); 
     } 
    } 

    /// <summary> 
    /// Like <see cref="Control.BeginInvoke(Delegate,Object[])"/> but 
    /// probably not as good. 
    /// </summary> 
    /// <param name="method"> 
    /// The method. 
    /// </param> 
    /// <param name="args"> 
    /// The arguments. 
    /// </param> 
    /// <remarks> 
    /// This can get called prior to finding the main window (likely if 
    /// the main window has yet to be created). In this case, the method 
    /// will get queued and called upon detection of the main window. 
    /// </remarks> 
    public void BeginInvoke(Delegate method, params object[] args) 
    { 
     // TODO: ExecutionContext ec = ExecutionContext.Capture(); 
     // TODO: then ExecutionContext.Run(ec, ...) 
     // TODO: in WndProc for more accurate security 
     lock (this.queue) 
     { 
      this.queue.Enqueue(
       new MethodArgs { Method = method, Args = args }); 
     } 

     if (this.Handle != IntPtr.Zero) 
     { 
      Win32.PostMessage(
        new HandleRef(this, this.Handle), 
        this.runOnUiThreadWindowsMessageNumber, 
        IntPtr.Zero, 
        IntPtr.Zero); 
     } 
    } 

    /// <summary> 
    /// Returns the handle of the main window menu. 
    /// </summary> 
    /// <returns> 
    /// The handle of the main window menu; Handle <see cref="IntPtr.Zero"/> 
    /// on failure. 
    /// </returns> 
    public HandleRef MenuHandle() 
    { 
     return new HandleRef(
       this, 
       this.Handle != IntPtr.Zero 
        ? Win32.GetMenu(new HandleRef(this, this.Handle)) 
        : IntPtr.Zero); 
    } 

    /// <summary> 
    /// When the instance gets disposed. 
    /// </summary> 
    public void Dispose() 
    { 
     this.ReleaseHandle(); 
    } 

    /// <summary> 
    /// Sets the handle. 
    /// </summary> 
    /// <param name="handle"> 
    /// The handle. 
    /// </param> 
    /// <param name="onlyIfNotSet"> 
    /// If true, will not assign to an already assigned handle. 
    /// </param> 
    public void AssignHandle(IntPtr handle, bool onlyIfNotSet) 
    { 
     bool emptyBacklog = false; 
     lock (this.handleLock) 
     { 
      if (this.Handle != handle 
        && (!onlyIfNotSet || this.Handle != IntPtr.Zero)) 
      { 
       base.AssignHandle(handle); 
       emptyBacklog = true; 
      } 
     } 

     if (emptyBacklog) 
     { 
      this.EmptyUiBacklog(); 
     } 
    } 

    /// <summary> 
    /// Adds a message handler for the given message number. 
    /// </summary> 
    /// <param name="messageNumber"> 
    /// The message number. 
    /// </param> 
    /// <param name="messageHandler"> 
    /// The message handler. 
    /// </param> 
    public void AddMessageHandler(
     int messageNumber, 
     MessageHandler messageHandler) 
    { 
     lock (this.messageHandlers) 
     { 
      if (this.messageHandlers.ContainsKey(messageNumber)) 
      { 
       this.messageHandlers[messageNumber] += messageHandler; 
      } 
      else 
      { 
       this.messageHandlers.Add(
         messageNumber, (MessageHandler)messageHandler.Clone()); 
      } 
     } 
    } 

    /// <summary> 
    /// Processes the window messages. 
    /// </summary> 
    /// <param name="m"> 
    /// The m. 
    /// </param> 
    protected override void WndProc(ref Message m) 
    { 
     if (m.Msg == this.runOnUiThreadWindowsMessageNumber && m.Msg != 0) 
     { 
      for (;;) 
      { 
       MethodArgs ma; 
       lock (this.queue) 
       { 
        if (!this.queue.Any()) 
        { 
         break; 
        } 

        ma = this.queue.Dequeue(); 
       } 

       ma.Method.DynamicInvoke(ma.Args); 
      } 

      return; 
     } 

     int messageNumber = m.Msg; 
     MessageHandler mh; 
     if (this.messageHandlers.TryGetValue(messageNumber, out mh)) 
     { 
      if (mh != null) 
      { 
       foreach (MessageHandler cb in mh.GetInvocationList()) 
       { 
        try 
        { 
         // if WM_DESTROY (messageNumber == 2), 
         // ignore return value 
         if (cb(this, ref m) && messageNumber != 2) 
         { 
          return; // done processing 
         } 
        } 
        catch (Exception ex) 
        { 
         Debug.WriteLine(string.Format("{0}", ex)); 
        } 
       } 
      } 
     } 

     base.WndProc(ref m); 
    } 

    /// <summary> 
    /// Empty any existing backlog of things to run on the user interface 
    /// thread. 
    /// </summary> 
    private void EmptyUiBacklog() 
    { 
     // Check to see if there is a backlog of 
     // methods to run on the UI thread. If there 
     // is than notify the UI thread about them. 
     bool haveBacklog; 
     lock (this.queue) 
     { 
      haveBacklog = this.queue.Any(); 
     } 

     if (haveBacklog) 
     { 
      Win32.PostMessage(
        new HandleRef(this, this.Handle), 
        this.runOnUiThreadWindowsMessageNumber, 
        IntPtr.Zero, 
        IntPtr.Zero); 
     } 
    } 

    /// <summary> 
    /// Holds a method and its arguments. 
    /// </summary> 
    private class MethodArgs 
    { 
     /// <summary> 
     /// Gets or sets the method arguments. 
     /// </summary> 
     public object[] Args { get; set; } 

     /// <summary> 
     /// Gets or sets Method. 
     /// </summary> 
     public Delegate Method { get; set; } 
    } 
} 

Lý do chính của đoạn mã trên là để có được tại BeginInvoke() cuộc gọi được thực hiện trong phạm vi - bạn cần có cuộc gọi đến tạo các biểu mẫu của riêng bạn trên thread GUI.Tuy nhiên, bạn cần phải có một cửa sổ xử lý trước khi bạn có thể có một cuộc gọi trở lại trên thread GUI. Điều dễ nhất là để có mã C++ vượt qua cửa sổ xử lý trong (đến như là một IntPtr) nhưng bạn cũng có thể sử dụng một cái gì đó như:

Process. GetCurrentProcess(). MainWindowHandle;

để xử lý cửa sổ chính ngay cả khi bạn đang ở trong C# được gọi từ C++. Lưu ý rằng mã C++ có thể thay đổi tay cầm cửa sổ chính và để mã C# với mã không hợp lệ (dĩ nhiên, điều này có thể được ghi lại bằng cách lắng nghe các thông điệp cửa sổ thích hợp trên tay cầm ban đầu - bạn cũng có thể làm điều này với đoạn mã trên).

Rất tiếc nhưng các khai báo mà Win32 gọi ở trên không được hiển thị. Bạn có thể nhận được các khai báo P/Invoke cho chúng bằng cách tìm kiếm trên web. (Lớp Win32 của tôi là khổng lồ.)

Theo như callbacks vào C++ đi - miễn là bạn thực hiện callbacks khá đơn giản, bạn có thể sử dụng Marshal.GetDelegateForFunctionPointer để chuyển đổi một trôi qua trong con trỏ hàm (mà được biến thành một IntPtr) vào một đại biểu C# cũ.

Vì vậy, ít nhất gọi lại cho C++ là khá dễ dàng (miễn là bạn nhận được khai báo ủy nhiệm được xác định đúng). Ví dụ nếu bạn có một ++ chức năng C mà phải mất một const char * và trả về void, tuyên bố đại diện của bạn sẽ giống như thế:

public delegate void MyCallback([MarshalAs(UnmanagedType.LPStr)] string myText); 

Đó bao gồm những điều cơ bản. Sử dụng lớp ở trên có trình xử lý cửa sổ được chuyển vào để tạo cửa sổ dựa trên biểu mẫu của riêng bạn trong các cuộc gọi NativeWindowWithCallbacks.BeginInvoke(). Bây giờ, nếu bạn muốn chơi với mã cửa sổ C++, để nói, thêm một mục trình đơn trong một cửa sổ mã C++ quản lý, mọi thứ lại trở nên phức tạp hơn. Mã điều khiển .Net không thích giao tiếp với bất kỳ cửa sổ nào mà nó không tạo ra. Vì vậy, để thêm một mục trình đơn, bạn sẽ kết thúc viết mã với rất nhiều Win32 P/Invokes để thực hiện các cuộc gọi giống hệt nhau mà bạn sẽ làm nếu bạn viết mã C. Các lớp trên NativeWindowWithCallbacks sẽ có ích trở lại.

0

Nếu bạn muốn tải bất kỳ tệp DLL .NET nào trong ứng dụng C++ thì bạn phải lưu trữ .NET trong ứng dụng C++ của bạn.

Bạn có thể tìm thấy ví dụ từ Microsoft tại đây: https://code.msdn.microsoft.com/CppHostCLR-e6581ee0 Ví dụ đó cũng bao gồm một số tệp tiêu đề bắt buộc.

Nói tóm lại bạn cần phải làm như sau:

  1. Nạp mscoree.dll cách sử dụng lệnh LoadLibrary (nếu không bạn có thể liên kết các tĩnh mscoree.dll vào dự án của bạn)
  2. Gọi chức năng CLRCreateInstance, được xuất bởi mscoree.dll, để tạo đối tượng ICLRMetaHost
  3. Gọi phương thức GetRuntime của đối tượng ICLRMetaHost, để nhận đối tượng ICLRRuntimeInfo của phiên bản .NET ưa thích của bạn.
  4. Kiểm tra xem phiên bản có thể gọi là ICLRRuntimeInfo không.IsLoadable
  5. Gọi phương pháp GetInterface từ ICLRRuntimeInfo để có được những ICorRuntimeHost
  6. Gọi phương pháp Bắt đầu của đối tượng ICorRuntimeHost
  7. Gọi phương pháp GetDefaultDomain từ đối tượng ICorRuntimeHost để geht đối tượng IAppDomain

Sau đó, bạn có thể tải thư viện bằng cách sử dụng IAppDomain.Load_2. Nếu bạn muốn tải .NET DLLs từ mạng chia sẻ nó phức tạp hơn, bởi vì bạn cần gọi UnsafeLoadFrom, vốn không có sẵn trong IAppDomain. Nhưng điều này cũng có thể.

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