C#获取dll自身路径

            //获取dll的绝对路径,请根据不同情况自己选用
            MessageBox.Show(System.Reflection.Assembly.GetExecutingAssembly().Location);
            MessageBox.Show(System.Reflection.Assembly.GetEntryAssembly().Location);   
            MessageBox.Show(System.Windows.Forms.Application.ExecutablePath);
            MessageBox.Show(System.Windows.Forms.Application.StartupPath);
            MessageBox.Show(System.Environment.CurrentDirectory);
            MessageBox.Show(System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName);

C#枚举窗口句柄

在CS程序中启动其他应用后,要获取进程的主窗体其实很简单:

        Process p = Process.Start(exePath);
        //p.WaitForInputIdle();
        p.Refresh(); 
        IntPtr mainWnd = p.MainWindowHandle;

但是,总有很多特殊的情况,上面的方法根本无法用,所以,要用Windows API来搞定了

1、如果窗口信息很固定而且没有重名的话,可以用Findwindow搞定

        [DllImport("User32.dll", EntryPoint = "FindWindow")]
        public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
        IntPtr clientWnd = FindWindow(null,"FormClient");

2、根据标题枚举窗口句柄

        //枚举窗体
        [DllImport("User32.dll", EntryPoint = "EnumWindows", SetLastError = true)]
        public static extern bool EnumWindows(WNDENUMPROC lpEnumFunc, uint lParam);
        //获取窗体标题
        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        static extern int GetWindowText(IntPtr hWnd, StringBuilder lpText, int nCount);
        //设置错误状态
        [DllImport("kernel32.dll", EntryPoint = "SetLastError")]
        public static extern void SetLastError(uint dwErrCode);

        //线程主窗口句柄
        private static IntPtr processMainWnd = IntPtr.Zero;
        //要查找的窗口名称
        private static String winTitle = "__Web__Form__Main__";

        //声明委托函数
        public delegate bool WNDENUMPROC(IntPtr hwnd, uint lParam);

        //枚举进程句柄,非线程安全
        [SecuritySafeCritical]
        public static IntPtr GetCurrentWindowHandle()
        {
            IntPtr ptrWnd = IntPtr.Zero;

            bool bResult = EnumWindows(new WNDENUMPROC(EnumWindowsProc), (uint)0);
            if (!bResult && Marshal.GetLastWin32Error() == 0)
            {
                ptrWnd = processMainWnd;
            }

            return ptrWnd;
        }

        //枚举函数,获取主窗口句柄然后退出
        [SecuritySafeCritical]
        private static bool EnumWindowsProc(IntPtr hwnd, uint lParam)
        {
            //根据标题获取窗体
            var sb = new StringBuilder(50);
            GetWindowText(hwnd, sb, sb.Capacity);

            if (winTitle.Equals(sb.ToString()))
            {
                processMainWnd = hwnd;
                SetLastError(0);
                return false;
            }

            return true;
        }

3、跟进进程id枚举获取主窗体句柄

        //枚举窗体
        [DllImport("User32.dll", EntryPoint = "EnumWindows", SetLastError = true)]
        //获取父窗体
        [DllImport("user32.dll", EntryPoint = "GetParent", SetLastError = true)]
        //根据窗口获取线程句柄
        [DllImport("User32.dll", CharSet = CharSet.Auto)]
        public static extern int GetWindowThreadProcessId(IntPtr hwnd, out uint pid);
        //设置错误状态
        [DllImport("kernel32.dll", EntryPoint = "SetLastError")]
        public static extern void SetLastError(uint dwErrCode);

        //线程主窗口句柄
        private static IntPtr processMainWnd = IntPtr.Zero;

        //枚举进程句柄
        [SecuritySafeCritical]
        public static IntPtr GetCurrentWindowHandle(int processId)
        {
            IntPtr ptrWnd = IntPtr.Zero;

            bool bResult = EnumWindows(new WNDENUMPROC(EnumWindowsProc), (uint)processId);
            if (!bResult && Marshal.GetLastWin32Error() == 0)
            {
                ptrWnd = processMainWnd;
            }

            return ptrWnd;
        }

        //枚举函数,获取主窗口句柄然后退出
        [SecuritySafeCritical]
        private static bool EnumWindowsProc(IntPtr hwnd, uint lParam)
        {
            uint uiPid = 0;
            //根据启动方式不同,这里要有相应修改
            if (GetParent(hwnd) == IntPtr.Zero)
            {
                GetWindowThreadProcessId(hwnd, out uiPid);
                if (uiPid == lParam)
                {
                    processMainWnd = hwnd;
                    SetLastError(0);
                    return false;
                }
            }
        }

4、跟进窗口WindowsClassName举获取主窗体句柄

        //查找桌面
        [DllImport("User32.dll", ExactSpelling = true, CharSet = CharSet.Ansi, SetLastError = true)]
        public static extern IntPtr GetDesktopWindow();
        //获取窗体句柄
        [DllImport("User32.dll", CharSet = CharSet.Auto, SetLastError = true, ExactSpelling = true)]
        public static extern IntPtr GetWindow(IntPtr hwnd, int wFlag);
        //获取窗体类
        [DllImport("User32.dll", CharSet = CharSet.Auto)]
        public static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);

        public const int GW_CHILD = 5;
        public const int GW_HWNDNEXT = 2;

        //枚举桌面窗口,根据WindowsClassName获取句柄
        [SecuritySafeCritical]
        public static IntPtr GetWinHandleByClasName(String windowClassNmae)
        {   
            //取得桌面窗口,再获取第一个子窗口
            IntPtr hwnd = GetDesktopWindow();
            hwnd = GetWindow((IntPtr)hwnd, System.Convert.ToInt32(GW_CHILD));
            
            //通过循环来枚举所有的子窗口
            while ((long)hwnd != 0)
            {
                System.Text.StringBuilder ClassName = new System.Text.StringBuilder(256);
                GetClassName((IntPtr)hwnd, ClassName, ClassName.Capacity);
                String tmpClassName= ClassName.ToString().Trim();
                if (tmpClassName.Length > 0)
                {
                    if (tmpClassName.Equals(windowClassNmae))
                    {
                        break;
                    }
                }

                hwnd = GetWindow((IntPtr)hwnd, System.Convert.ToInt32(GW_HWNDNEXT));
            }

            return hwnd;
        }

使用WebBrower修改网页iFrame中的内容

由于很变态的中国式需求,我们需要在自己的网站中,嵌入第三方厂商的网站。
而这个第三方的网站中,使用了几层的iFrame,同时使用了基于IE的Activex控件。
由于双方的控件风格差距太大,所以需要替换CSS样式,同时要删掉部分与我们网站冲突的内容。

想了几种方法:
1、说服对方进行调整,但经过接触,了解到其超级不靠谱,只能靠自己了。
2、直接通过iFrame嵌入对方网站,用JS进行修改。可是跨站修改,浏览器不同意啊。
3、自己写个控件,嵌入到自己网页中,在控件中调用对方URL,并通过WebBrowser控件修改CSS及HTML。问题是,IE中,不能再直接嵌入一个WebBrowser控件,主要出于安全考虑。(如果IE允许这样操作的话,我只需要在WebBrowser中指向同一个URL,就可以无限调用,耗光资源,其他的事情,有太多可以做了)自己用很挫的方法实现了这个功能,后来考虑到稳定性,放弃了。
4、使用非基于IE的Web插件,在控件中调用对方URL,并通过控件修改CSS及HTML。比如Flash或者CefSharp,但问题是,非IE核心如何调用Activex控件啊。
5、限制入口,基于WebBrowser定制IE浏览器

最后使用了方法5,说实话,真烦。

这里把通过WebBrowser修改CSS及HTML的方法说一下,下面是我写的一段测试用代码,请按自己的实际情况修改:

        //替换样式
        //webMain是WebBrowser控件的名称
        //NewStyle.css中是要替换的样式
        private void modifyStyle()
        {
            int frameNum = webMain.Document.Window.Frames.Count;
            if (frameNum == 2)
            {
                IHTMLDocument2 leftIFrame = webMain.Document.Window.Frames[0].Document.DomDocument as IHTMLDocument2;

                int length = leftIFrame.styleSheets.length;
                IHTMLStyleSheet styleSheet = leftIFrame.createStyleSheet(@"", length + 1);

                TextReader reader = new StreamReader(Path.Combine(Path.GetDirectoryName(Application.ExecutablePath), "NewStyle.css"));
                string style = reader.ReadToEnd();
                styleSheet.cssText = style;
            }
        }

        //删除tree节点下的div,只保留特定的一个节点
        private void delDiv(String flowid)
        {
            int frameNum = webMain.Document.Window.Frames.Count;
            if (frameNum == 2)
            {
                IHTMLDocument3 leftIFrame = webMain.Document.Window.Frames[0].Document.DomDocument as IHTMLDocument3;
                foreach (dynamic e in leftIFrame.getElementById("tree").children)
                {
                    String ret = e.InnerText;
                    if (!ret.Contains(flowid))
                    {
                        e.parentNode.removeChild(e);
                    }
                }
            }
        }

如何在WinForm程序中测试C#写的COM控件

在网上查了一下,应该是不能直接在WinForm中使用C#写的COM控件。
但可以用变通的方法测试COM控件中的方法,比如,下面的例子中,就可以测试addTest方法:

Type comType = null;
object comObject = null;

//这个无效,不知道为什么
//comType = Type.GetTypeFromProgID("XX.CSControl.TestControl");
if (comType == null)
{
    Guid guid = new Guid("4B4D1D4C-16CA-48E0-87A4-AFF3C6CB6E26");
    comType = Type.GetTypeFromCLSID(guid);
}

try
{
    comObject = Activator.CreateInstance(comType);
}
catch(Exception e)
{
    MessageBox.Show(e.Message);
    return;
}

object[] args = new object[2];
args[0] = 1;
args[1] = 2;

int ret = (int)comType.InvokeMember("addTest", BindingFlags.InvokeMethod, null, comObject, args);
if(ret==3)
{
    MessageBox.Show("TestOK");
}

C#开发COM组件(Web)

这两天由于一个很挫的需求,准备开发一个COM组件,由于所有用户的客户端都安装有.Net Framework 4.0,加上工期紧的很,就准备用C#上了。

1、首先,建立一个Windows Forms Control Library项目

2、项目属性,Build,选中,Register for COM interop

3、项目属性,Signing,选中,Sign the assembly,并生成一个签名文件

4、编辑AssemblyInfo.cs,查看是否有下面两行,没有要添加

using System.Security;

[assembly: AllowPartiallyTrustedCallers()] 
[assembly: ComVisible(true)]

5、添加接口文件IObjectSafety.cs

using System;
using System.Runtime.InteropServices;

namespace CSControl
{
    //不要修改GUID
    [ComImport]
    [Guid("CB5BDC81-93C1-11CF-8F20-00805F2CD064")]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    public interface IObjectSafety
    {
        [PreserveSig]
        int GetInterfaceSafetyOptions(ref Guid riid,
                      [MarshalAs(UnmanagedType.U4)] ref int pdwSupportedOptions,
                      [MarshalAs(UnmanagedType.U4)] ref int pdwEnabledOptions);

        [PreserveSig()]
        int SetInterfaceSafetyOptions(ref Guid riid,
                      [MarshalAs(UnmanagedType.U4)] int dwOptionSetMask,
                      [MarshalAs(UnmanagedType.U4)] int dwEnabledOptions);
    }
}

6、控件类要同时实现UserControl, IObjectSafety两个接口

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Reflection;
using System.IO;
using System.Diagnostics;
using System.Threading;
using System.Security;

namespace CSControl
{
    //更改为你自己的UUID
    [ComVisible(true)]
    [Guid("4B4D1D4C-16CA-48E0-87A4-AFF3C6CB6E26")]
    [ProgId("XX.CSControl.TestControl")]
    public partial class TestControl : UserControl, IObjectSafety
    {
        //===================================================
        //实现IObjectSafety接口
        //不要修改下面的GUID
        private const string _IID_IDispatch = "{00020400-0000-0000-C000-000000000046}";
        private const string _IID_IDispatchEx = "{a6ef9860-c720-11d0-9337-00a0c90dcaa9}";
        private const string _IID_IPersistStorage = "{0000010A-0000-0000-C000-000000000046}";
        private const string _IID_IPersistStream = "{00000109-0000-0000-C000-000000000046}";
        private const string _IID_IPersistPropertyBag = "{37D84F60-42CB-11CE-8135-00AA004BB851}";

        private const int INTERFACESAFE_FOR_UNTRUSTED_CALLER = 0x00000001;
        private const int INTERFACESAFE_FOR_UNTRUSTED_DATA = 0x00000002;
        private const int S_OK = 0;
        private const int E_FAIL = unchecked((int)0x80004005);
        private const int E_NOINTERFACE = unchecked((int)0x80004002);

        private bool _fSafeForScripting = true;
        private bool _fSafeForInitializing = true;

        public int GetInterfaceSafetyOptions(ref Guid riid,
                             ref int pdwSupportedOptions,
                             ref int pdwEnabledOptions)
        {
            int Rslt = E_FAIL;

            string strGUID = riid.ToString("B");
            pdwSupportedOptions = INTERFACESAFE_FOR_UNTRUSTED_CALLER | INTERFACESAFE_FOR_UNTRUSTED_DATA;
            switch (strGUID)
            {
                case _IID_IDispatch:
                case _IID_IDispatchEx:
                    Rslt = S_OK;
                    pdwEnabledOptions = 0;
                    if (_fSafeForScripting == true)
                        pdwEnabledOptions = INTERFACESAFE_FOR_UNTRUSTED_CALLER;
                    break;
                case _IID_IPersistStorage:
                case _IID_IPersistStream:
                case _IID_IPersistPropertyBag:
                    Rslt = S_OK;
                    pdwEnabledOptions = 0;
                    if (_fSafeForInitializing == true)
                        pdwEnabledOptions = INTERFACESAFE_FOR_UNTRUSTED_DATA;
                    break;
                default:
                    Rslt = E_NOINTERFACE;
                    break;
            }

            return Rslt;
        }

        public int SetInterfaceSafetyOptions(ref Guid riid,
                             int dwOptionSetMask,
                             int dwEnabledOptions)
        {
            int Rslt = E_FAIL;

            string strGUID = riid.ToString("B");
            switch (strGUID)
            {
                case _IID_IDispatch:
                case _IID_IDispatchEx:
                    if (((dwEnabledOptions & dwOptionSetMask) == INTERFACESAFE_FOR_UNTRUSTED_CALLER) &&
                         (_fSafeForScripting == true))
                        Rslt = S_OK;
                    break;
                case _IID_IPersistStorage:
                case _IID_IPersistStream:
                case _IID_IPersistPropertyBag:
                    if (((dwEnabledOptions & dwOptionSetMask) == INTERFACESAFE_FOR_UNTRUSTED_DATA) &&
                         (_fSafeForInitializing == true))
                        Rslt = S_OK;
                    break;
                default:
                    Rslt = E_NOINTERFACE;
                    break;
            }

            return Rslt;
        }

        //===================================================
        //实现UserControl接口
        ......
    }
}

7、编译,控件会自动注册

8、写个网页,测试一下

<!--CSControl-->
<object id="testControl" width="500" height="500" classid ="clsid:4B4D1D4C-16CA-48E0-87A4-AFF3C6CB6E26"&#93;</object>

9、打CAB包,发布到网站
由于是自己生产的签名文件,所以会有安全提示。最简单的方法是从靠谱的CA那里买一个证书,还有办法就是将证书导入到IE的信任列表中,再就是将IE的安全级别降为最低。

10、当然也可以通过命令行本地注册

rem 注册
regtlibv12 %PATH_TO_DLL_AND_TLB_FOLDER%\CSControl.tlb

rem 反注册
regtlibv12 -u %PATH_TO_DLL_AND_TLB_FOLDER%\CSControl.tlb

C#开发COM组件(本地)

本篇文章介绍了如何用C#写一个简单的COM组件(Dll)。

1、新建一个C# Class Library项目“CSCOMTest”

2、项目中,新增一个接口文件IJustATestCOM.cs,Guid要自己生成

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;

namespace CSCOMTest
{
    [ComVisible(true)]
    [InterfaceType(ComInterfaceType.InterfaceIsDual)]
    [Guid("A5377871-3334-4679-A3CD-84312B1DCD3E")]
    public interface IJustATestCOM
    {
        int Add(int a, int b);
        String SayHiTo(String someOne);
    }
}

3、项目中,新增一个类文件JustATestCOM.cs,Guid要自己生成

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;

namespace CSCOMTest
{
    [ComVisible(true)]
    [Guid("A6E4F456-32C1-4C8E-9171-D616B5DA1E20")]
    [ProgId("CSCOMTest.JustATestCOM")]
    public class JustATestCOM : IJustATestCOM
    {
        public int Add(int a, int b)
        {
            return a + b;
        }

        public String SayHiTo(String someOne)
        {
            return "Hi " + someOne + "!";
        }
    }
}

4、项目属性->Application->Assembly Infomation->Make Assembly COM-Visible->打勾

5、项目属性->Signing->Sign the assembly->打勾
项目属性->Signing->Choose a strong name key file->New

6、编译

7、注册

#这里一定要用正确版本的gacutil及RegAsm
C:\VBS\DNFW4\gacutil.exe /i CSCOMTest.dll
C:\VBS\DNFW4\RegAsm.exe CSCOMTest.dll

8、反注册

#这里一定要用正确版本的gacutil及RegAsm
C:\VBS\DNFW4\gacutil.exe /u CSCOMTest
C:\VBS\DNFW4\RegAsm.exe /u CSCOMTest.dll

9、调用测试

'发生错误时,继续运行
On Error Resume Next

'清除错误状态
Err.Clear

Set Obj=CreateObject("CSCOMTest.JustATestCOM")

'输出错误信息
If Err.Number <> 0 Then
    WScript.Echo "Error: " & Err.Number
    WScript.Echo "Error (Hex): " & Hex(Err.Number)
    WScript.Echo "Source: " &  Err.Source
    WScript.Echo "Description: " &  Err.Description
    'Err.Clear
    '退出程序
    WScript.Quit(Err.Number)
End If

'On Error Goto 0

WScript.Echo obj.Add(1,2)
WScript.Echo obj.SayHiTo("dcom")


set obj=Nothing

10、生成tlb文件

regasm CSCOMTest.dll /tlb:CSCOMTest.tlb

WSDL生成代码

1、axis2 java

rem axis2 生成client代码
wsdl2java -uri file:///C:/Users/Hansen/Desktop/test.wsdl
rem axis2 生成server代码
wsdl2java -ss -uri file:///C:/Users/Hansen/Desktop/test.wsdl

2、cxf java

rem jaxws2.2 生成代码
wsdl2java  -p com.neohope -d src -all PATH_TO_WSDL
rem jaxws2.1 生成代码
wsdl2java  -frontend jaxws21 -p com.neohope -d src -all PATH_TO_WSDL

3、wsdl soap csharp

rem cs生成client代码
wsdl C:/Users/Hansen/Desktop/test.wsdl
rem cs生成server代码
wsdl /serverInterface C:/Users/Hansen/Desktop/test.wsdl

4、svcutil wcf csharp

svcutil.exe http://localhost:1168/Service1.xamlx?wsdl /out:MyService.cs /config:app.config

使用时,WCF服务端直接实现服务接口即可。
WCF客户端,将app.config文件拷贝到项目中,然后直接调用代理类中的方法即可。

.Net4的WinForm程序使用.Net2控件

在.Net4的WinForm程序中,混用.Net2控件会报下面的问题

Mixed mode assembly is built against version 'v2.0.50727' of the runtime and cannot be loaded in the 4.0 runtime without additional configuration information.

解决方法为在App.config文件中,增加以下配置

<configuration>
  <startup useLegacyV2RuntimeActivationPolicy="true">
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
  </startup>
</configuration>