rainit2006 / C-Program

C# Program knowledge
0 stars 0 forks source link

DLLの作成,托管DLL和非托管DLL的区别, C# using C++ dll. P invoke, C++ Wrapper for C#, marshal #20

Open rainit2006 opened 7 years ago

rainit2006 commented 7 years ago

C++生成DLL http://hp.vector.co.jp/authors/VA019517/howtodll.html

  1. 新規作成>Win32 Dynamic-Linc Library
  2. 空のDLLプロジェクト を選択し、終了ボタンを押し
  3. 新規作成>C/C++ ソースファイル.
  4. プロジェクトへ追加にチェックが入ってることを確認して、ファイル名を適当に決めてOKボタンを押し

5, Writing code as below.

/////////////////////////////////////////////////////////////////////////////
// dlltest.cpp
#include <windows.h>

BOOL WINAPI DllMain( HINSTANCE hDLL, DWORD dwReason, LPVOID lpReserved)
{
    return TRUE;
}

LONG __stdcall SelectLarge(LONG A,LONG B)
{
    return (A > B) ? A : B;
}
/////////////////////////////////////////////////////////////////////////////

つぎはdefファイルです

/////////////////////////////////////////////////////////////////////////////
// dlltest.def
LIBRARY dlltest

EXPORTS
    SelectLarge
/////////////////////////////////////////////////////////////////////////////
  1. Win32 Releaseを選択してビルドして下さい. Releaseフォルダに dlltest.dll が出来ています。また、dlltest.lib と言うファイルも出来ています。
rainit2006 commented 7 years ago

C++里使用DLL: libファイル使用編 (静的ロード)~

  1. 新規作成>Win32 Console Applicationを選択し、プロジェクトを作ります。[dllread]というProjiect名にする。
  2. プロジェクトが有るフォルダにDLLとlibファイルをコピーしておきます。 3, Writing code as below
    
    /////////////////////////////////////////////////////////////////////////////
    // dllread.cpp
    #include <windows.h>
    #include <iostream.h>

LONG __stdcall SelectLarge(LONG A,LONG B);

void main(void) { int a = SelectLarge(53,72); cout << a; cin >> a; } /////////////////////////////////////////////////////////////////////////////



3. Win32 Release を選択後、
すぐに「プロジェクト>設定>リンク」の所に、
「オブジェクト/ライブラリ モジュール」と言う覧があります。そこに
kernel32.lib user32.lib gdi32.lib 等々と有りますが、先頭にdlltest.libを付け足してください。

それでビルド・実行できる。
rainit2006 commented 7 years ago

C++里使用DLL:動的にDLLを読み込む方法~

  1. まずはプロジェクトを新規作成してください。ここでは dllreadnew にします。 2.ソースファイルを追加です。これは dllreadnaw.cpp にします。
    
    /////////////////////////////////////////////////////////////////////////////
    // dllreadnaw.cpp
    #include <windows.h>
    #include <iostream.h>

HINSTANCE hDll; LONG ( __stdcall *OKiHo)(LONG,LONG);

void main(void) { hDll = ::LoadLibraryEx("dlltest.dll",NULL,0); if( ! hDll ){ cout << "open err"; return; }

OKiHo = (LONG(__stdcall*)(LONG,LONG))  ::GetProcAddress( hDll,"SelectLarge");
if( ! OKiHo ){
    ::FreeLibrary( hDll );
    cout << "read err";
    return;
}

int b = OKiHo(526,325);

::FreeLibrary( hDll );

cout << b;
cin >> b;

} /////////////////////////////////////////////////////////////////////////////



3. プロジェクトのフォルダにDLLだけコピーしてください。
そして、libファイルの設定もせずに実行します。

遭遇问题:
により Run-Time Check Failure #0 - The value of ESP was not properly saved across a function call. This is usually a result of calling a function declared with one calling convention with a function pointer declared with a different calling convention. が発生しました

解决方法:
typedef long(__stdcall *FUNCLvInitial)(int, int, char*);
改成
typedef long(__cdecl *FUNCLvInitial)(int, int, char*);

解释:
如果通过VC++编写的DLL欲被其他语言编写的程序调用,应将函数的调用方式声明为__stdcall方式,WINAPI都采用这种方式,而C/C++ 缺省的调用方式却为__cdecl。
__stdcall方式与__cdecl对函数名最终生成符号的方式不同。
若采用C编译方式(在C++中需将函数声明为extern "C"),__stdcall调用约定在输出函数名前面加下划线,后面加“@”符号和参数的字节数,形如_functionname@number;
而__cdecl调用约定仅在输出函数名前面加下划线,形如_functionname。
rainit2006 commented 7 years ago

C#からDLL関数の呼び出し 总结:因为托管和非托管程序的变量在内存里的形态不同,所以要考虑的地方比较多。

image

C# 言語では外部メソッドを呼び出す場合、必ず、メソッドに extern 修飾子を指定しなければなりません。この extern キーワードは、一般的に DllImport 属性とセットで用いられます

using System.Runtime.InteropServices; // DLL Import
class Win32Api
{
    [DllImport("User32.Dll", EntryPoint="SetWindowText")]
    public static extern void SetWindowText(int hwnd, String text);
}

■ Blittable 和非 Blittable 類型 https://msdn.microsoft.com/zh-tw/library/75dwhxf7(v=vs.110).aspx

关于blittable类型和marshall(内存转换) blittable类型意味着在托管和原生代码中,内存的表现是一致的,没有区别(比如:byte,int,float)。Non-blittable类型在两者中的内存表现就不一致。(比如:bool,string,array)。正因为这样,blittable类型数据能够直接传递给原生代码,但是non-blittable类型就需要做转换工作了。而这个转换工作很自然的就牵扯到新内存的分配。

marshall:让托管代码中的数据和原生代码中的数据可以相互访问。在这里中都称之为内存转换。

image

[StructLayout(LayoutKind.Sequential,Pack=4)]
public struct RECT
{
    public int left;
    public int top;
    public int right;
    public int bottom;
}
class Win32Api
{
    [DllImport("User32.Dll", EntryPoint="GetWindowRect")]
    public static extern int GetWindowRect(int hwnd, ref RECT rc);
}

呼び出し側

private void button4_Click(object sender, EventArgs e)
{
    RECT rc = new RECT();
    int hwnd = (int)this.Handle;
    Win32Api.GetWindowRect(hwnd, ref rc);

    string msg = String.Format("top={0}, left={1}, right={2}, bottom={3}."
    , rc.top, rc.left, rc.right, rc.bottom);
    MessageBox.Show(msg);
}

.NET Frameworkでは、通常、アクセスに最適な配置となるように、CLRがメモリ上における構造体の各メンバの配置を決定する。つまり、単純に同じ構造体をC#などで宣言しただけでは、各メンバのオフセットをDLL側と確実に一致させることは不可能である(事実、通常のC#の構造体をWin32 APIやDLL関数に渡そうとすると例外が発生する)。

 従って、Win32 APIやDLL関数に渡す構造体を定義する場合は、そのメンバの配置方法を変更する必要がある。これを実現するには、構造体に対してStructLayout属性(System.Runtime.InteropServices名前空間)を設定する。このとき、配置方法としてLayoutKind列挙体(System.Runtime.InteropServices名前空間)のメンバのいずれかを指定する。C言語などの構造体と同様に、メンバが宣言された順に配置されるようにするには、LayoutKind.Sequentialという値を指定すればよい。

-ポインタ型の関数パラメータ Netでは、IntPtrを使ってラップします。 ただし、受け取ったポインタから構造体へ変換するには、Marshal.PtrToStructureなどを使用して、データをC#側のプログラムに転送する必要があります。

using System.Runtime.InteropServices;

public delegate bool EnumWindowCB2(int hwnd, IntPtr lparm);

[DllImport("User32.Dll", EntryPoint = "EnumWindows")]
public static extern int EnumWindows2(EnumWindowCB2 cb, IntPtr lparm);
rainit2006 commented 7 years ago

卸载DLL 在C++中加载和卸载DLL是一件很容易的事,LoadLibrary和FreeLibrary让你能够轻易的在程序中加载DLL,然后在任何地方 卸载。

在C#中我们也能使用Assembly.LoadFile实现动态加载DLL,但是Assembly没有提供任何 卸载的方法。 这是由于托管代码的自动垃圾回收机制会做这件事情,所以C#不提供释放资源的函数,一切由垃圾回收来做。 问题:用Assembly加载的DLL可能只在程序结束的时候才会被释放,这也意味着在程序运行期间无法更新被加载的DLL。

C#也提供了实现动态卸载DLL的方法,通过AppDomain来实现。AppDomain是一个独立执行应用程序的环境,当AppDomain被卸载的 时候,在该环境中的所有资源也将被回收。

 class Program 
 { 
    static void Main(string[] args) 
    { 
      string callingDomainName =  
                        AppDomain.CurrentDomain.FriendlyName;//Thread.GetDomain().FriendlyName; 
      Console.WriteLine(callingDomainName); 

     AppDomain ad = AppDomain.CreateDomain("DLL Unload test"); 
     ProxyObject obj = (ProxyObject)ad.CreateInstanceFromAndUnwrap(@"UnloadDll.exe", 
                                  "UnloadDll.ProxyObject"); 

     obj.LoadAssembly(); 
     obj.Invoke("TestDll.Class1", "Test", "It's a test"); 

    AppDomain.Unload(ad); //卸载AppDomain,也就实现了卸载DLL

     obj = null; 
     Console.ReadLine(); 
   } 
 } 

 class ProxyObject : MarshalByRefObject 
 { 
          Assembly assembly = null; 
         public void LoadAssembly() 
        { 
                assembly = Assembly.LoadFile(@"TestDLL.dll"); 
         } 

        public bool Invoke(string fullClassName, string methodName, params Object[] args) 
        { 
              if(assembly == null) 
                             return false; 
              Type tp = assembly.GetType(fullClassName); 
              if (tp == null) 
                        return false; 
              MethodInfo method = tp.GetMethod(methodName); 
             if (method == null)  
                         return false; 
            Object obj = Activator.CreateInstance(tp); 
            method.Invoke(obj, args); 
            return true; 
         } 
 } 

注意:

  1. 要想让一个对象能够穿过AppDomain边界,必须要继承MarshalByRefObject类,否则无法被其他AppDomain使用。
  2. 每个线程都有一个默认的AppDomain,可以通过Thread.GetDomain()来得到。

托管DLL和非托管DLL的区别 http://www.cnblogs.com/lasthelloworld/p/4958966.html

托管DLL就是能够在公共语言运行库(Common Language Runtime,简称CLR)中能够直接引用的,并且扩展名为“DLL”的文件。具体所指就是封装各种命名空间所在的DLL文件,如System.dll等。

非托管DLL就是平常所的动态链接库等,其中就包括了封装所有Windows API函数的DLL文件。各种非托管DLL中的函数在公共语言运行库中不能直接被调用,而需要经过.Net框架提供的“平台调用”服务后才可以。

托管DLL文件,可以在Dotnet环境通过 “添加引用” 的方式,直接把托管DLL文件添加到项目中。然后通过 Using DLL命名空间,来调用相应的DLL对象 。 非托管DLL文件,在Dotnet环境应用时,通过 DllImport 调用。 image

-regasm: regasm注册的是.net框架下生成的dll(托管与clr)

regsvr32: regsvr32注册的是要用C++写的(非托管)。而C#写的就必须用Regasm注册

rainit2006 commented 7 years ago

http://toruuetani.hatenablog.com/entry/20060702/p1 Marshal.GetDelegateForFunctionPointer 同じようなインターフェイスを持つ、複数のアンマネージドDLL関数を呼び出す必要があったのでいろいろ調べていたら、 .NET 2.0 から用意された System.Runtime.InteropServices.Marshal.GetDelegateForFunctionPointer を使えば関数ポインタをデリゲートに変換できることがわかった。

やり方としてはこんな感じ。

using System;
using System.Runtime.InteropServices;

namespace LateBindingApplication {
    static class Program {
        /// <summary>
        /// アプリケーションのメイン エントリ ポイントです。
        /// </summary>
        [STAThread]
        static void Main() {
            using (LateBinding b = new LateBinding("user32.dll")) {
                MessageBox m = (MessageBox)b.GetDelegate("MessageBoxW", typeof(MessageBox));
                m(IntPtr.Zero, "call by c#", "LateBindingApplication", 0);
            }
        }
    }

    public delegate int MessageBox(IntPtr hwnd,
        [MarshalAs(UnmanagedType.LPWStr)]string text,
        [MarshalAs(UnmanagedType.LPWStr)]string Caption,
        int type);

    public class LateBinding : IDisposable {
        [DllImport("kernel32", CharSet = CharSet.Unicode, SetLastError = true)]
        private static extern IntPtr LoadLibrary([MarshalAs(UnmanagedType.LPWStr)] string lpFileName);

        [DllImport("kernel32", SetLastError = true)]
        private static extern bool FreeLibrary(IntPtr hModule);

        [DllImport("kernel32", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = false)]
        private static extern IntPtr GetProcAddress(IntPtr hModule,
            [MarshalAs(UnmanagedType.LPStr)]  string lpProcName);

        private IntPtr _module;

        /// <summary>
        /// DLL名を指定して遅延バインディングを行うオブジェクトを生成します。
        /// </summary>
        /// <param name="filename">バインドするDLL名</param>
        public LateBinding(string filename) {
            _module = LateBinding.LoadLibrary(filename);
            if (_module != IntPtr.Zero) {
                return;
            }

            int result = Marshal.GetHRForLastWin32Error();
            throw Marshal.GetExceptionForHR(result);
        }

        /// <summary>
        /// 指定した名前を持つアンマネージ関数ポインタをデリゲートに変換します。
        /// </summary>
        /// <param name="procName">アンマネージ関数名</param>
        /// <param name="delegateType">変換するデリゲートのType</param>
        /// <returns>変換したデリゲート</returns>
        public Delegate GetDelegate(string procName, Type delegateType) {
            IntPtr ptr = LateBinding.GetProcAddress(_module, procName);
            if (ptr != IntPtr.Zero) {
                Delegate d = Marshal.GetDelegateForFunctionPointer(ptr, delegateType);
                return d;
            }

            int result = Marshal.GetHRForLastWin32Error();
            throw Marshal.GetExceptionForHR(result);
        }

        #region IDisposable メンバ

        public void Dispose() {
            if (_module != IntPtr.Zero) {
                LateBinding.FreeLibrary(_module);
            }
        }

        #endregion
    }
}
rainit2006 commented 7 years ago

C# 動的using C++ dll

要するに、kernel32.dll中のLoadLibrary、GetProcAddress、FreeLibrary関数を利用して、C++のように実施する。

Marshal.GetDelegateForFunctionPointerを使うことで関数ポインタをデリゲートに変換して呼び出すことができます。

// デリゲートの定義
delegate IntPtr LoadImageDelegate(string filename, int flags);
delegate int SaveImageDelegate(string filename, IntPtr img);

[DllImport("kernel32.dll")]
static extern IntPtr LoadLibrary(string lpFileName);
[DllImport("kernel32.dll")]
static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName);
[DllImport("kernel32.dll")]
static extern bool FreeLibrary(IntPtr hLibModule);

static void Main(string[] args)
{
    // 関数ポインタを取得
    IntPtr ptrLib = LoadLibrary("highgui110.dll");
    IntPtr ptrLoad = GetProcAddress(ptrLib, "cvLoadImage");
    IntPtr ptrSave = GetProcAddress(ptrLib, "cvSaveImage");
    // 関数ポインタをデリゲートに変換する 
    LoadImageDelegate loadDelegate 
        = (LoadImageDelegate)Marshal.GetDelegateForFunctionPointer(ptrLoad, typeof(LoadImageDelegate));
    SaveImageDelegate saveDelegate
        = (SaveImageDelegate)Marshal.GetDelegateForFunctionPointer(ptrSave, typeof(SaveImageDelegate));
    // デリゲートを呼び出す
    IntPtr img = loadDelegate("hoge.jpg", 1);
    saveDelegate("fuga.png", img);
    // ライブラリを解放
    FreeLibrary(ptrLib);
}

注意:适当地设定CharSet = CharSet.Unicode [DllImport("msi.dll", CharSet = CharSet.Unicode)]

rainit2006 commented 7 years ago

C# 動的にC#のDLLをロード C++のDLLを動的に呼び出すにはDllImportを使用しますが、.Net用DLLはDllImportでは呼び出せません。.Net用DLLを呼び出すにはSystem.Reflectionの機能を使用します。

https://code.msdn.microsoft.com/windowsdesktop/17-c3491bd5

呼び出し側

using System; 
using System.Reflection; 

class Program 
{ 
    static void Main(string[] args) 
    { 
        Assembly m = Assembly.LoadFrom(args[0]); 
        dynamic instance1 =  
          Activator.CreateInstance(m.GetType("ClassLibrary1.Class1")); 
        instance1.SayWelcome(); 
    } 
}

呼ばれる側: (クラス ライブラリ) C#

using System; 

namespace ClassLibrary1 
{ 
    public class Class1 
    { 
        public void SayWelcome() 
        { 
            Console.WriteLine("Welcome C# World!"); 
        } 
    } 
}
rainit2006 commented 7 years ago

Quick C++/CLI-- C++ Wrapper for C# https://drthitirat.wordpress.com/2013/06/03/use-c-codes-in-a-c-project-wrapping-native-c-with-a-managed-clr-wrapper/ 讲解的过程很详细。


You have a C++ class NativeClass that you want to expose to C#.

class NativeClass { 
public:
    void Method();
};

1) Create a C++/CLI project. Link to your C++ library and headers.

2) Create a wrapper class that exposes the methods you want. Example:

#include "NativeClass.h"

public ref class NativeClassWrapper {
    NativeClass* m_nativeClass;

public:
    NativeClassWrapper() { m_nativeClass = new NativeClass(); }
    ~NativeClassWrapper() { this->!NativeClassWrapper(); }
    !NativeClassWrapper() { delete m_nativeClass; }
    void Method() {
        m_nativeClass->Method();
    }
};

3) Add a reference to your C++/CLI project in your C# project.

4) Use the wrapper type within a using statement:

using (var nativeObject = new NativeClassWrapper()) {
    nativeObject.Method();
}

The using statement ensures Dispose() is called, which immediately runs the destructor and destroys the native object. You will otherwise have memory leaks and probably will die horribly (not you, the program).


https://www.codeproject.com/Articles/19354/Quick-C-CLI-Learn-C-CLI-in-less-than-minutes#A8

CLI stands for Common Language Infrastructure. C++/CLI is the means to program .NET in C++, similarly like C# or VB.NET are used.

in C++, we have the "*" to denote a pointer, and in C++/CLI, we have the ^ to denote a handle.

To wrap a C++ class, we follow this simple guideline:

  1. Create the managed class, and let it have a member variable pointing to the native class.
  2. In the constructor or somewhere else you find appropriate, construct the native class on the native heap (using "new").
  3. Pass the arguments to the constructor as needed; some types you need to marshal as you pass from managed to unmanaged.
  4. Create stubs for all the functions you want to expose from your managed class.
  5. Make sure you delete the native pointer in the destructor of the managed class.
rainit2006 commented 7 years ago

Pinvoke プラットフォーム呼び出しサービス (PInvoke) を使用すると、DLL に実装されているアンマネージ関数をマネージ コードから呼び出すことができます。 P/Invokeの宣言は、System.Runtime.InteropServices.DllImportAttribute属性をメソッドの宣言につけることで行う。

C# コードからアンマネージ コードを直接呼び出すには、次の 2 とおりの方法があります。

  1. DLL からエクスポートされた関数を直接呼び出します。
  2. COM オブジェクトのインターフェイス メソッドを呼び出します。 どちらの方法でも、C# コンパイラに対してアンマネージ関数を宣言し、さらにアンマネージ コードとやり取りするパラメータと戻り値のマーシャリングの方法を記述する必要があります。
那么在C#中,函数原型可以定义如下:
int SetConfig(int type, IntPtr p);
而且结构A定义为:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]         public struct A {             [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 100)]             public string osdbuffer;                     public ushort ix;                       //显示坐标x             public ushort iy;                       //显示坐标y        }

注意这里的CharSet,它由c中wchar_t决定的,如果c程序编译时使用Unicode,这里就用CharSet.Unicode,否则使用CharSet.Ansi。

这里还有一个很重要的问题,那就是内存在编译时的分配问题。一般默认情况下,内存的分配是4byte的整数倍,在这里我省略了。
否则完整写法为如下(注意pack的值):
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode,Pack = 4)]

C#里调用代码如下:

A s_a = new A();   int lenght = Marshal.SizeOf(s_a); IntPtr pA= Marshal.AllocHGlobal(lenght); Marshal.StructureToPtr(s_a, pA, true); int type = 1; int ret = SetConfig( type, pA); Marshal.FreeHGlobal(pA);


再假设如果A的结构体包含一个指向另一个结构体B的指针:

struct A
{ wchar_t osdbuffer[100];
unsigned short ix;
unsigned short iy;、 B *pB;
}; struct B
{ wchar_t title[20];
};

在C#中你要做的也就稍微复杂一点,也就是说你不但要为A分配内存,也要为B分配内存

B s_b = new B();   //赋值省略   int lenght1 = Marshal.SizeOf(s_b); IntPtr pB= Marshal.AllocHGlobal(lenght1); Marshal.StructureToPtr(s_b, pB, true);   A s_a = new A();   s_a.pB = pB;   //其他赋值   //   int lenght2 = Marshal.SizeOf(s_a);   IntPtr pA= Marshal.AllocHGlobal(lenght2); Marshal.StructureToPtr(s_a, pA, true); int type = 1; int ret = SetConfig( type, pA); Marshal.FreeHGlobal(pB); Marshal.FreeHGlobal(pA);

rainit2006 commented 6 years ago

UnmanagedType 列挙型 Identifies how to marshal parameters or fields to unmanaged code. https://msdn.microsoft.com/ja-jp/library/system.runtime.interopservices.unmanagedtype(v=vs.110).aspx

UnmanagedType.Bool :A 4-byte Boolean value (true != 0, false = 0). This is the Win32 BOOL type. UnmanagedType.I1: A 1-byte signed integer. You can use this member to transform a Boolean value into a 1-byte, C-style bool (true = 1, false = 0). UnmanagedType.U1 : A 1-byte unsigned integer.

Blittable 型と非 Blittable 型 Most data types have a common representation in both managed and unmanaged memory and do not require special handling by the interop marshaler. These types are called blittable types because they do not require conversion when they are passed between managed and unmanaged code.

Structures that are returned from platform invoke calls must be blittable types. Platform invoke does not support non-blittable structures as return types.

Object references are not blittable. This includes an array of references to objects that are blittable by themselves. For example, you can define a structure that is blittable, but you cannot define a blittable type that contains an array of references to those structures.

rainit2006 commented 6 years ago

#import ,#pragma comment ,LoadLibrary"三种引入dll的方式

rainit2006 commented 6 years ago

アンマネージコードにC#のデリゲートを渡す http://d.hatena.ne.jp/aharisu/20090401/1238561406

たとえばC++のWin32APIには BOOL EnumWindows(WNDENUMPROC lpEnumFunc, LPARAM lParam ); という関数があります。 この第一引数のWNDENUMPROCはコールバック関数なので、C#からこの関数を呼び出したい場合はC#からデリゲートを渡してあげないといけません。

まずWNDENUMPROCの定義は BOOL CALLBACK EnumWindowsProc(HWND hwnd,LPARAM lParam); それに対応するデリゲートは delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam); になると思います。

ついでにEnumWindowsのDllImportも書いておきます。

[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool EnumWindows([MarshalAs(UnmanagedType.FunctionPtr)] EnumWindowsProc lpEnumFunc, IntPtr lParam);

このとき引数のデリゲートに属性[MarshalAs(UnmanagedType.FunctionPtr)]をつけることをお勧めします。

VCでは標準の呼び出し規約はcdeclになっていると思います。 なので関数ポインタもcdeclだと認識されます。 そして.netのメソッドはすべて__stdcallになっています。

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);

にします。

■参照Sample: https://qiita.com/yz2cm/items/8bc26f789c3308799aa9


MarshalAs

struct Person {
    [MarshalAs(UnmanagedType.ByValArray,SizeConst=8)]
    public byte[] name;
    public int  age;
}

/* C言語上での型
    struct Person {
        char    name    [8];
        int     age;
    };

    [MarshalAs(UnmanagedType.ByValArray,SizeConst=8)]
       を付けないと
    struct Person {
        char*   name;
        int     age;
    };  
    ↑このような型を意味する
*/

マーシャリングする必要のある型が、非 Blittable 型です。以下のような型は Blittable 型ではありません。

char
bool
decimal
string
object
string を含む構造体も Blittable 型ではありません。

文字列に対する既定のマーシャリング: https://msdn.microsoft.com/ja-jp/library/s9ts558h(v=vs.110).aspx image


UnmanagedType 列挙型 https://msdn.microsoft.com/ja-jp/library/system.runtime.interopservices.unmanagedtype(v=vs.110).aspx パラメーターまたはフィールドをアンマネージ コードにマーシャリングする方法を示します。

アラインメントの指定 (StructLayoutAttribute.Pack) http://smdn.jp/programming/netfx/struct/1_structlayout_fieldoffset/#StructLayoutAttribute.Pack  Packを指定した場合、各フィールドはPackで指定された値の倍数のオフセットに配置されます。 例えば2を指定すれば各フィールドのオフセットは2の倍数となります。 Packに指定できる値は2nである値のうち、0, 1, 2, 4, 8, 16, 32, 64, 128のいずれかです。 0を指定した場合は、デフォルトと同じ、つまりPackを指定しなかった場合と同じになります。

struct S1 {
  byte F1;
  int  F2;
  int  F3;
}

image


※リトルエンディアンでBYTE[2]からWORDに

rainit2006 commented 6 years ago

IntPtr IntPtr構造体 : C#でポインタを処理するための構造体です。 https://so-zou.jp/software/tech/programming/c-sharp/grammar/type/pointer/

C#には IntPtr という型があります。これは汎用的なポインタを表す型で、ほぼ void と同義です。 ただしC#は超厳しい型付け言語なので、void みたいな万能選手は万能ゆえの曖昧さを解決するために、回りくどい変換メソッドを経由しないと使えません。 具体的には、IntPtrの変数に Marshal.AllocHGlobalで必要なサイズのメモリを確保し、それをC++のDLLに渡します。 さらにMarshal.ReadInt16(必要な型によって異なる)などで変換後、確保したメモリをMarshal.FreeHGlobalで解放する、と3段階の面倒なプロセスを経なければいけません。 http://rokujo.hatenadiary.com/entry/2015/07/21/135938

//usingではこれが必要
using System.Runtime.InteropServices;

//ここはクラス内で定義
[DllImport("DrsUtil.dll", EntryPoint = "ConvertToShort")]
extern static void ConvertToShort(string pstr, IntPtr pret);

//ここは関数内の記述
IntPtr buffer = new IntPtr();
buffer = Marshal.AllocHGlobal(2); //2バイトのメモリ確保
ConvertToShort("XXXXXX", buffer);
short sval = Marshal.ReadInt16(buffer); //変換
Marshal.FreeHGlobal(buffer); //メモリ解放

intptr-からの色々な型への変換 http://kenrow.b.osdn.me/2011/11/24/intptr-%E3%81%8B%E3%82%89%E3%81%AE%E8%89%B2%E3%80%85%E3%81%AA%E5%9E%8B%E3%81%B8%E3%81%AE%E5%A4%89%E6%8F%9B/ ・IntPtr → int[]

//生成一个指针
IntPtr buffer = new IntPtr();
int size = 配列MAX

int ws = sizeof(int);

//为指针在unmanaged区域里分别空间
buffer = Marshal.AllocHGlobal(size);
int[] result = new int[size / ws];
Marshal.Copy(buffer, result, 0, size / ws);
Marshal.FreeHGlobal(buffer);

・IntPtr → string (ASCII文字列)

IntPtr buffer = new IntPtr();
int size = 文字列最大長
buffer = Marshal.AllocHGlobal(size);
string result = “”;
result = Marshal.PtrToStringAnsi(buffer).SubString(size);;
Marshal.FreeHGlobal(buffer);

・IntPtr → Int16 (Short/WORD)

IntPtr buffer = new IntPtr();
int size = 配列MAX
buffer = Marshal.AllocHGlobal(size);
Int16 result = “”;
result = Marshal.ReadInt16(buffer);
Marshal.FreeHGlobal(buffer);

ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー https://zhuanlan.zhihu.com/p/29161824

建议:当调用结构体类型的变量时,采用IntPtr的方式来处理。 如果以ref方式来传递结构体的指针,对于字符串这样的字段,可能会出现乱码等异常。那么我们更彻底一点,直接全部传入IntPtr。

PERSON p1 = new PERSON { 
    name="kikay",
    age=18,
    male=0,
    address="china"
};
PERSON p2 = new PERSON();

//在非托管区动态分配一片内存,并复制结构体给这片内存
IntPtr pP1 = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(PERSON)));
Marshal.WriteByte(pP1, 0);
Marshal.StructureToPtr(p1, pP1, true);

IntPtr pP2 = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(PERSON)));
Marshal.WriteByte(pP2, 0);
Marshal.StructureToPtr(p2, pP2, true);

IntPtr pName = getName2(pP1);
string strName = Marshal.PtrToStringAnsi(pName);

int iAge = getAge2(pP1);

byte blMale = getMale2(pP1);

IntPtr pAddress = getAddress2(pP1);
string strAddress = Marshal.PtrToStringAnsi(pAddress);

clone2(pP1, pP2);

//将非托管区的内存复制给托管区,并装换为结构体
PERSON p3 = (PERSON)Marshal.PtrToStructure(pP2, typeof(PERSON));

//释放动态分配的内存
Marshal.FreeHGlobal(pP1);
Marshal.FreeHGlobal(pP2);

https://zhuanlan.zhihu.com/p/29161824

rainit2006 commented 6 years ago

Marshal.PtrToStringAnsi メソッド Allocates a managed String and copies all or part of an unmanaged ANSI string into it. PtrToStringAnsi(IntPtr) :Copies all characters up to the first null character from an unmanaged ANSI string to a managed String, and widens each ANSI character to Unicode. PtrToStringAnsi(IntPtr, Int32) :Allocates a managed String, copies a specified number of characters from an unmanaged ANSI string into it, and widens each ANSI character to Unicode.

<C++>
  // Create an unmanaged c string.
    const char * myString = "Hello managed world (from the unmanaged world)!";
    // Convert the c string to a managed String.
    String ^ myManagedString = Marshal::PtrToStringAnsi((IntPtr) (char *) myString);
    // Display the string to the console.
    Console::WriteLine(myManagedString);

Marshal.StringToCoTaskMemAnsi メソッド (String) Copies the contents of a managed String to a block of memory allocated from the unmanaged COM task allocator.

Marshal.AllocHGlobal Allocates memory from the unmanaged memory of the process by using the specified number of bytes.

[SecurityCriticalAttribute]
[ComVisibleAttribute(true)]
public static void StructureToPtr(
    object structure,
    IntPtr ptr,
    bool fDeleteOld
)

StructureToPtr copies the contents of structure to the pre-allocated block of memory that the ptr parameter points to. If structure contains reference types that marshal to COM interface pointers (interfaces, classes without layout, and System.Object), the managed objects are kept alive with reference counts. All other reference types (for example, strings and arrays) are marshaled to copies. To release these managed or unmanaged objects, you must call the Marshal.DestroyStructuremethod before you free the memory block.

       // Create a point struct.
        Point p;
        p.x = 1;
        p.y = 1;

        // Initialize unmanged memory to hold the struct.
        IntPtr pnt = Marshal.AllocHGlobal(Marshal.SizeOf(p));

        try
        {

            // Copy the struct to unmanaged memory.
            Marshal.StructureToPtr(p, pnt, false);

            // Create another point.
            Point anotherP;

            // Set this Point to the value of the 
            // Point in unmanaged memory. 
            anotherP = (Point)Marshal.PtrToStructure(pnt, typeof(Point));
  略

Marshal.WriteByte Writes a single byte value to unmanaged memory. WriteByte(IntPtr, Byte) : Writes a single byte value to unmanaged memory. WriteByte(IntPtr, Int32, Byte) : Writes a single byte value to unmanaged memory at a specified offset.

Marshal.Copy メソッド Copies data from a managed array to an unmanaged memory pointer, or from an unmanaged memory pointer to a managed array.

rainit2006 commented 6 years ago

举例:FCICreate function的C#库实现。 分析:FCICreate 这个函数C++下的定义为:

HFCI FCICreate(
  _Inout_  PERF              perf,  //结构体
  _In_     PFNFCIFILEPLACED  pfnfiledest, //回调函数的指针
  _In_     PFNFCIALLOC       pfnalloc, //回调函数的指针
  _In_     PFNFCIFREE        pfnfree, //回调函数的指针
  _In_     PFNFCIOPEN        pfnopen,
  _In_     PFNFCIREAD        pfnread,
  _In_     PFNFCIWRITE       pfnwrite,
  _In_     PFNFCICLOSE       pfnclose,
  _In_     PFNFCISEEK        pfnseek,
  _In_     PFNFCIDELETE      pfndelete,
  _In_     PFNFCIGETTEMPFILE pfnfcigtf,
  _In_     PCCAB             pccab,
  _In_opt_ void FAR          *pv
);

可见,该函数的参数多是回调函数的指针,那么在C#里声明这个函数时,这些参数的类型需要用到InPtr。而且因为是回调函数(该指针要返回到C++里),所以还要用到delegat,UnmanagedFunctionPointer 和 Marshal.GetFunctionPointerForDelegate。

//C#里函数的声明
[DllImport("Cabinet.dll", ExactSpelling = true,
            CallingConvention = CallingConvention.Cdecl)]
        private static extern IntPtr FDICreate([In] IntPtr pfnalloc,
            [In] IntPtr pfnfree,
            [In] IntPtr pfnopen,
            [In] IntPtr pfnread,
            [In] IntPtr pfnwriter,
            [In] IntPtr pfnclose,
            [In] IntPtr pfnseek,
            [In, MarshalAs(UnmanagedType.I4)] CPUType cpuType,
            [In, Out, MarshalAs(UnmanagedType.LPStruct)] ERF perf);

。。。。
    //函数在C#里使用
    IntPtr hfci = FCICreate(erf,
                    Marshal.GetFunctionPointerForDelegate(fciFilePlacedHandler),
                    Marshal.GetFunctionPointerForDelegate(fciAllocHandler),  //委托函数实例的指针
                    Marshal.GetFunctionPointerForDelegate(fciFreeHandler),
                    Marshal.GetFunctionPointerForDelegate(fciOpenHandler),
                    Marshal.GetFunctionPointerForDelegate(fciReadHandler),
                    Marshal.GetFunctionPointerForDelegate(fciWriteHandler),
                    Marshal.GetFunctionPointerForDelegate(fciCloseHandler),
                    Marshal.GetFunctionPointerForDelegate(fciSeekHandler),
                    Marshal.GetFunctionPointerForDelegate(fciDeleteHandler),
                    Marshal.GetFunctionPointerForDelegate(fciGetTempFileHandler),
                    ccab, IntPtr.Zero);

//当然,参数里的每个函数以及实例也要事先定义和创建。例如:

       //定义委托函数
        [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private delegate void FNFCIFILEPLACED([In, Out][MarshalAs(UnmanagedType.LPStruct)] CCAB pccab,
            [In, MarshalAs(UnmanagedType.LPStr)] string pszFile, uint cbFile,
            [MarshalAs(UnmanagedType.Bool)] bool fContinuation, IntPtr pv);

       //委托函数的实例
         var fciFilePlacedHandler = new FNFCIFILEPLACED((pccab, pszFile, cbFile, fContinuation, pv) =>
                {
                    //Currently does nothing.
                    //May add events in future.
                });
rainit2006 commented 6 years ago

ref的使用

C++代码如下. 其中参数a 和 b是参照渡し(int&)方式。那么在C#里对应的是ref。

int __stdcall substract(MyMath<int>* pMath, const int& a,const int& b)
{
    if(pMath!=NULL)
    {
        return pMath->substract(a,b);
    }
    return INT_MIN;
}

对应的C#里

//函数声明
[DllImport("ClassDLL.dll", EntryPoint = "substract", CharSet = CharSet.Ansi)]
public static extern int substract(IntPtr ptr, ref int a, ref int b);

//函数使用
    int a = 1;
    int b = 101;

    int iSubsract = substract(ptr,ref a,ref b);
    pRes = toString(ptr);
    Console.WriteLine(Marshal.PtrToStringAnsi(pRes)+"结果("+iSubsract.ToString()+")");

随便说一下: C#には値型と参照型という2つの種類があり、 値型のオブジェクトは数値(int)や構造体(struct)を指す。この種類のオブジェクトを関数へ渡すとき、そのコピーが渡される。 一方の参照型は、クラスにあたるオブジェクトで、stringやListなど、C#のほとんどのオブジェクトがクラスタイプ。参照型のオブジェクトを関数へ渡すと、その参照が渡され、コピーはされない。

rainit2006 commented 6 years ago

C#里调用C++的 int Merger(int argc, char** argv) 函数的实现方法。参考下面网页:

https://stackoverflow.com/questions/37733313/how-to-get-char-using-c

[DllImport("third_party.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
private static extern int start(Int32 args, IntPtr argv);

public bool start(params string[] arguments)
{
int result;

if (arguments == null || arguments.Length == 0)
{
    result = dll_system_startup(0, IntPtr.Zero);
}
else
{
    List<IntPtr> allocatedMemory = new List<IntPtr>();

    int sizeOfIntPtr = Marshal.SizeOf(typeof(IntPtr));
    IntPtr pointersToArguments = Marshal.AllocHGlobal(sizeOfIntPtr * arguments.Length);

    for (int i = 0; i < arguments.Length; ++i)
    {
        IntPtr pointerToArgument = Marshal.StringToHGlobalAnsi(arguments[i]);
        allocatedMemory.Add(pointerToArgument);
        Marshal.WriteIntPtr(pointersToArguments, i * sizeOfIntPtr, pointerToArgument);
    }

    result = start(arguments.Length, pointersToArguments);

    Marshal.FreeHGlobal(pointersToArguments);

    foreach (IntPtr pointer in allocatedMemory)
    {
        Marshal.FreeHGlobal(pointer);
    }
}

return result == 0;
}
rainit2006 commented 6 years ago
//C++中的DLL函数原型为
    //extern "C" __declspec(dllexport) bool 方法名一(const char* 变量名1, unsigned char* 变量名2)
    //extern "C" __declspec(dllexport) bool 方法名二(const unsigned char* 变量名1, char* 变量名2)

    //C#调用C++的DLL搜集整理的所有数据类型转换方式,可能会有重复或者多种方案,自己多测试
    //c++:HANDLE(void   *)          ----    c#:System.IntPtr 
    //c++:Byte(unsigned   char)     ----    c#:System.Byte 
    //c++:SHORT(short)              ----    c#:System.Int16 
    //c++:WORD(unsigned   short)    ----    c#:System.UInt16 
    //c++:INT(int)                  ----    c#:System.Int16
    //c++:INT(int)                  ----    c#:System.Int32 
    //c++:UINT(unsigned   int)      ----    c#:System.UInt16
    //c++:UINT(unsigned   int)      ----    c#:System.UInt32
    //c++:LONG(long)                ----    c#:System.Int32 
    //c++:ULONG(unsigned   long)    ----    c#:System.UInt32 
    //c++:DWORD(unsigned   long)    ----    c#:System.UInt32 
    //c++:DECIMAL                   ----    c#:System.Decimal 
    //c++:BOOL(long)                ----    c#:System.Boolean 
    //c++:CHAR(char)                ----    c#:System.Char 
    //c++:LPSTR(char   *)           ----    c#:System.String 
    //c++:LPWSTR(wchar_t   *)       ----    c#:System.String 
    //c++:LPCSTR(const   char   *)  ----    c#:System.String 
    //c++:LPCWSTR(const   wchar_t   *)      ----    c#:System.String 
    //c++:PCAHR(char   *)   ----    c#:System.String 
    //c++:BSTR              ----    c#:System.String 
    //c++:FLOAT(float)      ----    c#:System.Single 
    //c++:DOUBLE(double)    ----    c#:System.Double 
    //c++:VARIANT           ----    c#:System.Object 
    //c++:PBYTE(byte   *)   ----    c#:System.Byte[]

    //c++:BSTR      ----    c#:StringBuilder
    //c++:LPCTSTR   ----    c#:StringBuilder
    //c++:LPCTSTR   ----    c#:string
    //c++:LPTSTR    ----    c#:[MarshalAs(UnmanagedType.LPTStr)] string 
    //c++:LPTSTR 输出变量名    ----    c#:StringBuilder 输出变量名
    //c++:LPCWSTR   ----    c#:IntPtr
    //c++:BOOL      ----    c#:bool   
    //c++:HMODULE   ----    c#:IntPtr    
    //c++:HINSTANCE ----    c#:IntPtr 
    //c++:结构体    ----    c#:public struct 结构体{}; 
    //c++:结构体 **变量名   ----    c#:out 变量名   //C#中提前申明一个结构体实例化后的变量名
    //c++:结构体 &变量名    ----    c#:ref 结构体 变量名

    //c++:WORD      ----    c#:ushort
    //c++:DWORD     ----    c#:uint
    //c++:DWORD     ----    c#:int

    //c++:UCHAR     ----    c#:int
    //c++:UCHAR     ----    c#:byte
    //c++:UCHAR*    ----    c#:string
    //c++:UCHAR*    ----    c#:IntPtr

    //c++:GUID      ----    c#:Guid
    //c++:Handle    ----    c#:IntPtr
    //c++:HWND      ----    c#:IntPtr
    //c++:DWORD     ----    c#:int
    //c++:COLORREF  ----    c#:uint

    //c++:unsigned char     ----    c#:byte
    //c++:unsigned char *   ----    c#:ref byte
    //c++:unsigned char *   ----    c#:[MarshalAs(UnmanagedType.LPArray)] byte[]
    //c++:unsigned char *   ----    c#:[MarshalAs(UnmanagedType.LPArray)] Intptr

    //c++:unsigned char &   ----    c#:ref byte
    //c++:unsigned char 变量名      ----    c#:byte 变量名
    //c++:unsigned short 变量名     ----    c#:ushort 变量名
    //c++:unsigned int 变量名       ----    c#:uint 变量名
    //c++:unsigned long 变量名      ----    c#:ulong 变量名

    //c++:char 变量名       ----    c#:byte 变量名   //C++中一个字符用一个字节表示,C#中一个字符用两个字节表示
    //c++:char 数组名[数组大小]     ----    c#:MarshalAs(UnmanagedType.ByValTStr, SizeConst = 数组大小)]        public string 数组名; ushort

    //c++:char *            ----    c#:string       //传入参数
    //c++:char *            ----    c#:StringBuilder//传出参数
    //c++:char *变量名      ----    c#:ref string 变量名
    //c++:char *输入变量名  ----    c#:string 输入变量名
    //c++:char *输出变量名  ----    c#:[MarshalAs(UnmanagedType.LPStr)] StringBuilder 输出变量名

    //c++:char **           ----    c#:string
    //c++:char **变量名     ----    c#:ref string 变量名
    //c++:const char *      ----    c#:string
    //c++:char[]            ----    c#:string
    //c++:char 变量名[数组大小]     ----    c#:[MarshalAs(UnmanagedType.ByValTStr,SizeConst=数组大小)] public string 变量名;

    //c++:struct 结构体名 *变量名   ----    c#:ref 结构体名 变量名
    //c++:委托 变量名   ----    c#:委托 变量名

    //c++:int       ----    c#:int
    //c++:int       ----    c#:ref int
    //c++:int &     ----    c#:ref int
    //c++:int *     ----    c#:ref int      //C#中调用前需定义int 变量名 = 0;

    //c++:*int      ----    c#:IntPtr
    //c++:int32 PIPTR *     ----    c#:int32[]
    //c++:float PIPTR *     ----    c#:float[]

    //c++:double** 数组名          ----    c#:ref double 数组名
    //c++:double*[] 数组名          ----    c#:ref double 数组名
    //c++:long          ----    c#:int
    //c++:ulong         ----    c#:int

    //c++:UINT8 *       ----    c#:ref byte       //C#中调用前需定义byte 变量名 = new byte();       

    //c++:handle    ----    c#:IntPtr
    //c++:hwnd      ----    c#:IntPtr

    //c++:void *    ----    c#:IntPtr        
    //c++:void * user_obj_param    ----    c#:IntPtr user_obj_param
    //c++:void * 对象名称    ----    c#:([MarshalAs(UnmanagedType.AsAny)]Object 对象名称

    //c++:char, INT8, SBYTE, CHAR                               ----    c#:System.SByte  
    //c++:short, short int, INT16, SHORT                        ----    c#:System.Int16  
    //c++:int, long, long int, INT32, LONG32, BOOL , INT        ----    c#:System.Int32  
    //c++:__int64, INT64, LONGLONG                              ----    c#:System.Int64  
    //c++:unsigned char, UINT8, UCHAR , BYTE                    ----    c#:System.Byte  
    //c++:unsigned short, UINT16, USHORT, WORD, ATOM, WCHAR , __wchar_t             ----    c#:System.UInt16  
    //c++:unsigned, unsigned int, UINT32, ULONG32, DWORD32, ULONG, DWORD, UINT      ----    c#:System.UInt32  
    //c++:unsigned __int64, UINT64, DWORDLONG, ULONGLONG                            ----    c#:System.UInt64  
    //c++:float, FLOAT                                                              ----    c#:System.Single  
    //c++:double, long double, DOUBLE                                               ----    c#:System.Double 

    //Win32 Types        ----  CLR Type  

    //Struct需要在C#里重新定义一个Struct
    //CallBack回调函数需要封装在一个委托里,delegate static extern int FunCallBack(string str);

    //unsigned char** ppImage替换成IntPtr ppImage
    //int& nWidth替换成ref int nWidth
    //int*, int&, 则都可用 ref int 对应
    //双针指类型参数,可以用 ref IntPtr
    //函数指针使用c++: typedef double (*fun_type1)(double); 对应 c#:public delegate double  fun_type1(double);
    //char* 的操作c++: char*; 对应 c#:StringBuilder;
    //c#中使用指针:在需要使用指针的地方 加 unsafe

    //unsigned   char对应public   byte
    /*
     * typedef void (*CALLBACKFUN1W)(wchar_t*, void* pArg);
     * typedef void (*CALLBACKFUN1A)(char*, void* pArg);
     * bool BIOPRINT_SENSOR_API dllFun1(CALLBACKFUN1 pCallbackFun1, void* pArg);
     * 调用方式为
     * [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
     * public delegate void CallbackFunc1([MarshalAs(UnmanagedType.LPWStr)] StringBuilder strName, IntPtr pArg);
     * 
     * 
     */
rainit2006 commented 6 years ago

实例:C# 和 C++ dll之间传参形式可行。 (1),  C++ dllはC#から渡される変数を表示。 (2)、C++ dllから文字列をC#へ渡す。Callbackを利用。

TestDLL.h

#pragma once
#ifndef _TEST_DLL_H_  
#define _TEST_DLL_H_  
#define DLL_EXPORT  

#if defined DLL_EXPORT  
#define DECLDIR __declspec(dllexport)  
#else  
#define DECLDIR __declspec(dllimport)  
#endif  

#include <Windows.h>

typedef  int(*CallbackFunctionPtr)(LPTSTR);

extern "C"
{
    DECLDIR void test(int* val, CallbackFunctionPtr callback);
}

#endif

TestDLL.cpp

#include "TestDLL.h"
#include <iostream>
#include <Windows.h>
    void test(int* val, CallbackFunctionPtr callback) 
   //val是C#传来的时刻在变, callback是为了把dll里的情报传给C#。
    {

        Sleep(1000);
        std::cout << *val << std::endl;
        callback(L"Begin.\n");

        Sleep(1000);
        std::cout << *val << std::endl;
        callback(L"second.\n");

        Sleep(1000);
        std::cout << *val << std::endl;
        Sleep(1000);
        std::cout << *val << std::endl;
        callback(L"next.\n");
        Sleep(1000);
        std::cout << *val << std::endl;
        Sleep(1000);
        std::cout << *val << std::endl;
        callback(L"Done.\n");
    }

C#文件

 public partial class MainWindow : Window
    {
        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        private delegate int CallbackDelegate([MarshalAs(UnmanagedType.LPWStr)]string message);

        [DllImport("dllTestDemo.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        static extern void test(ref int i, [MarshalAs(UnmanagedType.FunctionPtr)]CallbackDelegate callback);

       private void btn_merger(object sender, RoutedEventArgs e)
        {
            int val = 0;
            string str = "";
            CallbackDelegate pn = ProgressNotifier;

            var task = Task.Run(() =>
            {
                try
                {
                    //doMerger();
                    test(ref val, pn);
                }
                catch(Exception ex)
                {
                    MessageBox.Show(ex.Message);
                }
            });

            for (int i = 0; i < 10; i++)
            {
                Thread.Sleep(900);
                val++;   //改变val的值,以观察C++ dll里能否正确显示。
                Console.WriteLine(str);   //显示C++ dll传来的字符串信息。
            }
        }

       private static int ProgressNotifier(string progressMessage)
        {
            Console.WriteLine($"{progressMessage}");
            return 0;
        }
}
rainit2006 commented 6 years ago

C++里调用C++ dll的实例

typedef bool(*FUNC)(CString, CTime, string*);

int main()
{
    HMODULE hModule = ::LoadLibrary(_T("MergerDll.dll"));
    if (hModule == NULL)
    {
        printf("%s", "Failed to load MergerDll.dll\n");
        return - 1;
    }

    FUNC lpFunc = (FUNC) ::GetProcAddress(hModule, "GetVicsInfo");
    if (lpFunc == NULL)
    {
        printf("%s", "Failed to get an address for GetVicsInfo");
        FreeLibrary(hModule);
        return -1;
    }

    CTime timestamp = CTime(2015, 10, 20, 14, 38, 0);
    CString filename = _T("..\\vics.log");
    string sVicsInfo = "";

    bool result = (*lpFunc)(filename, timestamp, &sVicsInfo);
    if (result)
    {
        printf("%s", sVicsInfo.c_str());
    }

    FreeLibrary(hModule);

    std::cout << "Process Done!" << std::endl;
    std::string s;
    std::cin >> s;

    return 0;
}