web-dev-qa-db-ja.com

ネイティブC ++からC#ライブラリを呼び出す方法(C ++ \ CLIおよびIJWを使用)

背景:より大きな課題の一部として、アンマネージC++およびCコードからアクセス可能なC#ライブラリを作成する必要があります。この質問に自分で答えようとして、私は過去数日/数週間C++/CLIを学んでいます。

アンマネージC++およびCのC#dllを使用して達成するためのさまざまな方法があるようです。簡単な答えのいくつかは、Interlopeサービスの使用、.comの使用です。そして、regasm、PInvokeの使用(C#からC++のみに移動するように見える)、C++/CLRでIJWの使用(Interlopeサービスのように見える)。おそらく、ネイティブC++およびCコードの代わりにIJWを使用してC#dllを呼び出すCLRラッパーであるライブラリをセットアップするのが最善だと考えています。

詳細:C++コードからC#dllに文字列とintの値を渡し、voidを返す必要があります。

関連性:多くの企業には、C++、C、C#を組み合わせて一致させる多くの言い訳があります。パフォーマンス:通常、アンマネージコードはより高速で、インターフェイス:マネージインターフェイスは一般に保守、展開が容易であり、多くの場合目には簡単です、とマネージャーも語っています。レガシーコードも私たちを強制します。そこにありました(私たちが登った山のように)。 C#からC++ライブラリを呼び出す方法の例は豊富です。 C++コードからC#ライブラリを呼び出す方法の例は、特に更新された4.0+コードを表示したい場合、グーグルで見つけるのは困難です。

ソフトウェア:C#、C++/CLR、C++、C、Visual Studio 2010、および.NET 4.0

質問の詳細:OKマルチパート質問:

  1. Comオブジェクトを使用する利点はありますか?またはPInvoke?または他の方法? (Google Landでこのトピックに関する詳細な情報を見つけたとしても、ここでの学習曲線は同じように急になると思います。IJWは、私がやりたいことを約束しているようです。代わりにこれに集中しますか?)(利点/欠点?)

  2. C++/CLRでIJWを使用するラッパーを作成するソリューションがあることを想像して正しいですか?このトピックに関する詳細情報はどこで入手できますか。また、Googleが十分ではなかったと言ったり、MSDNを参照したりせずに、MSDNを見てください。 (明確でシンプルなコードを書くために、このオプションを好むと思います。)

  3. 質問の範囲の狭まり:私の本当の問題と必要性は、次の小さな質問に答えていると感じています:アンマネージC++ファイルがVisual Studio内で使用できるC++/CLRライブラリを設定するにはどうすればよいですか。アンマネージC++コードでマネージC++クラスを単純にインスタンス化できれば、残りの部分(インターフェイスやラッピングなど)を解決できると思います。私の主な愚かさは、Visual Studio内で参照/#includesなどを設定しようとすることです。他の誤解があるのは明らかだと思いました。おそらく、この全体に対する答えは、これに役立つチュートリアルまたは指示へのリンクだけかもしれません。

研究:何度か何度もGoogledとBingをして成功しました。 C#コードからアンマネージライブラリを使用する方法を示す多くのリンクを見つけました。そして、comオブジェクトを使用してそれを行う方法を示すリンクがいくつかあったことを認めます。 VS 2010を対象とした結果は多くありませんでした。

参考文献:私は何度も何度も記事を読みました。私は最も関連性の高いものを試してみました。答えに興味をそそる人もいますが、うまくいかないようです。私は、キーワードrefの誤用、#includeまたはusingステートメントの欠落、名前空間の誤用、IJW機能を実際に使用していない、VSコンパイルなどを正しく処理する必要があります。コードを含めないのはなぜでしょうか。まあ、私は自分が動作しなければならないコードを理解し期待している場所にいないように感じます。私はそれを理解できる場所にいたい、そこに着いたら、それを修正するのに助けが必要でしょう。 2つのリンクをランダムに含めますが、現在のHitpointレベルですべてを表示することは許可されていません。

http://www.codeproject.com/Articles/35437/Moving-Data-between-Managed-Code-and-Unmanaged-Cod

これは、マネージコードとアンマネージコードから、C++からVisual Basicへ、およびC++ CLRを介して双方向にコードを呼び出します。もちろん、C#に興味があります。 http://www.codeproject.com/Articles/9903/Calling-Managed-Code-from-Unmanaged-Code-and-vice

43
amalgamate

少なくとも自分の質問に答え始めるものを見つけました。次の2つのリンクには、Microsoftのwmvファイルがあり、アンマネージC++でC#クラスを使用する方法を示しています。

この最初のものは、COMオブジェクトとregasmを使用します: http://msdn.Microsoft.com/en-us/vstudio/bb892741

2番目の例は、C++/CLIの機能を使用してC#クラスをラップします: http://msdn.Microsoft.com/en-us/vstudio/bb892742 。マネージコードからc#クラスをインスタンス化し、ビデオのように文字列を取得できました。これは非常に役立ちましたが、文字列の境界を持つクラスをc#クラスにインスタンス化するため、質問の2/3にしか答えません。概念実証として、次のメソッドの例で示されているコードを変更し、この目標を達成しました。もちろん、{public string PickDate(string Name)}メソッドを変更して、名前の文字列を使用して、それが機能していることを証明しました。

wchar_t * DatePickerClient::pick(std::wstring nme)
{
    IntPtr temp(ref);// system int pointer from a native int
    String ^date;// tracking handle to a string (managed)
    String ^name;// tracking handle to a string (managed)
    name = gcnew String(nme.c_str());
    wchar_t *ret;// pointer to a c++ string
    GCHandle gch;// garbage collector handle
    DatePicker::DatePicker ^obj;// reference the c# object with tracking handle(^)
    gch = static_cast<GCHandle>(temp);// converted from the int pointer 
    obj = static_cast<DatePicker::DatePicker ^>(gch.Target);
    date = obj->PickDate(name);
    ret = new wchar_t[date->Length +1];
    interior_ptr<const wchar_t> p1 = PtrToStringChars(date);// clr pointer that acts like pointer
    pin_ptr<const wchar_t> p2 = p1;// pin the pointer to a location as clr pointers move around in memory but c++ does not know about that.
    wcscpy_s(ret, date->Length +1, p2);
    return ret;
}

私の質問の一部は次のとおりでした:何が良いですか?私が答えを研究するための多くの努力で読んだことから、COMオブジェクトは使いやすいと考えられており、代わりにラッパーを使用することでより優れた制御が可能になります。 COMオブジェクトは自動的に標準サイズのフットプリントを持ち、ラッパーは必要な大きさしかないため、ラッパーを使用するとサンクのサイズを小さくすることができます(常にではありません)。

サンク(上で使用したように)は、COMオブジェクトの場合はC#とC++の間、C++/CLIをコーディングして使用する場合はC++/CLIとネイティブC++の間で使用される時空とリソースを指しますラッパー。したがって、私の答えの別の部分には、サンク境界を絶対に必要以上に横断することは悪い習慣であり、ループ内でサンク境界にアクセスすることは推奨されず、ラッパーを誤って設定してサンクを二重にする可能性があるという警告を含める必要があります(1つのサンクだけが呼び出される境界を2回越えます)私のような初心者にはコードが間違っているように見えません。

Wmvについての2つのメモ。最初に、いくつかの映像が両方で再利用されます。だまされないでください。最初は同じように見えますが、異なるトピックを扱っています。次に、マーシャリングなどのボーナス機能がありますが、これは現在wmvではカバーされていないCLIの一部です。

編集:

インストールに影響があることに注意してください。C++ラッパーはCLRによって検出されません。 c ++アプリケーションがそれを使用する任意の/すべてのディレクトリにインストールすることを確認するか、インストール時にライブラリ(厳密に名前を付ける必要があります)をGACに追加する必要があります。これは、開発環境のどちらの場合でも、アプリケーションを呼び出す各ディレクトリにライブラリをコピーする必要があることを意味します。

4
amalgamate

これはかなり簡単に行えます。

  1. .h/.cppコンボを作成する
  2. 新しく作成する.cppファイルで/ clrを有効にします。 (CPP->右クリック->プロパティ)
  3. 「追加#usingディレクトリ」の検索パスを設定して、C#dllを指すようにします。

Native.h

void NativeWrapMethod();

Native.cpp

#using <mscorlib.dll>
#using <MyNet.dll>

using namespace MyNetNameSpace;

void NativeWrapMethod()
{
    MyNetNameSpace::MyManagedClass::Method(); // static method
}

これが、ネイティブコードでC++\CLIのC#ライブラリを使用する基本です。 (必要に応じてNative.hを参照し、関数を呼び出します。)

マネージC++\CLIコードでC#コードを使用する場合もほぼ同じです。

この件に関しては多くの誤報がありますので、これにより誰かの手間が省けることを願っています。 :)


私はこれをVS2010で行ってきました-VS2012(おそらくVS2008でも動作します。)

33
Smoke

2018年更新

このソリューションは、Visual Studio 2017以降では機能しないようです。残念ながら、現在Visual Studioを使用していないため、この回答を自分で更新することはできません。しかし、 kaylee は私の更新バージョンを投稿しました answer 、ありがとう!

UPDATE END

COMを使用する場合、この問題に対する私の解決策は次のとおりです。

C#ライブラリ

まず、COM互換ライブラリが必要です。

  • もう持ってる?完璧です。この部分はスキップできます。

  • ライブラリにアクセスできますか?手順に従って、COM互換であることを確認してください。

    1. プロジェクトのプロパティで[COM相互運用機能に登録]オプションをチェックしたことを確認してください。プロパティ->ビルド->スクロールダウン-> COM相互運用機能に登録

次のスクリーンショットは、このオプションの場所を示しています。

Screenshot Project Properties Build

  1. 使用できるはずのすべてのインターフェースとクラスには、 [〜#〜] guid [〜#〜] が必要です。

    namespace NamespaceOfYourProject
    {
        [Guid("add a GUID here")]
        public interface IInterface
        {
            void Connect();
    
            void Disconnect();
        }
    }
    
    namespace NamespaceOfYourProject
    {
         [Guid("add a GUID here")]
         public class ClassYouWantToUse: IInterface
         {
             private bool connected;
    
             public void Connect()
             {
                 //add code here
             }
    
             public void Disconnect()
             {
                 //add code here
             }
         }
    }
    

だから、C#コードでやらなければならないことはほとんどあります。 C++コードを続けましょう。

C++

  1. まず、C#ライブラリをインポートする必要があります。

C#ライブラリをコンパイルすると、.tlbファイルが作成されます。

#import "path\to\the\file.tlb"

この新しく作成したファイルをfile.cppにインポートすると、オブジェクトをローカル変数として使用できます。

#import "path\to\the\file.tlb"

int _tmain(int argc, _TCHAR* argv[])
{
    CoInitialize(NULL);

    NamespaceOfYourProject::IInterfacePtr yourClass(__uuidof(NamespaceOfYourProject::ClassYouWantToUse));

    yourClass->Connect();

    CoUninitialize();
}
  1. クラスを属性として使用します。

最初のステップはローカル変数でのみ機能することに気付くでしょう。次のコードは、属性として使用する方法を示しています。 this 質問に関連しています。

Atlcomcli.hにあるCComPtrが必要です。このファイルをヘッダーファイルに含めます。

CPlusPlusClass.h

#include <atlcomcli.h> 
#import "path\to\the\file.tlb"

class CPlusPlusClass
{
public:
    CPlusPlusClass(void);
    ~CPlusPlusClass(void);
    void Connect(void);

private:
    CComPtr<NamespaceOfYourProject::IInterface> yourClass;
}

CPlusPlusClass.cpp

CPlusPlusClass::CPlusPlusClass(void)
{
    CoInitialize(NULL);

    yourClass.CoCreateInstance(__uuidof(NamespaceOfYourProject::ClassYouWantToUse));

}

CPlusPlusClass::~CPlusPlusClass(void)
{
    CoUninitialize();
}

void CPlusPlusClass::Connect(void)
{
    yourClass->Connect();
}

それでおしまい! COMを使用したC++でC#クラスをお楽しみください。

20
0lli.rocks

これを実現するために私が見つけた絶対的な最良の方法は、c#コードをネイティブC++に接続するc ++/cliブリッジを作成することです。 COMオブジェクトを使用して問題を解決することはお勧めしません。 3つの異なるプロジェクトでこれを行うことができます。

  • 最初のプロジェクト:C#ライブラリ
  • 2番目のプロジェクト:C++/CLIブリッジ(これはC#ライブラリをラップします)
  • 3番目のプロジェクト:2番目のプロジェクトを使用するネイティブC++アプリケーション

これに関する優れた視覚的/チュートリアルは here にあり、これを行うために私が見つけた最良の完全なチュートリアルは here !これらの2つのリンクと小さなグリットの間で、ネイティブC++でC#コードを使用できるC++/CLIブリッジを作成することができます。

8
jsmith

lli.rocksからの回答 は、残念ながら古いか不完全です。私の同僚は私がこれを機能させるのを手伝ってくれました。この回答はギャップを修正するものであり、独自の使用のためにVisual Studio 2017に直接コピーできる必要があります。

警告:私はこれをC++/WinRTで機能させることができませんでした。 IUnknownインターフェイスのあいまいさによるあらゆる種類のコンパイルエラー。また、これをアプリのメインで使用するのではなく、ライブラリの実装だけで機能させるのに問題がありました。具体的には、0lli.rocksの指示に従ってみましたが、コンパイルできませんでした。

ステップ01:C#ライブラリを作成する


デモに使用するものは次のとおりです。

_using System;
using System.Runtime.InteropServices;

namespace MyCSharpClass
{
    [ComVisible(true)] // Don't forget 
    [ClassInterface(ClassInterfaceType.AutoDual)] // these two lines
    [Guid("485B98AF-53D4-4148-B2BD-CC3920BF0ADF")] // or this GUID
    public class TheClass
    {
        public String GetTheThing(String arg) // Make sure this is public
        {
            return arg + "the thing";
        }
    }
}
_

ステップ02-COM可視性のためにC#ライブラリを構成する


サブステップA-COM相互運用性の登録

enter image description here

サブステップB-アセンブリをCOMから見えるようにする

enter image description here

ステップ3-_.tlb_ファイル用のライブラリを構築する


より具体的なものが本当に必要な場合を除いて、おそらくReleaseAnyCPUとしてこれを実行したいでしょう。

ステップ4-_.tlb_ファイルをC++プロジェクトのソースの場所にコピーします


enter image description here

ステップ5-_.tlb_ファイルをC++プロジェクトにインポートします


_#include "pch.h"
#include <iostream>
#include <Windows.h>
#import "MyCSharpClass.tlb" raw_interfaces_only

int wmain() {
    return 0;
}
_

ステップ6-Intellisenseが失敗してもパニックに陥らない


enter image description here

引き続きビルドされます。実際のクラスをC++プロジェクトに実装すると、さらに赤い線で囲まれたコードが表示されます。

ステップ7-C++プロジェクトをビルドして_.tlh_ファイルを生成します


このファイルは、初めてビルドすると中間オブジェクトのビルドディレクトリに格納されます

enter image description here

ステップ8-実装手順について_.tlh_ファイルを評価する


これは、中間オブジェクトフォルダーに生成される_.tlh_ファイルです。編集しないでください。

_// Created by Microsoft (R) C/C++ Compiler Version 14.15.26730.0 (333f2c26).
//
// c:\users\user name\source\repos\consoleapplication6\consoleapplication6\debug\mycsharpclass.tlh
//
// C++ source equivalent of Win32 type library MyCSharpClass.tlb
// compiler-generated file created 10/26/18 at 14:04:14 - DO NOT EDIT!

//
// Cross-referenced type libraries:
//
//  #import "C:\Windows\Microsoft.NET\Framework\v4.0.30319\mscorlib.tlb"
//

#pragma once
#pragma pack(Push, 8)

#include <comdef.h>

namespace MyCSharpClass {

//
// Forward references and typedefs
//

struct __declspec(uuid("48b51671-5200-4e47-8914-eb1bd0200267"))
/* LIBID */ __MyCSharpClass;
struct /* coclass */ TheClass;
struct __declspec(uuid("1ed1036e-c4ae-31c1-8846-5ac75029cb93"))
/* dual interface */ _TheClass;

//
// Smart pointer typedef declarations
//

_COM_SMARTPTR_TYPEDEF(_TheClass, __uuidof(_TheClass));

//
// Type library items
//

struct __declspec(uuid("485b98af-53d4-4148-b2bd-cc3920bf0adf"))
TheClass;
    // [ default ] interface _TheClass
    // interface _Object

struct __declspec(uuid("1ed1036e-c4ae-31c1-8846-5ac75029cb93"))
_TheClass : IDispatch
{
    //
    // Raw methods provided by interface
    //

      virtual HRESULT __stdcall get_ToString (
        /*[out,retval]*/ BSTR * pRetVal ) = 0;
      virtual HRESULT __stdcall Equals (
        /*[in]*/ VARIANT obj,
        /*[out,retval]*/ VARIANT_BOOL * pRetVal ) = 0;
      virtual HRESULT __stdcall GetHashCode (
        /*[out,retval]*/ long * pRetVal ) = 0;
      virtual HRESULT __stdcall GetType (
        /*[out,retval]*/ struct _Type * * pRetVal ) = 0;
      virtual HRESULT __stdcall GetTheThing (
        /*[in]*/ BSTR arg,
        /*[out,retval]*/ BSTR * pRetVal ) = 0;
};

} // namespace MyCSharpClass

#pragma pack(pop)
_

そのファイルには、使用するパブリックメソッドの次の行があります。

_virtual HRESULT __stdcall GetTheThing (
        /*[in]*/ BSTR arg,
        /*[out,retval]*/ BSTR * pRetVal ) = 0;
_

つまり、インポートされたメソッドは、タイプBSTRの入力文字列と、インポートされたメソッドが成功時に返す出力文字列のBSTRへのポインタを期待することを意味します。たとえば、次のように設定できます。

_BSTR thing_to_send = ::SysAllocString(L"My thing, or ... ");
BSTR returned_thing;
_

インポートされたメソッドを使用する前に、それを構築する必要があります。 _.tlh_ファイルから、次の行が表示されます。

_namespace MyCSharpClass {

//
// Forward references and typedefs
//

struct __declspec(uuid("48b51671-5200-4e47-8914-eb1bd0200267"))
/* LIBID */ __MyCSharpClass;
struct /* coclass */ TheClass;
struct __declspec(uuid("1ed1036e-c4ae-31c1-8846-5ac75029cb93"))
/* dual interface */ _TheClass;

//
// Smart pointer typedef declarations
//

_COM_SMARTPTR_TYPEDEF(_TheClass, __uuidof(_TheClass));

//
// Type library items
//

struct __declspec(uuid("485b98af-53d4-4148-b2bd-cc3920bf0adf"))
TheClass;
    // [ default ] interface _TheClass
    // interface _Object
_

最初に、MyCSharpClassであるクラスの名前空間を使用する必要があります

次に、名前空間からスマートポインターを決定する必要があります。これは__TheClass_ + Ptrです。このステップは、_.tlh_ファイル内のどこにも存在しないため、リモートで明らかではありません.

最後に、クラスの正しい構築パラメーターを提供する必要があります。これは__uuidof(MyCSharpClass::TheClass)です

で終わる、

_MyCSharpClass::_TheClassPtr obj(__uuidof(MyCSharpClass::TheClass));
_

手順9-COMの初期化とインポートされたライブラリのテスト


CoInitialize(0)または特定のCOMイニシャライザーが何であれ、それを行うことができます。

_#include "pch.h"
#include <iostream>
#include <Windows.h>
#import "MyCSharpClass.tlb" raw_interfaces_only

int wmain() {
    CoInitialize(0); // Init COM
    BSTR thing_to_send = ::SysAllocString(L"My thing, or ... ");
    BSTR returned_thing;
    MyCSharpClass::_TheClassPtr obj(__uuidof(MyCSharpClass::TheClass)); 
    HRESULT hResult = obj->GetTheThing(thing_to_send, &returned_thing);

    if (hResult == S_OK) {
        std::wcout << returned_thing << std::endl;
        return 0;
    }
    return 1;
}
_

繰り返しますが、Intellisenseがフリークしてもパニックに陥らないでください。あなたはブラックマジック、ブードゥー、およびタービードラゴンズの領土にいるので、先に押してください!

enter image description here

6

いろいろ調べてみると、Microsoftがそれをどのように行うことができるかを詳述した比較的最近の記事を見つけました(古い情報がたくさんあります)。記事自体から:

コードサンプルでは、​​CLR 4ホスティングAPIを使用して、ネイティブC++プロジェクトでCLRをホストし、.NETアセンブリをロードして呼び出します。

https://code.msdn.Microsoft.com/CppHostCLR-e6581ee

基本的には、2つのステップで説明します。

  1. CLRをプロセスに読み込む
  2. アセンブリをロードします。
0
gremwell