今日、私は(おそらく、他の多くのプログラマーのように).NETプラットフォームとC#言語をますます使用していますが、それでもC ++の使用が正当化されるコーナーがあります。 これにより、統合が必要になります。
なんで?
- C ++では、パフォーマンスが重要なアルゴリズムを作成することをお勧めします
- C ++では、アプリケーションのセキュリティに関連する部分を作成することをお勧めします
- 古いコードの多くはC ++で書かれており、すべてを書き直すことは最良の解決策ではありません。
これらは主な理由に過ぎず、リストは完全ではありません。 しかし、必要がある場合は、解決策があります。
habracatの前に、この記事では、このような相互作用の古典的な方法ではなく、多くの実際のアプリケーションで非常に役立つ可能性のある方法について説明します。
どうやって?
これらのソリューションにはそれぞれ長所と短所があります。 この記事の目的は、これらのアプローチのすべての利点と欠点の概要を説明することではありませんでしたが、簡単に触れます。
P / Invoke-非常にシンプルですが、柔軟性はありません。 OOPのサポートはありません。一度に多くのメソッドを作成するのは便利ではありません。
COMは便利で非常に強力ですが、COMライブラリにはシステムレジストリへの登録が必要です。さらに、インストールには管理者権限が必要です(UPD:コメントに正しく記載されているように、不要です)。
C ++ / CLIは非常に緊密な統合であり、非常に優れたソリューションです。 ただし、/ CLIキーを使用してC ++コードをコンパイルできない場合は、追加の中間C ++ / CLIライブラリを入力し、その中の各呼び出しのラッパーを記述する必要があります。 さらに、CLIはコードを複雑にすることが多く、いくつかの制限があります。
他にどう?
この記事では、COM Interop + P / Invokeに基づく別の興味深い非常にエレガントなソリューションについて説明します。これにより、システムでのCOM登録を必要とせずに、C#からC ++クラスを簡単に呼び出すことができます(たとえば、プログラムをポータブルに起動できます。フラッシュドライブから)。 これを行うには、仮想メソッドを使用して最も一般的なC ++クラスを作成し、COMインターフェイスとしてP / Invokeを介してC#に渡します。
ビジネスに取り掛かろう。
2つのプロジェクトを作成します。 1つはC ++です(CLRとMFCを使用せずにUnicodeを有効にします)。 たとえば、Libと呼ばれます。
(注意:デバッグを容易にし、さらにコピーする必要がないように、出力ライブラリはWindowsフォルダーに移動します)。
その後、C#プロジェクト-Windows Formsを作成します。 net2cと呼びましょう。 テストフォームを収集します。
インターフェースからは、C ++ライブラリがほぼ何をするかはすでに明らかだと思います。
次に、C ++プロジェクトに戻り、オブジェクトを作成する1つの関数を記述します。
//
enum EObjectType
{
Hello = 0,
GraphicAlgorithm
};
__declspec(dllexport) void * __stdcall Create(EObjectType AType)
{
if (AType == Hello) return new CHello();
if (AType == GraphicAlgorithm) return new CGraphicAlgorithm();
return NULL;
}
エクスポートされた関数の名前が装飾されないようにするには、別の.DEFファイルを作成して書き込む必要があります。
EXPORTS
Create
別の方法として、必要なオブジェクトを作成するために、この関数によって返される個別の単一クラスを作成できますが、最も簡単な方法で行います。
次に、CHelloクラスのコード:
//
// {08356CFE-A3DD-43c2-980C-1393E37118B2}
static const GUID IID_IHello =
{ 0x8356cfe, 0xa3dd, 0x43c2, { 0x98, 0xc, 0x13, 0x93, 0xe3, 0x71, 0x18, 0xb2 } };
class IHello : public CBaseUnknown
{
public :
STDHRESULTMETHOD SetName(LPWSTR AName) = 0;
STDHRESULTMETHOD Say(HWND AParent) = 0;
};
class CHello :
public IHello
{
public :
STDHRESULTMETHOD QueryInterface( const CLSID &AId, void ** ARet)
{
__super::QueryInterface(AId, ARet);
if (AId == IID_IHello) *ARet = (IHello*) this ;
return (*ARet != NULL) ? S_OK : E_NOINTERFACE;
}
STDHRESULTMETHOD SetName(LPWSTR AName)
{
mName = AName;
return S_OK;
}
STDHRESULTMETHOD Say(HWND AParent)
{
wstring message = L "Hello, my friend " + mName;
MessageBox(AParent, message.c_str(), L "From C++" , MB_ICONINFORMATION);
return S_OK;
}
private :
wstring mName;
};
ご覧のとおり、すべてが非常に簡単です。 このクラスは単一のIHelloインターフェイスをサポートし、基本メソッドのペアを実装します。
次に、楽しい部分として、C#プロジェクトに移動して次のように記述します。
// enum C#
public enum EObjectType : int
{
Hello = 0,
GraphicAlgorithm
}
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
Guid ( "08356CFE-A3DD-43c2-980C-1393E37118B2" )]
public interface IHello
{
void SetName([MarshalAs(UnmanagedType.LPWStr)] string AName);
void Say( IntPtr AParent);
}
public class Lib
{
//
[DllImport( "Lib.dll" )]
protected static extern IntPtr Create(EObjectType AType);
//
public static object CreateObject(EObjectType AType)
{
IntPtr ptr = Create(AType);
return Marshal.GetObjectForIUnknown(ptr);
}
}
繰り返しますが、おそらく
Marshal.GetObjectForIUnknownを除き、複雑なことはありません。 この関数は、COMオブジェクトを指すIntPtrを受け取り、そのための
System.Objectを返します。 それとは別に、
IHelloインターフェイスがどのように
宣言されているかに注意を
向けます。 ここでは、これがCOMインターフェイスであることをコンパイラに伝え、C ++プログラムと同じGUIDを記述します。
それだけです! これで、
CHelloクラスのC ++メソッドをC#から問題なく呼び出すことができ、その背後に邪悪で怖いC ++があることすら覚えていません。 自分で見てください:
object hiObject = Lib.CreateObject(EObjectType.Hello);
IHello hello = hiObject as IHello;
hello.SetName(txtName.Text);
hello.Say(Handle);
(すべてが本当に機能します)
デザート用
次に、2番目のボーナス部分は、いくつかの単純なグラフィカルアルゴリズムです。 これはより現実的な例です。 大きな画像を処理するときは、アルゴリズムコードをC ++ライブラリに転送することをお勧めします。
C ++ファイル
STDMETHODIMP CGraphicAlgorithm::QueryInterface( const CLSID &AId, void ** ARet)
{
__super::QueryInterface(AId, ARet);
if (AId == IID_IGraphicAlgorithm) *ARet = (CBaseUnknown*) this ;
return (*ARet != NULL) ? S_OK : E_NOINTERFACE;
}
STDMETHODIMP CGraphicAlgorithm::MakeGrayScale( void * APointer, int AWidth, int AHeight)
{
RGBQUAD *p = (RGBQUAD*)APointer;
for ( int y = 0; y < AHeight; y++)
{
for ( int x = 0; x < AWidth; x++)
{
// :)
short mid = (( short )p->rgbBlue + p->rgbGreen + p->rgbRed) / 3;
if (mid > 255) mid = 255;
BYTE v = (BYTE)mid;
memset(p, v, 3);
p++;
}
}
return S_OK;
}
STDMETHODIMP CGraphicAlgorithm::MakeAlpha( void * APointer, int AWidth, int AHeight)
{
RGBQUAD *p = (RGBQUAD*)APointer;
for ( int y = 0; y < AHeight; y++)
{
for ( int x = 0; x < AWidth; x++)
{
// , :)
memset(p, p->rgbReserved, 4);
p++;
}
}
return S_OK;
}
Hファイル
// {65ACBBC0-45D2-4622-A779-E67ED41D2F26}
static const GUID IID_IGraphicAlgorithm =
{ 0x65acbbc0, 0x45d2, 0x4622, { 0xa7, 0x79, 0xe6, 0x7e, 0xd4, 0x1d, 0x2f, 0x26 } };
class CGraphicAlgorithm : CBaseUnknown
{
public :
STDHRESULTMETHOD MakeGrayScale( void * APointer, int AWidth, int AHeight);
STDHRESULTMETHOD MakeAlpha( void * APointer, int AWidth, int AHeight);
STDHRESULTMETHOD QueryInterface( const CLSID &AId, void ** ARet);
};
それとは別に、
IID_IGraphicAlgorithmが存在するという事実に注意を
向けますが、
IGraphicAlgorithmインターフェイス
自体はそうではありません。 どうして? これは、お客様との作業をさらに簡素化し、さらに少ないコードを書くために意図的に行われます。 ここで考慮すべき重要なことは、
IGraphicAlgorithmインターフェイスに属するすべての仮想メソッドがクラスの最初に厳密な順序で配置される必要があることです。 さらに、クラスは複数の異なるインターフェイスを提供できなくなります(必要ないため、別のクラスを作成する方が便利で論理的です)。
sharpeに戻り、次のコードを記述します。
IGraphicAlgorithm utils = Lib.CreateObject(EObjectType.GraphicAlgorithm) as IGraphicAlgorithm;
BitmapData data = bmp.LockBits( new Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.ReadWrite, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
if (optGray.Checked)
utils.MakeGrayScale(data.Scan0, data.Width, data.Height);
else
utils.MakeAlpha(data.Scan0, data.Width, data.Height);
bmp.UnlockBits(data);
picDest.Image = bmp;
picSrc.Refresh();
picDest.Refresh();
ここでは、まずビットマップデータが保存されているメモリへのポインターを取得し、次にこのポインターをC ++ライブラリに渡してさらに処理します。 これは最も効果的で便利な方法ではないことをすぐに警告しますが、トピックに興味がある場合は、別の記事でメモリ内の画像を直接操作する方法について説明できます。
その結果:
まとめ
私たちが得たものを見てください:
- オブジェクトの作成を確実にするために、最初にいくつかのコードを記述する必要があります
- さらに、新しいクラスを追加するには、1つのGUIDを生成し、C#インターフェイスのC ++クラスのメソッドの記述を複製するだけで十分です。
- 新しいメソッドを追加するには、C ++およびC#で一度だけ指定します
- オブジェクトの削除について心配することはできません。 基本クラスCBaseUnknownに参照カウンターがある場合、ガベージコレクターはすべてを行います
- 両方のプロジェクトが1つのソリューションに含まれている場合、完全に完全なデバッグ
- ライブラリを機能させるには、プログラムと同じフォルダにドロップしてください。 非常にシンプルで便利。
PS完全なソースコードはここからダウンロードできます:
http :
//66bit.ru/files/paper/net2c/net2c.zip
PPS次の記事は、逆問題-C ++でのC#ライブラリの直接使用についてです。
UPD。 パート2リリース