Developing .Net C# COM dll for C++

之前在這一篇有簡短提到過,沒想到事隔四五年又遇到要寫這樣程式的機會,順便整理一下吧。

Using .Net dll (class library) in *MANAGED* C++ program

如果是managed C++,那其實只要把.Net dll (class library)加入 reference 就可以了。(如下面兩張圖)
01

02
而.Net dll (class library)不需要多做什麼處理,就跟寫給其他.Net程式用的寫法即可。

namespace NMASNG
{
    public class sng
    {
        public sng()
        {
        }

        public string _HDD()
        {
            char[] _ret;
            string tmp = "this is test";
            return tmp;
        }

        public string _BaseBoard()
        {
            return "haha";
        }

        public int _gInt()
        {
            return 10;
        }
    }
}

比較不一樣的是在 C++ 這邊的寫法

using namespace NMASNG;
using namespace std;
#include <msclr/marshal_cppstd.h>

int _tmain(int argc, _TCHAR* argv[])
{
	NMASNG::sng^ p = gcnew NMASNG::sng();
	int x = p->_gInt();
	std::string xs =msclr::interop::marshal_as< std::string >( p->_HDD() );

	cout << x << endl;
	cout << xs << endl;

	return 0;
}

這樣子再編譯 c++ 程式的時候,.Net Framework 也會進來參一腳,意即編譯出來的 .exe 不是單純的 win32 程式...

Using .Net dll (class library) in *UN-MANAGED* C++ program

有些時候"純 win32"來開發程式還是有必要的,而在這樣的環境限制下,就不可能呼叫 .Net dll (class library),而必須用傳統的 Win32 COM DLL。

所以在撰寫 .Net Class library 時,就必需要建立 COM 物件介面。

using System.Runtime.InteropServices;

namespace NMASNG
{
    // Interface declaration.
    [Guid("47F555AC-AB17-437a-AF2C-9F869EC589C0")]
    public interface I_Nmasng
    {
        [DispId(1)]
        string _HDD();

        [DispId(2)]
        string _BaseBoard();

        [DispId(3)]
        int _gInt();
    }

    [Guid("4605A8CD-B0D2-4766-A31B-8E51FF3928CE"),
    InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
    public interface I_Nmasng_Events
    {
    }
    
    // Interface implementation.
    [Guid("8ADEC2A9-FE99-4d11-8F34-5DB75D70F146"),
    ClassInterface(ClassInterfaceType.None),
    ComSourceInterfaces(typeof(I_Nmasng_Events))]
    public class sng : I_Nmasng
    {
        public sng()
        {
        }

        public string _HDD()
        {
            char[] _ret;
            string tmp = "this is test";
            return tmp;
        }

        public string _BaseBoard()
        {
            return "haha";
        }

        public int _gInt()
        {
            return 10;
        }
    }
}

GUID 的產生方式,可以用guidgen.exe這個程式來產生。(如下面兩張圖)
03

04
(guidgen.exe 在 C:\Program Files (x86)\Microsoft Visual Studio 9.0\Common7\Tools 或類似路徑)

另外,要在project properties做如以下三張圖片的設定。其中 Build Events 裡頭的 Post-build 是為了幫助跟系統註冊 dll 檔案。而Register for COM interop的選項是為了產生出COM物件,而最重要的是要Sign the assembly。
06

05

07

在 Sign the assembly 時,需要有個 .snk 檔案來儲存 strong name key。這個 .snk 檔案的產生方式為執行

sn -k ooxx.snk

(sn.exe 在 C:\Program Files\Microsoft SDKs\Windows\v6.0A\Bin 或類似的路徑)

如此一來,build這個 project 會得到帶有 COM 介面的 Class Library (.dll 跟 .tlb 檔案)

在 c++ 程式中,則要寫成這樣

#import "C:\_eapple\Release\NMASNG.tlb"
using namespace NMASNG;
using namespace std;

int _tmain(int argc, _TCHAR* argv[])
{
	NMASNG::I_NmasngPtr p(__uuidof(NMASNG::sng));
	I_Nmasng *ptr = p;
	cout << ptr->_gInt() << endl;
	cout << ptr->_HDD() << endl;

	return 0;
}

然而,compile出來的 .exe 可以順利執行是因為有 .dll 與 .tlb,更重要的是前面我們已經跟系統註冊過 dll 檔案了。所以,如果要把這個程式搬到其他電腦上面用,也需要重新註冊 dll,這部分我建議可以寫成 batch,或者是包安裝程式內,讓使用者在在安裝時就順便做dll註冊。

@echo off
@regasm.exe /tlb /codebase YOUR_DLL_FILE.dll
@YOUR_EXEC_FILE.exe