21
38

More than 1 year has passed since last update.

【C++/C#】C++で作成したDLLをC#で呼ぶ

Last updated at Posted at 2019-04-01

もくじ
https://tera1707.com/entry/2022/02/06/144447

やりたいこと

C++で作ったDLLを、C#で使いたい。

参考

呼び出し規約の種類(cdeclとか、stdcallとか)
https://konuma.org/blog/2006/01/02/post_1fd3/

extern "C" とは?
https://konuma.org/blog/2006/01/04/post_144e/

DLLを静的リンクで呼び出す
http://yamatyuu.net/computer/program/sdk/base/static_dll/index.html

C++側(呼ばれる側=DLL)

ヘッダーファイル

__declspec(dllexport) と __declspec(dllimport)

マクロを使用して、エクスポートする側(関数の実装を書いているcppにインクルードするとき)には「declspec(dllexport)」を書く。
インポートする側(dllを使う側のcppでインクルードするとき)には、「declspec(dllimport)」を書く。

→詳細は、下記を参照
https://konuma.org/blog/2006/01/02/post_1fd3/

extern "C"

通常、DLLを作成するときは、C形式のDLLとして作成する。
C形式:マングリングが行われない
C++形式:マングリングが行われる

→詳細は、下記を参照
https://konuma.org/blog/2006/01/04/post_144e/

DllTest.h
#include <Windows.h>
#include <string.h>

// エクスポートとインポートの切り替え
#ifdef VC_DLL_EXPORTS
#undef VC_DLL_EXPORTS
#define VC_DLL_EXPORTS extern "C" __declspec(dllexport)
#else
#define VC_DLL_EXPORTS extern "C" __declspec(dllimport)
#endif

// エクスポート関数のプロトタイプ宣言
// 通常、__stdcallを適用する(__stdcall = WINAPI)。
VC_DLL_EXPORTS void __cdecl Test_MyApi();
VC_DLL_EXPORTS void __cdecl Test_MyApi2(const wchar_t* lpText, const wchar_t* lpCaption);
VC_DLL_EXPORTS void __cdecl Test_MyApi3(int count);

cppファイル

DllTest.h
#include "stdafx.h"
#include <windows.h>
#include <stdio.h>

#define VC_DLL_EXPORTS
#include "DllTest.h"

// エクスポート関数の実装
void __cdecl Test_MyApi()
{
    const wchar_t* lpText = L"Test\0";
    wprintf_s(lpText);
}

void __cdecl Test_MyApi2(const wchar_t* lpText, const wchar_t* lpCaption)
{
    // MessageBoxを呼び出すだけ。
    wprintf_s(L"%s", lpText);
    wprintf_s(L"%s", L" ");
    wprintf_s(L"%s", lpCaption);
}

void __cdecl Test_MyApi3(int count)
{
    for (int i = 0; i < count; i++)
    {
        wprintf_s(L"%d,", i);
    }
}

C#側(呼ぶ側)

program.cs
using System.Runtime.InteropServices;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            NativeMethods.Test_MyApi(1);

            NativeMethods.Test_MyApi2("テキスト", "キャプション");

            NativeMethods.Test_MyApi3(3);
        }
    }

    public static class NativeMethods
    {
        [DllImport("DllTest.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
        public extern static void Test_MyApi(int param);

        [DllImport("DllTest.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
        public extern static void Test_MyApi2(string lpText, string lpCaption);

        // 下記でも、うまく動く。(CharSetの代わりにMarshallAsでLPWSTRを指定する)
        //[DllImport("DllTest.dll", CallingConvention = CallingConvention.Cdecl)]
        //public extern static void Test_MyApi2([MarshalAs(UnmanagedType.LPWStr)]string lpText, [MarshalAs(UnmanagedType.LPWStr)]string lpCaption);

        [DllImport("DllTest.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
        public extern static void Test_MyApi3(int count);
    }
}

不明点

  • C#側で、C++の関数に文字列を渡すとき、stringで渡してwchar_tで受けてるが、本当にこれでOK?(C#のstringは、C++の何の型にあたる??)

→albireo様にコメント頂きました。
C++の文字列変数は「メモリ上のアドレス」を指している。
C#の文字列はマネージドメモリ上を参照しているため、特定のメモリアドレスを持っていない。
C++側に文字列型を渡すときは、「マーシャリング」という変換処理を自動で行う。

「[MarshalAs(UnmanagedType.LPWStr)]」などで、マーシャリングの方法をコンパイラに指定する。

コード

21
38
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
21
38