2016年5月14日 星期六

Visual Studio - How to use VB.NET or C# to call SAP RFC and return parameters and tables

在與 SAP 進行溝通時,最常用的方法即為 RFC,透過 RFC 的方式直接將需要的資料回傳或是將 SAP 需要的資訊進行上傳,以下將以 .NET 的 VB 與 C# 進行說明

事前準備,先建立好兩個要測試的 RFC

第一個是 Z_HELLO_WORLD,傳入一個 NAME,然後回傳訊息 Hello NAME,主要是進行參數的 Import 與 Export 練習


第二個 RFC Z_HELLO_TABLE,傳入一個 TABLE,裡面分別放加數與被加數,系統將兩個值算完後回傳 Table,進行練習傳入 Table 與輸出 Table

接下來進行 VB.NET 與 C# 的程式測試呼叫上面兩個 RFC

VB.NET :

使用 VB 連線 SAP RFC,需要 Import 下面三個 COM 元件
SAP Logon Unicode Control : 登入 SAP 使用
SAP Remote Function Call Unicode Control : 呼叫 RFC 使用
SAP Table Factory Unicode : 傳 Table 參數使用


Unicode 與非 Unicode 的差別在當傳入像是 "彣" 這類非 BIG5 編碼的文字時,是否能正常被 SAP 處理,所以當 SAP 是 Unicode 編碼時,建議使用 Unicode 的參考

拉一個 GUI 如下畫面

一、與SAP 進行連線

在 Form 的最上方需要 Import 上面三個參考
Imports SAPLogonCtrl
Imports SAPTableFactoryCtrl
Imports SAPFunctionsOCX

連線 SAP 需要兩個物件,分別是 SAPLogonCtrl.SAPLogonControl 與 SAPLogonCtrl.Connection,前者設定登入資訊,後者建立 Connection 並進行登入回傳是否成功,將下面 Code 放置在任一個 Button 物件的 On_Clock 事件


Dim login As New SAPLogonCtrl.SAPLogonControl
Dim conn As SAPLogonCtrl.Connection

login.ApplicationServer = "XX.XX.XX.XX"  //SAP IP
login.Client = "XXX"                     //SAP CLIENT
login.Language = "EN"                    //SAP LOGON LANGUAGE
login.User = "XXXXX"                     //USER ID
login.Password = "********"              //USER PASSWORD
login.SystemNumber = 0                   //SAP SYSTEM NUMBER
conn = CType(login.NewConnection(), SAPLogonCtrl.Connection)
If conn.Logon(0, True) Then
    MsgBox("Connected")
Else
    MsgBox("Failed")
    Exit Sub
End If

要斷線則下

conn.Logoff()

此時執行時會發生下面錯誤

此時在專案的屬性中

將 SAP Remote Fnction Call Control 與 SPA Table Factory 內嵌 Interop 類型設為 false 即可 (SAP Logon Control 的 "內嵌 Interop 類型" 設 True or False 並沒有影響)

接下來執行成功就會看到 Connected 的訊息

連線成功時,再 conn.Logoff() 前下中斷點可在 SAP SM04 看到登入的資訊

接下來進行 Button Call RFC By Parameter,基本上把下面的 Code 放在前面 conn.Logon(0, True) 與 conn.Logoff() 之間,再放在 Button "Call RFC by Parameter" Click Event


Dim login As New SAPLogonCtrl.SAPLogonControl
Dim conn As SAPLogonCtrl.Connection

login.ApplicationServer = "XX.XX.XX.XX"   //SAP IP
login.Client = "XXX"                      //SAP CLIENT
login.Language = "EN"                     //SAP LOGON LANGUAGE
login.User = "XXXXX"                      //USER ID
login.Password = "********"               //USER PASSWORD
login.SystemNumber = 0                    //SAP SYSTEM NUMBER
conn = CType(login.NewConnection(), SAPLogonCtrl.Connection)
If conn.Logon(0, True) Then
    MsgBox("Connected")
Else
    MsgBox("Failed")
    Exit Sub
End If

Dim SAP_RFC As New SAPFunctionsOCX.SAPFunctions
SAP_RFC.Connection = conn

Dim ifunc As SAPFunctionsOCX.Function = CType(SAP_RFC.Add("Z_HELLO_WORLD"), SAPFunctionsOCX.Function)

Dim parameter1 As Parameter
parameter1 = CType(ifunc.Exports("I_NAME"), Parameter)
parameter1.Value = "彣"

ifunc.Call()

Dim parameter2 As Parameter = CType(ifunc.Imports("E_MSG"), Parameter)
MsgBox(CType(parameter2.Value, String))

SAP_RFC.RemoveAll()

conn.Logoff()

因為使用的是 UNICODE 的元件,因此傳入的字串是 utf-8 的文字也可處理,主要是透過建立一個 Function 的物件 ifunc,再透過 ifunc 以 Exports/Imports Method 進行傳入值與取得值與 SAP RFC 溝通,注意 SAP 的 Import 是 VB.NET 的 Export。

接下來針對 Call RFC by Tables 同樣把下面的 Code 放在前面 conn.Logon(0, True) 與 conn.Logoff() 之間,再放在 Button "Call RFC by tables" Click Event

Dim login As New SAPLogonCtrl.SAPLogonControl
Dim conn As SAPLogonCtrl.Connection

login.ApplicationServer = "XX.XX.XX.XX"    //SAP IP
login.Client = "XXX"                       //SAP CLIENT
login.Language = "EN"                      //SAP LOGON LANGUAGE
login.User = "XXXXX"                       //USER ID
login.Password = "********"                //USER PASSWORD
login.SystemNumber = 0                     //SAP SYSTEM NUMBER
conn = CType(login.NewConnection(), SAPLogonCtrl.Connection)
If conn.Logon(0, True) Then
    MsgBox("Connected")
Else
    MsgBox("Failed")
    Exit Sub
End If

Dim SAP_RFC As New SAPFunctionsOCX.SAPFunctionsClass
SAP_RFC.Connection = conn

Dim ifunc As SAPFunctionsOCX.Function
ifunc = CType(SAP_RFC.Add("Z_HELLO_TABLE"), SAPFunctionsOCX.Function)

Dim Tables1_data As Object
Tables1_data = CType(ifunc.Tables("O_TABLE"), SAPTableFactoryCtrl.Table)

Tables1_data.Rows.Add()
Tables1_data(1, "NUM1") = "6"
Tables1_data(1, "NUM2") = "3"
Tables1_data.Rows.Add()
Tables1_data(2, "NUM1") = "8"
Tables1_data(2, "NUM2") = "0"

If ifunc.Call = True Then
    Dim dt As New DataTable
    dt.Columns.Add("NUM1")
    dt.Columns.Add("NUM2")
    dt.Columns.Add("RESULT")
    dt.Columns.Add("MSG")

    Dim dr As DataRow
    dr = dt.NewRow

    Dim Tables1_show As SAPTableFactoryCtrl.Table
    Tables1_show = CType(ifunc.Tables("O_TABLE"), SAPTableFactoryCtrl.Table)
    dr("NUM1") = CType(Tables1_data(1, "NUM1"), String)
    dr("NUM2") = CType(Tables1_data(1, "NUM2"), String)
    dr("RESULT") = CType(Tables1_data(1, "RESULT"), String)
    dr("MSG") = CType(Tables1_data(1, "MSG"), String)
    dt.Rows.Add(dr)

    dr = dt.NewRow
    dr("NUM1") = CType(Tables1_data(2, "NUM1"), String)
    dr("NUM2") = CType(Tables1_data(2, "NUM2"), String)
    dr("RESULT") = CType(Tables1_data(2, "RESULT"), String)
    dr("MSG") = CType(Tables1_data(2, "MSG"), String)
    dt.Rows.Add(dr)

    DataGridView1.DataSource = dt
Else
    MsgBox("Error")
    Exit Sub
End If

Tables1_data.FreeTable()
SAP_RFC.RemoveAll()

conn.Logoff()

此段程式是傳入加數與被加數,由 SAP RFC 把 NUM1 + NUM2 放到 Result 後再 MSG 填入 Done,回傳後再放到 DataGridView 上。

以上即 VB.NET 與 RFC 溝通的例子,接下來說明的是 C#

C# :

基本上 C# 在 Parameter 與取得 Table 沒有什麼太大的問題,但最大的問題是在傳入 Table 參數時,使用 AppendGridData 一次只能填入 Table 其中一欄值就要換一筆,而使用 Tables1_data.Rows.Add() 再對欄位填入值時會發生 Exception,先進行一般語法的說明

與 VB.NET 拉一樣的 GUI 畫面

C# 同樣要 Import 三個 COM 元件
SAP Logon Unicode Control : 登入 SAP 使用
SAP Remote Function Call Unicode Control : 呼叫 RFC 使用
SAP Table Factory Unicode : 傳 Table 參數使用
(圖片請參考上面 VB.NET)

C# 在最上方引用使用的是
using SAPLogonCtrl;
using SAPFunctionsOCX;
using SAPTableFactoryCtrl;

引用後一定會有 Interop 的異常要在屬性視窗設為 False

CALL RFC BY PARAMETER CODE 如下

SAPLogonCtrl.SAPLogonControlClass logon = new SAPLogonCtrl.SAPLogonControlClass();
SAPLogonCtrl.Connection Conn;

logon.ApplicationServer = "XXX.XXX.XXX.XXX";    //SAP系统IP
logon.Client = "XXX";                           //SAP客户端号
logon.Language = "EN";                          //SAP登陆语言
logon.User = "XXXXXX";                          //用户帐号
logon.Password = "******";                      //用户密码
logon.SystemNumber = 0;                         //SAP系统编号
Conn = (SAPLogonCtrl.Connection)logon.NewConnection();
if (Conn.Logon(0, true))
{
    MessageBox.Show("good job");
}

SAPFunctionsOCX.SAPFunctionsClass SAP_RFC = new SAPFunctionsOCX.SAPFunctionsClass();
SAP_RFC.Connection = Conn;

SAPFunctionsOCX.Function ifunc = (SAPFunctionsOCX.Function)SAP_RFC.Add("Z_HELLO_WORLD");

Parameter parameter1 = (Parameter)ifunc.get_Exports("I_NAME");
parameter1.Value = "Eric";

if (ifunc.Call())
{
    Parameter parameter2 = (Parameter)ifunc.get_Imports("E_MSG");
    MessageBox.Show((String)parameter2.Value);
}

SAP_RFC.RemoveAll();

Conn.Logoff();

基本上只是在宣告與部份語法的差別,大致上無太多差異

CALL RFC BY TABLE CODE 如下,但這邊出了一個很大的問題,原 VB.NET 的 Tables1_data.Rows.Add() 與 Tables1_data(1, "NUM1") = "6" 語法不能使用,而使用 Tables1_data.AppendGridData(1, 1, 1, 1); 一次只能對一個欄位新增一筆資料

SAPLogonCtrl.SAPLogonControlClass logon = new SAPLogonCtrl.SAPLogonControlClass();
SAPLogonCtrl.Connection Conn;

logon.ApplicationServer = "XXX.XXX.XXX.XXX"; //SAP IP
logon.Client = "XXX";                        //SAP CLIENT
logon.Language = "EN";                       //SAP LOGON LANGUAGE
logon.User = "XXXXXX";                       //USER ID
logon.Password = "******";                   //USER PASSWORD
logon.SystemNumber = 0;                      //SAP SYSTEM NUMBER
Conn = (SAPLogonCtrl.Connection)logon.NewConnection();
if (Conn.Logon(0, true))
{
    MessageBox.Show("Connected");
}

SAPFunctionsOCX.SAPFunctionsClass SAP_RFC = new SAPFunctionsOCX.SAPFunctionsClass();
SAP_RFC.Connection = Conn;

SAPFunctionsOCX.Function ifunc = (SAPFunctionsOCX.Function)SAP_RFC.Add("Z_HELLO_TABLE");

SAPTableFactoryCtrl.Tables Tables1 = (SAPTableFactoryCtrl.Tables)ifunc.Tables;
SAPTableFactoryCtrl.Table Tables1_data = (SAPTableFactoryCtrl.Table)Tables1.get_Item("O_TABLE");

//這邊會出現題,變成寫入 Table 中的第一個參數放 1,就新增一筆,其最後 Table 只有第一欄有值
Tables1_data.AppendGridData(1, 1, 1, 1);
Tables1_data.AppendGridData(1, 1, 1, 2);

if (ifunc.Call())
{
    DataTable dt = new DataTable();
    dt.Columns.Add("NUM1", typeof(string));
    dt.Columns.Add("NUM2", typeof(string));
    dt.Columns.Add("RESULT", typeof(string));
    dt.Columns.Add("MSG", typeof(string));

    Tables1 = (Tables)ifunc.Tables;
    Table Talbe1_show = (Table)Tables1.get_Item("O_TABLE");

    dt.Rows.Add(Talbe1_show.get_Cell(1, 1).ToString(),
                Talbe1_show.get_Cell(1, 2).ToString(),
                Talbe1_show.get_Cell(1, 3).ToString(),
                Talbe1_show.get_Cell(1, 4).ToString());

    dt.Rows.Add(Talbe1_show.get_Cell(2, 1).ToString(),
                Talbe1_show.get_Cell(2, 2).ToString(),
                Talbe1_show.get_Cell(2, 3).ToString(),
                Talbe1_show.get_Cell(2, 4).ToString());

    Tables1_data.FreeTable();

    dataGridView1.DataSource = dt;
}

Conn.Logoff();

產生的結果變成只能傳入第一個參數,被加數都會是 0,在筆者寫到此時並未看到 C# 有什麼方法可直接傳入 Table 參數

SOLUTION : 可透過 C# 加入 VB.NET 的類別庫產生的 dll 解決上面的問題

使用 Visual Studio 建立 VB 的類別庫


使用預設的命名規則
再加入以下 Code 於程式中 (直接全部取代即可),此 VB.NET 程式仍要 imports 之前加入的 SAP 相關 dll
下面的程式其實同之前 VB.NET 從 Connection 的建立,到產生 RFC 和 Table 相關物建,到取回資料到 DataTable,最後 Disconnection 的程式,之後 C# 只是傳入登入資訊以及要加回資料的 DataTable

Imports SAPFunctionsOCX
Imports SAPLogonCtrl
Imports SAPTableFactoryCtrl

Public Class Class1

    Function CALL_RFC(ByVal strIP As String, ByVal strClient As String, ByVal strLanguage As String,
                      ByVal strID As String, ByVal strPassword As String, ByVal strSystemnumber As String,
                      ByRef dt As DataTable) As Boolean
        CALL_RFC = True

        Dim conn As SAPLogonCtrl.Connection
        Dim login As New SAPLogonCtrl.SAPLogonControlClass()
        login.ApplicationServer = strIP
        login.Client = strClient
        login.Language = strLanguage
        login.User = strID
        login.Password = strPassword
        login.SystemNumber = strSystemnumber
        conn = CType(login.NewConnection(), SAPLogonCtrl.Connection)
        If conn.Logon(0, True) Then
        Else
            CALL_RFC = False
            Exit Function
        End If

        Dim SAP_RFC As New SAPFunctionsOCX.SAPFunctionsClass
        SAP_RFC.Connection = conn

        Dim ifunc As SAPFunctionsOCX.Function
        ifunc = CType(SAP_RFC.Add("Z_HELLO_TABLE"), SAPFunctionsOCX.Function)

        Dim Tables1_data As Object
        Tables1_data = CType(ifunc.Tables("O_TABLE"), SAPTableFactoryCtrl.Table)


        Tables1_data.Rows.Add()
        Tables1_data(1, "NUM1") = "6"
        Tables1_data(1, "NUM2") = "3"
        Tables1_data.Rows.Add()
        Tables1_data(2, "NUM1") = "8"
        Tables1_data(2, "NUM2") = "1"

        If ifunc.Call = True Then
            dt.Columns.Add("NUM1")
            dt.Columns.Add("NUM2")
            dt.Columns.Add("RESULT")
            dt.Columns.Add("MSG")

            Dim dr As DataRow
            dr = dt.NewRow

            Dim Tables1_show As SAPTableFactoryCtrl.Table
            Tables1_show = CType(ifunc.Tables("O_TABLE"), SAPTableFactoryCtrl.Table)
            dr("NUM1") = CType(Tables1_data(1, "NUM1"), String)
            dr("NUM2") = CType(Tables1_data(1, "NUM2"), String)
            dr("RESULT") = CType(Tables1_data(1, "RESULT"), String)
            dr("MSG") = CType(Tables1_data(1, "MSG"), String)
            dt.Rows.Add(dr)

            dr = dt.NewRow
            dr("NUM1") = CType(Tables1_data(2, "NUM1"), String)
            dr("NUM2") = CType(Tables1_data(2, "NUM2"), String)
            dr("RESULT") = CType(Tables1_data(2, "RESULT"), String)
            dr("MSG") = CType(Tables1_data(2, "MSG"), String)
            dt.Rows.Add(dr)

            Tables1_show.FreeTable()
        Else
            CALL_RFC = False
            Exit Function
        End If

        Tables1_data.FreeTable()
        SAP_RFC.RemoveAll()

        conn.Logoff()
    End Function
End Class

建擇建置方案,並確認建置無任何錯誤訊息


在該 Project 下即可找到該建置後的 dll 檔,其中 ClassLibrary1.dll 要放置到之後 C# 要引用 VB.NET 的 Project 下,其他的 dll 則要放在,而其他 VB.NET 引用的 SAP dll 檔則要放在 C# 建置後執行檔的 bin\debug 目錄下
VB.NET 的 dll 要放在 c# 的 Project 目錄之後提供 C# 參考加入此 dll
而 VB.NET class 產生的 SAP dll 要放在bin\debug 下,因為 C# 基
本上是透過 VB.NET 來呼叫 SAP,並沒有引用SAP 的 dll,不會產
生在 bin\debug 目錄,沒放會造成 VB.NET dll 呼叫時失敗

接下來回到 C# Project 中,引用該 VB.NET 的 dll,在 Project 的參考加入 dll
使用瀏覽加入之前建置的 dll,並在 C# using 之前 VB.NET 產生的 ClassLibrary1;
將前面 C# CALL RFC BY TABLE CODE Code 變動如下
P.S. 因為傳入的參數是寫死在上方 VB.NET 裡,基本上可以考慮使用 C# 透過 DataTable 傳入 VB.NET 建立的 dll,即可達成依需要的參數透過 VB.NET call SAP RFC
private void button2_Click(object sender, EventArgs e)
{
    Class1 VB_RFC_CONNECT = new Class1();
    DataTable dt1 = new DataTable();
    Boolean result =
        VB_RFC_CONNECT.CALL_RFC("SAP IP", "SAP Client", "Language", "SAP ID", "SAP PASSWORD", "SAP SYSTEM ID", ref dt1);

    GridView1.DataSource = dt1;
    GridView1.Visible = true;
}

最後即可透過 C# 透過 VB.NET dll 進行 Call SAP RFC 並 import Table 參數
後記 : 雖然 VB.NET 就可以達成,但有時公司限制使用 C# 開發 Project 時,C# 呼叫 SAP 的元件會發生 Table 無法 import 的問題時,則只好退而求其次使用 C# call VB.NET dll 來進行

3 則留言:

  1. 哇~你好棒棒~會好多工具~這些非sap的工具都是你自己study的嗎? 還是本身就用了~
    公司完全禁用~你們好free
    從完全不會~要進入到這樣的開發~這門檻....
    排版很舒服~偶爾來看貼文~心情都會變好^^

    回覆刪除
    回覆
    1. 公司有要求使用專門的工具啦,只是私下喜歡嘗試其他工具,這都是在 IDES Server 上嘗試,就比較不受限制 ... XD

      刪除
  2. 好強大丫~ 什麼都難不倒喔~
    公司很窮~啥都沒升級~也不想花錢~IDES環境也沒有~
    寫了一陣子abap 都沒摸其他的~突然要再來學原本不會的的工具和其他語言~ 覺得困難重重~完全跟不上丫~
    有看到你有篇python 文章的分享也很棒~ 實例說的很清楚 ~雖然也還不會..XD~ 但有時間也想來study~
    希望有機會可以看到你更多 none-sap tool 或與sap介接的學習經驗的分享~^^

    回覆刪除

How to install & specified python version or distreibtuion package version in google colab

在買了 RTX 3080 要來 挖礦...  嗯~是跑機器學習後,結果發現了 GOOGLE COLAB,其實之前在「GAN 對抗式生成網路」一書就有看到,但資訊人就是什麼都想自己安裝,在本機用 Anaconda + pyCharm 弄了 GPU 環境,結果有天從新竹回家發現家裡沒...