C ++クラスの仮想関数を呼び出すためにC#を使用する方法(すなわち、動的なメモリの呼び出し)

  この記事では、非仮想関数は仮想関数テーブルに存在しないとして、輸出の輸出を使用しない限り、クラスオブジェクトから計算アドレスをオフセットし、gccのデフォルトすることはできない仮想関数の唯一の.h C ++クラスのヘッダファイル(非インスタンス関数を呼び出すためにC#を使用する方法について説明しますすべての例では)あなたが知っているが、知られたくない場合は、私はゴシップを探すために選択することができ、なぜMSVC必要性の.libでエクスポート機能、であり、COMコンポーネントを直接例えば、(世代introp.dllを引用する必要はありません)は、C#を呼び出すこと。

  我々はすべて知っている、アンマネージ関数、使用Pを呼び出すC#のサポート/ Inovkeを容易に実現することができ、例えば、次のコード

[DllImport("msvcrt", EntryPoint = "memcpy", CallingConvention = CallingConvention.Cdecl)]
public static extern void memcpy(IntPtr dest, IntPtr src, int count);

しかし、唯一のエクスポート関数としてマークされたDLLを呼び出すことができますDLLIMPORTを使用して、我々は以下のように、エクスポートされた関数を表示するためにいくつかのツールを使用することができます

通常、エクスポートされた関数は、C言語形式です。

  C ++ので、メモリ状態は、そのメモリは、仮想関数テーブルを保持し、我々はC ++クラス・メモリ・アドレスを知っている場合も、そのヘッダファイルを持っているかのクラス、そして我々はの関数を呼び出すために、自分の希望を計算することができますその直接のアドレスの呼び出しにとして、次のように単純な例です

#include <iostream>

class A_A_A {
public:
    virtual void hello() {
        std::cout << "hello from A\n";
    };
};

//typedef void (*HelloMethod)(void*);

int main()
{
    A_A_A* a = new A_A_A();
    a->hello();

    //HelloMethod helloMthd = *(HelloMethod *)*(void**)a;
    
    //helloMthd(a);
    (*(void(**)(void*))*(void**)a)(a);

    int c;
    std::cin >> c;
}

(上記のコメント行23、および、他のオープンコメント行で同じ効果が読みやすくすることができる)
、我々は簡単に(クラスメモリ構造は、2つの仮想関数テーブルポインタにC ++であり、コードから見ることができますアレイは、多重継承複数)が存在し得る場合、各仮想関数テーブルは、2つのポインタ(配列の関数である)仮想関数ポインタの多くの数が存在します。上記の第一の機能は、仮想無効であることをクラスオブジェクトである場合私たちは知っている(*)(無効)のタイプは、我々は単に機能を呼び出すことができます。

  そして、サン作業を開始した、我々は、C ++のdllを書く、と我々はすべての後に、C ++のnew演算子は、(新しいオブジェクトを提供するために使用されるCフォーマットのエクスポート機能を提供し、C ++の仮想関数を呼び出すためにC#を使用してみてください複雑で、多くの場合、実際には、私たちは以下のように、)について詳細に説明する私のCOMコンポーネントの後半部分を呼び出して、この新しいオブジェクトから抜け出すことができます

dll.h

class DummyClass {
private:
    virtual void sayHello();
};

dll.cpp

#include "dll.h"
#include <stdio.h>

void DummyClass::sayHello() {
    printf("Hello World\n");
}

extern "C" __declspec(dllexport) DummyClass* __stdcall newObj() {
    return new DummyClass();
}

私たちは長い間、DLLをコンパイル

sayHelloを呼び出すためにC#で書かれてみましょう

using System;
using System.Runtime.InteropServices;

namespace ConsoleApp2
{
    class Program
    {
        [DllImport("Dll1", EntryPoint = "newObj")]
        static extern IntPtr CreateObject();

        [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
        delegate void voidMethod1(IntPtr thisPtr);

        static void Main(string[] args)
        {
            IntPtr dummyClass = CreateObject();
            IntPtr vfptr = Marshal.ReadIntPtr(dummyClass);
            IntPtr funcPtr = Marshal.ReadIntPtr(vfptr);
            voidMethod1 voidMethod = (voidMethod1)Marshal.GetDelegateForFunctionPointer(funcPtr, typeof(voidMethod1));
            voidMethod(dummyClass);

            Console.ReadKey();
        }
    }
}

(因为调用的是c++的函数,所以this指针是第一个参数,当然,不同调用约定时它入栈方式和顺序不一样)
下面有一种另外的写法

using System;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.InteropServices;

namespace ConsoleApp2
{
    class Program
    {
        [DllImport("Dll1", EntryPoint = "newObj")]
        static extern IntPtr CreateObject();

        //[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
        //delegate void voidMethod1(IntPtr thisPtr);

        static void Main(string[] args)
        {
            IntPtr dummyClass = CreateObject();
            IntPtr vfptr = Marshal.ReadIntPtr(dummyClass);
            IntPtr funcPtr = Marshal.ReadIntPtr(vfptr);
            /*voidMethod1 voidMethod = (voidMethod1)Marshal.GetDelegateForFunctionPointer(funcPtr, typeof(voidMethod1));
            voidMethod(dummyClass);*/

            AssemblyName MyAssemblyName = new AssemblyName();
            MyAssemblyName.Name = "DummyAssembly";
            AssemblyBuilder MyAssemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(MyAssemblyName, AssemblyBuilderAccess.Run);
            ModuleBuilder MyModuleBuilder = MyAssemblyBuilder.DefineDynamicModule("DummyModule");
            MethodBuilder MyMethodBuilder = MyModuleBuilder.DefineGlobalMethod("DummyFunc", MethodAttributes.Public | MethodAttributes.Static, typeof(void), new Type[] { typeof(int) });
            ILGenerator IL = MyMethodBuilder.GetILGenerator();

            IL.Emit(OpCodes.Ldarg, 0);
            IL.Emit(OpCodes.Ldc_I4, funcPtr.ToInt32());

            IL.EmitCalli(OpCodes.Calli, CallingConvention.ThisCall, typeof(void), new Type[] { typeof(int) });
            IL.Emit(OpCodes.Ret);

            MyModuleBuilder.CreateGlobalFunctions();

            MethodInfo MyMethodInfo = MyModuleBuilder.GetMethod("DummyFunc");

            MyMethodInfo.Invoke(null, new object[] { dummyClass.ToInt32() });

            Console.ReadKey();
        }
    }
}

上文中的方法虽然复杂了一点,但……就是没什么用。不用怀疑!

文章写到这里,可能有童鞋就要发问了。你说这么多,tmd到底有啥用?那接下来,我举一个栗子,activex组件的直接调用!
以前,我们调用activex组件需要做很多复杂的事情,首先需要使用命令行调用regsvr32将dll注册到系统,然后回到vs去引用com组件是吧

  仔细想想,需要吗?并不需要,因为两个原因:

  • COM组件规定DLL需要给出一个DllGetClassObject函数,它就可以为我们在DLL内部new一个所需对象
  • COM组件返回的对象其实就是一个只有虚函数的C++类对象(COM组件规定属性和事件用getter/setter方式实现)
  • COM组件其实不需要用户手动注册,执行regsvr32会操作注册表,而且32位/64位会混淆,其实regsvr32只是调用了DLL导出函数DllRegisterServer,而这个函数的实现一般只是把自己注册到注册表中,这一步可有可无(特别是对于我们已经知道某个activex的dll存在路径且它能提供的服务时,如果你非要注册,使用p/invoke调用该dll的DllRegisterServer函数是一样的效果)

因此,假如我们有一个activex控件(例如vlc),我们希望把它嵌入我们程序中,我们先看看常规的做法(本文没有讨论带窗体的vlc,因为窗体这块儿又复杂一些),直接贴图:

看起来很简单,但当我们需要打包给客户使用时就很麻烦,涉及到嵌入vlc的安装程序。而当我们会动态内存调用之后,就可以不注册而使用vlc的功能,我先贴出代码:

using System;
using System.Runtime.InteropServices;

namespace ConsoleApp3
{
    class Program
    {
        [DllImport("kernel32")]
        static extern IntPtr LoadLibraryEx(string path, IntPtr hFile, int dwFlags);
        [DllImport("kernel32")]
        static extern IntPtr GetProcAddress(IntPtr dll, string func);

        delegate int DllGetClassObject(Guid clsid, Guid iid, ref IntPtr ppv);

        delegate int CreateInstance(IntPtr _thisPtr, IntPtr unkown, Guid iid, ref IntPtr ppv);

        delegate int getVersionInfo(IntPtr _thisPtr, [MarshalAs(UnmanagedType.BStr)] out string bstr);

        static void Main(string[] args)
        {
            IntPtr dll = LoadLibraryEx(@"D:\Program Files\VideoLAN\VLC\axvlc.dll", default, 8);
            IntPtr func = GetProcAddress(dll, "DllGetClassObject");
            DllGetClassObject dllGetClassObject = (DllGetClassObject)Marshal.GetDelegateForFunctionPointer(func, typeof(DllGetClassObject));

            Guid vlc = new Guid("2d719729-5333-406c-bf12-8de787fd65e3");
            Guid clsid = new Guid("9be31822-fdad-461b-ad51-be1d1c159921");
            Guid iidClassFactory = new Guid("00000001-0000-0000-c000-000000000046");
            IntPtr objClassFactory = default;
            dllGetClassObject(clsid, iidClassFactory, ref objClassFactory);
            CreateInstance createInstance = (CreateInstance)Marshal.GetDelegateForFunctionPointer(Marshal.ReadIntPtr(Marshal.ReadIntPtr(objClassFactory) + IntPtr.Size * 3), typeof(CreateInstance));
            IntPtr obj = default;
            createInstance(objClassFactory, default, vlc, ref obj);
            getVersionInfo getVersion = (getVersionInfo)Marshal.GetDelegateForFunctionPointer(Marshal.ReadIntPtr(Marshal.ReadIntPtr(obj) + IntPtr.Size * 18), typeof(getVersionInfo));
            string versionInfo;
            getVersion(obj, out versionInfo);

            Console.ReadKey();
        }
    }
}

  上文中的代码有几处可能大家不容易懂,特别是指针偏移量的运算,这里面有比较复杂的地方,文章篇幅有限,下来咱们细细研究。

  今久しぶりするプログラミングを学習するために11年の後半からは、時々私は何も予想外の展開を感じます。実際には、人生はそれほど二つのこと、愛と青春よりも何もない、私たちはキャッチを持っていることを願って、行かせてはいけません。2年前、私は女の子と一緒に持ってはいくつかの単語は、人々のCOMコンポーネントへの話は、実際には、私も、私は彼女を失ってしまったことを、今、C ++仮想関数テーブルにはわからないが持っていると言います。その葉の生命である魂をさまよってきた恐怖、半分目を覚ましのために将来、。

おすすめ

転載: www.cnblogs.com/Johness/p/csharp-do-memory-call.html