.net下com呼叫支援x86/x64

沉迷程式碼的萌新發表於2020-12-22

起因

專案涉及u3d/wpf端的渲染圖形合成,採用了開源專案spout,為了便捷,採用了spout的com版本作為c#端的呼叫
專案調整後,細節已經捋清楚了。
但是考慮桌面應用採用anypc,根據執行環境自動切換x86/x64,就不想硬編碼繫結執行環境了。
故選項後採用 程式選擇anypc,執行時動態判斷x86/x64,載入對應的com元件
封裝代理層,代理層呼叫對應的x86/x64 com pinvoke

封裝分析

專案 LightjamsSpout
我們需要封裝的部分是

GLContext和LightjamsSpoutReceiver
都是com介面,硬編碼直接例項化即可,動態呼叫時,不能例項化介面,需要通過反射拿到型別上面的特性 CoClassAttribute
通過獲取 CoClassAttribute.CoClass 拿到實際型別去例項化
將x86/x64的庫分別放在不同的路徑,通過判斷 IntPtr.Size == 4/!Environment.Is64BitProcess 判斷是否是x86,反之則為x64
通過專案的引用,把com元件轉換成 Interop.xx.dll ,路徑為專案檔案/obj/Debug,專案動態載入實際上是這個 Interop.xx.dll

程式碼實現

代理端動態載入Assmebly

            loadDirectory = Path.Combine(Directory.GetCurrentDirectory(), IntPtr.Size == 4 ? "x86_Spout" : "x64_Spout");//LightjamsSpoutPS.dll
            var loadFile = Path.Combine(loadDirectory, "Interop.LightjamsSpoutLib.dll");

            assembly = Assembly.LoadFile(loadFile);

            var type = assembly.GetType("LightjamsSpoutLib.LightjamsSpoutReceiver");
            var coClassAttribute = type.GetCustomAttribute<CoClassAttribute>();

            try
            {
                instance = Activator.CreateInstance(coClassAttribute.CoClass);
            }
            catch (Exception e)
            {
                RegisterCom(loadDirectory);
                instance = Activator.CreateInstance(coClassAttribute.CoClass);
            }

通過表示式樹構建例項的方法
比如LightjamsSpoutReceiver.Connect

        delegate string ConnectInvoke(string senderName, out int width, out int height);

        private ConnectInvoke CreateConnect()
        {
            var senderNameParam = Expression.Parameter(typeof(string), "senderName");
            var widthParam = Expression.Parameter(typeof(int).MakeByRefType(), "width");
            var heightParm = Expression.Parameter(typeof(int).MakeByRefType(), "height");

            var instanceExp = Expression.Constant(instance);
            var method = instance.GetType().GetMethod("Connect");

            var callExp = Expression.Call(instanceExp, method, new Expression[]{ senderNameParam, widthParam, heightParm });

            return Expression.Lambda<ConnectInvoke>(callExp, new ParameterExpression[]{ senderNameParam, widthParam, heightParm }).Compile();
        }

值得一提的是type.MakeByRefType是生成type對應的引用型別
畢竟定義是

public virtual string Connect(string senderName, out int width, out int height)

型別轉換

有一個函式ReceiveImage,引數呼叫了com內部型別

public virtual void ReceiveImage(System.Array bytes, LightjamsSpoutLib.EPixelFormat format)

LightjamsSpoutLib.EPixelFormat是com內部的自定義列舉型別
根據內容,做一個列舉型別

    public enum NePixelFormat
    {
        BGR,
        RGB
    }

表示式樹的引數2改為int,內部轉換 Expression.Convertint 轉換成對應的 LightjamsSpoutLib.EPixelFormat
呼叫函式的引數2改為NePixelFormat,內部呼叫委託時,轉換成int即可

        public void ReceiveImage(System.Array bytes, NePixelFormat format)
        {
            _receiveImage.Invoke(bytes, (int)format);
        }

相關文章